Passed
Push — master ( 34e8da...f497d2 )
by
unknown
06:10 queued 02:50
created
version.php 3 patches
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -6,13 +6,13 @@
 block discarded – undo
6 6
  */
7 7
 
8 8
 if (!defined("GROMMUNIOSYNC_VERSION")) {
9
-	$path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME'])));
10
-	$branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\""));
11
-	$version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
12
-	if ($branch && $version) {
13
-		define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version);
14
-	}
15
-	else {
16
-		define("GROMMUNIOSYNC_VERSION", "GIT");
17
-	}
9
+    $path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME'])));
10
+    $branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\""));
11
+    $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
12
+    if ($branch && $version) {
13
+        define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version);
14
+    }
15
+    else {
16
+        define("GROMMUNIOSYNC_VERSION", "GIT");
17
+    }
18 18
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -10,7 +10,7 @@
 block discarded – undo
10 10
 	$branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\""));
11 11
 	$version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
12 12
 	if ($branch && $version) {
13
-		define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version);
13
+		define("GROMMUNIOSYNC_VERSION", $branch.'-'.$version);
14 14
 	}
15 15
 	else {
16 16
 		define("GROMMUNIOSYNC_VERSION", "GIT");
Please login to merge, or discard this patch.
Braces   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -11,8 +11,7 @@
 block discarded – undo
11 11
 	$version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
12 12
 	if ($branch && $version) {
13 13
 		define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version);
14
-	}
15
-	else {
14
+	} else {
16 15
 		define("GROMMUNIOSYNC_VERSION", "GIT");
17 16
 	}
18 17
 }
Please login to merge, or discard this patch.
mapi/class.meetingrequest.php 3 patches
Indentation   +3796 added lines, -3796 removed lines patch added patch discarded remove patch
@@ -6,7 +6,7 @@  discard block
 block discarded – undo
6 6
  */
7 7
 
8 8
 class Meetingrequest {
9
-	/*
9
+    /*
10 10
 	 * NOTE
11 11
 	 *
12 12
 	 * This class is designed to modify and update meeting request properties
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
 	 *
23 23
 	 */
24 24
 
25
-	/*
25
+    /*
26 26
 	 * How to use
27 27
 	 * ----------
28 28
 	 *
@@ -73,666 +73,666 @@  discard block
 block discarded – undo
73 73
 	 *     meeting object from calendar
74 74
 	 */
75 75
 
76
-	// All properties for a recipient that are interesting
77
-	public $recipprops = [
78
-		PR_ENTRYID,
79
-		PR_DISPLAY_NAME,
80
-		PR_EMAIL_ADDRESS,
81
-		PR_RECIPIENT_ENTRYID,
82
-		PR_RECIPIENT_TYPE,
83
-		PR_SEND_INTERNET_ENCODING,
84
-		PR_SEND_RICH_INFO,
85
-		PR_RECIPIENT_DISPLAY_NAME,
86
-		PR_ADDRTYPE,
87
-		PR_DISPLAY_TYPE,
88
-		PR_DISPLAY_TYPE_EX,
89
-		PR_RECIPIENT_TRACKSTATUS,
90
-		PR_RECIPIENT_TRACKSTATUS_TIME,
91
-		PR_RECIPIENT_FLAGS,
92
-		PR_ROWID,
93
-		PR_OBJECT_TYPE,
94
-		PR_SEARCH_KEY,
95
-	];
96
-
97
-	/**
98
-	 * Indication whether the setting of resources in a Meeting Request is success (false) or if it
99
-	 * has failed (integer).
100
-	 */
101
-	public $errorSetResource;
102
-
103
-	private $proptags;
104
-	private $store;
105
-	private $message;
106
-	private $session;
107
-	private $meetingTimeInfo;
108
-	private $enableDirectBooking;
109
-	private $includesResources;
110
-	private $nonAcceptingResources;
111
-	private $recipientDisplayname;
112
-
113
-	/**
114
-	 * Constructor.
115
-	 *
116
-	 * Takes a store and a message. The message is an appointment item
117
-	 * that should be converted into a meeting request or an incoming
118
-	 * e-mail message that is a meeting request.
119
-	 *
120
-	 * The $session variable is optional, but required if the following features
121
-	 * are to be used:
122
-	 *
123
-	 * - Sending meeting requests for meetings that are not in your own store
124
-	 * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
125
-	 *
126
-	 * @param mixed $store
127
-	 * @param mixed $message
128
-	 * @param mixed $session
129
-	 * @param mixed $enableDirectBooking
130
-	 */
131
-	public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
132
-		$this->store = $store;
133
-		$this->message = $message;
134
-		$this->session = $session;
135
-		// This variable string saves time information for the MR.
136
-		$this->meetingTimeInfo = false;
137
-		$this->enableDirectBooking = $enableDirectBooking;
138
-
139
-		$properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3';
140
-		$properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23';
141
-		$properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24';
142
-		$properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5';
143
-		$properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa';
144
-		$properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1';
145
-		$properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a';
146
-		$properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:0x8217';
147
-		$properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218';
148
-		$properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4';
149
-		$properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220';
150
-		$properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582';
151
-		$properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:0x8216';
152
-		$properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501';
153
-		$properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503';
154
-		$properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200';
155
-		$properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201';                    // AppointmentSequenceNumber
156
-		$properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203';            // AppointmentLastSequence
157
-		$properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202';
158
-		$properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205';
159
-		$properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224';
160
-		$properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
161
-		$properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2';
162
-		$properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208';
163
-		$properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229';        // PidLidFInvited, MeetingRequestWasSent
164
-		$properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
165
-		$properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e';
166
-		$properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560';
167
-		$properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516';
168
-		$properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517';
169
-		$properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223';
170
-		$properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235';
171
-		$properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236';
172
-		$properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD';                // StartRecurTime
173
-		$properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE';                // StartRecurTime
174
-		$properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF';                // EndRecurDate
175
-		$properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10';                // EndRecurTime
176
-		$properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA';                // LID_IS_EXCEPTION
177
-		$properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230';
178
-		// Propose new time properties
179
-		$properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250';
180
-		$properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8251';
181
-		$properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256';
182
-		$properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:0x8257';
183
-		$properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232';
184
-		$properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:0x8228';
185
-		$properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26';
186
-		$properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:0x8233';
187
-		$properties['timezone'] = 'PT_STRING8:PSETID_Appointment:0x8234';
188
-		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
189
-		$properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B';
190
-		$properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C';
191
-
192
-		$this->proptags = getPropIdsFromStrings($store, $properties);
193
-	}
194
-
195
-	/**
196
-	 * Sets the direct booking property. This is an alternative to the setting of the direct booking
197
-	 * property through the constructor. However, setting it in the constructor is preferred.
198
-	 *
199
-	 * @param bool $directBookingSetting
200
-	 */
201
-	public function setDirectBooking($directBookingSetting) {
202
-		$this->enableDirectBooking = $directBookingSetting;
203
-	}
204
-
205
-	/**
206
-	 * Returns TRUE if the message pointed to is an incoming meeting request and should
207
-	 * therefore be replied to with doAccept or doDecline().
208
-	 *
209
-	 * @param string $messageClass message class to use for checking
210
-	 *
211
-	 * @return bool returns true if this is a meeting request else false
212
-	 */
213
-	public function isMeetingRequest($messageClass = false) {
214
-		if ($messageClass === false) {
215
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
216
-			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
217
-		}
218
-
219
-		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) {
220
-			return true;
221
-		}
222
-
223
-		return false;
224
-	}
225
-
226
-	/**
227
-	 * Returns TRUE if the message pointed to is a returning meeting request response.
228
-	 *
229
-	 * @param string $messageClass message class to use for checking
230
-	 *
231
-	 * @return bool returns true if this is a meeting request else false
232
-	 */
233
-	public function isMeetingRequestResponse($messageClass = false) {
234
-		if ($messageClass === false) {
235
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
236
-			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
237
-		}
238
-
239
-		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) {
240
-			return true;
241
-		}
242
-
243
-		return false;
244
-	}
245
-
246
-	/**
247
-	 * Returns TRUE if the message pointed to is a cancellation request.
248
-	 *
249
-	 * @param string $messageClass message class to use for checking
250
-	 *
251
-	 * @return bool returns true if this is a meeting request else false
252
-	 */
253
-	public function isMeetingCancellation($messageClass = false) {
254
-		if ($messageClass === false) {
255
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
256
-			$messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
257
-		}
258
-
259
-		if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) {
260
-			return true;
261
-		}
262
-
263
-		return false;
264
-	}
265
-
266
-	/**
267
-	 * Function is used to get the last update counter of meeting request.
268
-	 *
269
-	 * @return bool|Number false when last_updatecounter not found else return last_updatecounter
270
-	 */
271
-	public function getLastUpdateCounter() {
272
-		$calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]);
273
-		if (isset($calendarItemProps) && !empty($calendarItemProps)) {
274
-			return $calendarItemProps[$this->proptags['last_updatecounter']];
275
-		}
276
-
277
-		return false;
278
-	}
279
-
280
-	/**
281
-	 * Process an incoming meeting request response. This updates the appointment
282
-	 * in your calendar to show whether the user has accepted or declined.
283
-	 */
284
-	public function processMeetingRequestResponse() {
285
-		if (!$this->isMeetingRequestResponse()) {
286
-			return;
287
-		}
288
-
289
-		if (!$this->isLocalOrganiser()) {
290
-			return;
291
-		}
292
-
293
-		// Get information we need from the response message
294
-		$messageprops = mapi_getprops($this->message, [
295
-			$this->proptags['goid'],
296
-			$this->proptags['goid2'],
297
-			PR_OWNER_APPT_ID,
298
-			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
299
-			PR_SENT_REPRESENTING_NAME,
300
-			PR_SENT_REPRESENTING_ADDRTYPE,
301
-			PR_SENT_REPRESENTING_ENTRYID,
302
-			PR_SENT_REPRESENTING_SEARCH_KEY,
303
-			PR_MESSAGE_DELIVERY_TIME,
304
-			PR_MESSAGE_CLASS,
305
-			PR_PROCESSED,
306
-			PR_RCVD_REPRESENTING_ENTRYID,
307
-			$this->proptags['proposed_start_whole'],
308
-			$this->proptags['proposed_end_whole'],
309
-			$this->proptags['proposed_duration'],
310
-			$this->proptags['counter_proposal'],
311
-			$this->proptags['attendee_critical_change'],
312
-		]);
313
-
314
-		$goid2 = $messageprops[$this->proptags['goid2']];
315
-
316
-		if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
317
-			return;
318
-		}
319
-
320
-		// Find basedate in GlobalID(0x3), this can be a response for an occurrence
321
-		$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
322
-
323
-		// check if delegate is processing the response
324
-		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
325
-			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
326
-
327
-			$userStore = $delegatorStore['store'];
328
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
329
-		}
330
-		else {
331
-			$userStore = $this->store;
332
-			$calFolder = $this->openDefaultCalendar();
333
-		}
334
-
335
-		// check for calendar access
336
-		if ($this->checkCalendarWriteAccess($userStore) !== true) {
337
-			// Throw an exception that we don't have write permissions on calendar folder,
338
-			// allow caller to fill the error message
339
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
340
-		}
341
-
342
-		$calendarItem = $this->getCorrespondentCalendarItem(true);
343
-
344
-		// Open the calendar items, and update all the recipients of the calendar item that match
345
-		// the email address of the response.
346
-		if ($calendarItem !== false) {
347
-			$this->processResponse($userStore, $calendarItem, $basedate, $messageprops);
348
-		}
349
-	}
350
-
351
-	/**
352
-	 * Process every incoming MeetingRequest response.This updates the appointment
353
-	 * in your calendar to show whether the user has accepted or declined.
354
-	 *
355
-	 * @param resource    $store        contains the userStore in which the meeting is created
356
-	 * @param MAPIMessage $calendarItem resource of the calendar item for which this response has arrived
357
-	 * @param bool        $basedate     if present the create an exception
358
-	 * @param array       $messageprops contains message properties
359
-	 */
360
-	public function processResponse($store, $calendarItem, $basedate, $messageprops) {
361
-		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
362
-		$messageclass = $messageprops[PR_MESSAGE_CLASS];
363
-		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
364
-
365
-		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
366
-		// the email address of the response.
367
-		$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
368
-
369
-		// check if meeting response is already processed
370
-		if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
371
-			// meeting is already processed
372
-			return;
373
-		}
374
-		mapi_setprops($this->message, [PR_PROCESSED => true]);
375
-		mapi_savechanges($this->message);
376
-
377
-		// if meeting is updated in organizer's calendar then we don't need to process
378
-		// old response
379
-		if ($this->isMeetingUpdated($basedate)) {
380
-			return;
381
-		}
382
-
383
-		// If basedate is found, then create/modify exception msg and do processing
384
-		if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) {
385
-			$recurr = new Recurrence($store, $calendarItem);
386
-
387
-			// Copy properties from meeting request
388
-			$exception_props = mapi_getprops($this->message, [
389
-				PR_OWNER_APPT_ID,
390
-				$this->proptags['proposed_start_whole'],
391
-				$this->proptags['proposed_end_whole'],
392
-				$this->proptags['proposed_duration'],
393
-				$this->proptags['counter_proposal'],
394
-			]);
395
-
396
-			// Create/modify exception
397
-			if ($recurr->isException($basedate)) {
398
-				$recurr->modifyException($exception_props, $basedate);
399
-			}
400
-			else {
401
-				// When we are creating an exception we need copy recipients from main recurring item
402
-				$recipTable = mapi_message_getrecipienttable($calendarItem);
403
-				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
404
-
405
-				// Retrieve actual start/due dates from calendar item.
406
-				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
407
-				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
408
-
409
-				$recurr->createException($exception_props, $basedate, false, $recips);
410
-			}
411
-
412
-			mapi_savechanges($calendarItem);
413
-
414
-			$attach = $recurr->getExceptionAttachment($basedate);
415
-			if ($attach) {
416
-				$recurringItem = $calendarItem;
417
-				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
418
-			}
419
-			else {
420
-				return false;
421
-			}
422
-		}
423
-
424
-		// Get the recipients of the calendar item
425
-		$reciptable = mapi_message_getrecipienttable($calendarItem);
426
-		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
427
-
428
-		// FIXME we should look at the updatecounter property and compare it
429
-		// to the counter in the recipient to see if this update is actually
430
-		// newer than the status in the calendar item
431
-		$found = false;
432
-
433
-		$totalrecips = 0;
434
-		$acceptedrecips = 0;
435
-		foreach ($recipients as $recipient) {
436
-			++$totalrecips;
437
-			if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
438
-				$found = true;
439
-
440
-				/*
76
+    // All properties for a recipient that are interesting
77
+    public $recipprops = [
78
+        PR_ENTRYID,
79
+        PR_DISPLAY_NAME,
80
+        PR_EMAIL_ADDRESS,
81
+        PR_RECIPIENT_ENTRYID,
82
+        PR_RECIPIENT_TYPE,
83
+        PR_SEND_INTERNET_ENCODING,
84
+        PR_SEND_RICH_INFO,
85
+        PR_RECIPIENT_DISPLAY_NAME,
86
+        PR_ADDRTYPE,
87
+        PR_DISPLAY_TYPE,
88
+        PR_DISPLAY_TYPE_EX,
89
+        PR_RECIPIENT_TRACKSTATUS,
90
+        PR_RECIPIENT_TRACKSTATUS_TIME,
91
+        PR_RECIPIENT_FLAGS,
92
+        PR_ROWID,
93
+        PR_OBJECT_TYPE,
94
+        PR_SEARCH_KEY,
95
+    ];
96
+
97
+    /**
98
+     * Indication whether the setting of resources in a Meeting Request is success (false) or if it
99
+     * has failed (integer).
100
+     */
101
+    public $errorSetResource;
102
+
103
+    private $proptags;
104
+    private $store;
105
+    private $message;
106
+    private $session;
107
+    private $meetingTimeInfo;
108
+    private $enableDirectBooking;
109
+    private $includesResources;
110
+    private $nonAcceptingResources;
111
+    private $recipientDisplayname;
112
+
113
+    /**
114
+     * Constructor.
115
+     *
116
+     * Takes a store and a message. The message is an appointment item
117
+     * that should be converted into a meeting request or an incoming
118
+     * e-mail message that is a meeting request.
119
+     *
120
+     * The $session variable is optional, but required if the following features
121
+     * are to be used:
122
+     *
123
+     * - Sending meeting requests for meetings that are not in your own store
124
+     * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
125
+     *
126
+     * @param mixed $store
127
+     * @param mixed $message
128
+     * @param mixed $session
129
+     * @param mixed $enableDirectBooking
130
+     */
131
+    public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
132
+        $this->store = $store;
133
+        $this->message = $message;
134
+        $this->session = $session;
135
+        // This variable string saves time information for the MR.
136
+        $this->meetingTimeInfo = false;
137
+        $this->enableDirectBooking = $enableDirectBooking;
138
+
139
+        $properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3';
140
+        $properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23';
141
+        $properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24';
142
+        $properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5';
143
+        $properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa';
144
+        $properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1';
145
+        $properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a';
146
+        $properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:0x8217';
147
+        $properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218';
148
+        $properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4';
149
+        $properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220';
150
+        $properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582';
151
+        $properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:0x8216';
152
+        $properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501';
153
+        $properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503';
154
+        $properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200';
155
+        $properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201';                    // AppointmentSequenceNumber
156
+        $properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203';            // AppointmentLastSequence
157
+        $properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202';
158
+        $properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205';
159
+        $properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224';
160
+        $properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
161
+        $properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2';
162
+        $properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208';
163
+        $properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229';        // PidLidFInvited, MeetingRequestWasSent
164
+        $properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
165
+        $properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e';
166
+        $properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560';
167
+        $properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516';
168
+        $properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517';
169
+        $properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223';
170
+        $properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235';
171
+        $properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236';
172
+        $properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD';                // StartRecurTime
173
+        $properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE';                // StartRecurTime
174
+        $properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF';                // EndRecurDate
175
+        $properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10';                // EndRecurTime
176
+        $properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA';                // LID_IS_EXCEPTION
177
+        $properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230';
178
+        // Propose new time properties
179
+        $properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250';
180
+        $properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8251';
181
+        $properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256';
182
+        $properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:0x8257';
183
+        $properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232';
184
+        $properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:0x8228';
185
+        $properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26';
186
+        $properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:0x8233';
187
+        $properties['timezone'] = 'PT_STRING8:PSETID_Appointment:0x8234';
188
+        $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
189
+        $properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B';
190
+        $properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C';
191
+
192
+        $this->proptags = getPropIdsFromStrings($store, $properties);
193
+    }
194
+
195
+    /**
196
+     * Sets the direct booking property. This is an alternative to the setting of the direct booking
197
+     * property through the constructor. However, setting it in the constructor is preferred.
198
+     *
199
+     * @param bool $directBookingSetting
200
+     */
201
+    public function setDirectBooking($directBookingSetting) {
202
+        $this->enableDirectBooking = $directBookingSetting;
203
+    }
204
+
205
+    /**
206
+     * Returns TRUE if the message pointed to is an incoming meeting request and should
207
+     * therefore be replied to with doAccept or doDecline().
208
+     *
209
+     * @param string $messageClass message class to use for checking
210
+     *
211
+     * @return bool returns true if this is a meeting request else false
212
+     */
213
+    public function isMeetingRequest($messageClass = false) {
214
+        if ($messageClass === false) {
215
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
216
+            $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
217
+        }
218
+
219
+        if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) {
220
+            return true;
221
+        }
222
+
223
+        return false;
224
+    }
225
+
226
+    /**
227
+     * Returns TRUE if the message pointed to is a returning meeting request response.
228
+     *
229
+     * @param string $messageClass message class to use for checking
230
+     *
231
+     * @return bool returns true if this is a meeting request else false
232
+     */
233
+    public function isMeetingRequestResponse($messageClass = false) {
234
+        if ($messageClass === false) {
235
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
236
+            $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
237
+        }
238
+
239
+        if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) {
240
+            return true;
241
+        }
242
+
243
+        return false;
244
+    }
245
+
246
+    /**
247
+     * Returns TRUE if the message pointed to is a cancellation request.
248
+     *
249
+     * @param string $messageClass message class to use for checking
250
+     *
251
+     * @return bool returns true if this is a meeting request else false
252
+     */
253
+    public function isMeetingCancellation($messageClass = false) {
254
+        if ($messageClass === false) {
255
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
256
+            $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false;
257
+        }
258
+
259
+        if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) {
260
+            return true;
261
+        }
262
+
263
+        return false;
264
+    }
265
+
266
+    /**
267
+     * Function is used to get the last update counter of meeting request.
268
+     *
269
+     * @return bool|Number false when last_updatecounter not found else return last_updatecounter
270
+     */
271
+    public function getLastUpdateCounter() {
272
+        $calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]);
273
+        if (isset($calendarItemProps) && !empty($calendarItemProps)) {
274
+            return $calendarItemProps[$this->proptags['last_updatecounter']];
275
+        }
276
+
277
+        return false;
278
+    }
279
+
280
+    /**
281
+     * Process an incoming meeting request response. This updates the appointment
282
+     * in your calendar to show whether the user has accepted or declined.
283
+     */
284
+    public function processMeetingRequestResponse() {
285
+        if (!$this->isMeetingRequestResponse()) {
286
+            return;
287
+        }
288
+
289
+        if (!$this->isLocalOrganiser()) {
290
+            return;
291
+        }
292
+
293
+        // Get information we need from the response message
294
+        $messageprops = mapi_getprops($this->message, [
295
+            $this->proptags['goid'],
296
+            $this->proptags['goid2'],
297
+            PR_OWNER_APPT_ID,
298
+            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
299
+            PR_SENT_REPRESENTING_NAME,
300
+            PR_SENT_REPRESENTING_ADDRTYPE,
301
+            PR_SENT_REPRESENTING_ENTRYID,
302
+            PR_SENT_REPRESENTING_SEARCH_KEY,
303
+            PR_MESSAGE_DELIVERY_TIME,
304
+            PR_MESSAGE_CLASS,
305
+            PR_PROCESSED,
306
+            PR_RCVD_REPRESENTING_ENTRYID,
307
+            $this->proptags['proposed_start_whole'],
308
+            $this->proptags['proposed_end_whole'],
309
+            $this->proptags['proposed_duration'],
310
+            $this->proptags['counter_proposal'],
311
+            $this->proptags['attendee_critical_change'],
312
+        ]);
313
+
314
+        $goid2 = $messageprops[$this->proptags['goid2']];
315
+
316
+        if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
317
+            return;
318
+        }
319
+
320
+        // Find basedate in GlobalID(0x3), this can be a response for an occurrence
321
+        $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
322
+
323
+        // check if delegate is processing the response
324
+        if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
325
+            $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
326
+
327
+            $userStore = $delegatorStore['store'];
328
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
329
+        }
330
+        else {
331
+            $userStore = $this->store;
332
+            $calFolder = $this->openDefaultCalendar();
333
+        }
334
+
335
+        // check for calendar access
336
+        if ($this->checkCalendarWriteAccess($userStore) !== true) {
337
+            // Throw an exception that we don't have write permissions on calendar folder,
338
+            // allow caller to fill the error message
339
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
340
+        }
341
+
342
+        $calendarItem = $this->getCorrespondentCalendarItem(true);
343
+
344
+        // Open the calendar items, and update all the recipients of the calendar item that match
345
+        // the email address of the response.
346
+        if ($calendarItem !== false) {
347
+            $this->processResponse($userStore, $calendarItem, $basedate, $messageprops);
348
+        }
349
+    }
350
+
351
+    /**
352
+     * Process every incoming MeetingRequest response.This updates the appointment
353
+     * in your calendar to show whether the user has accepted or declined.
354
+     *
355
+     * @param resource    $store        contains the userStore in which the meeting is created
356
+     * @param MAPIMessage $calendarItem resource of the calendar item for which this response has arrived
357
+     * @param bool        $basedate     if present the create an exception
358
+     * @param array       $messageprops contains message properties
359
+     */
360
+    public function processResponse($store, $calendarItem, $basedate, $messageprops) {
361
+        $senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
362
+        $messageclass = $messageprops[PR_MESSAGE_CLASS];
363
+        $deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
364
+
365
+        // Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
366
+        // the email address of the response.
367
+        $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
368
+
369
+        // check if meeting response is already processed
370
+        if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
371
+            // meeting is already processed
372
+            return;
373
+        }
374
+        mapi_setprops($this->message, [PR_PROCESSED => true]);
375
+        mapi_savechanges($this->message);
376
+
377
+        // if meeting is updated in organizer's calendar then we don't need to process
378
+        // old response
379
+        if ($this->isMeetingUpdated($basedate)) {
380
+            return;
381
+        }
382
+
383
+        // If basedate is found, then create/modify exception msg and do processing
384
+        if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) {
385
+            $recurr = new Recurrence($store, $calendarItem);
386
+
387
+            // Copy properties from meeting request
388
+            $exception_props = mapi_getprops($this->message, [
389
+                PR_OWNER_APPT_ID,
390
+                $this->proptags['proposed_start_whole'],
391
+                $this->proptags['proposed_end_whole'],
392
+                $this->proptags['proposed_duration'],
393
+                $this->proptags['counter_proposal'],
394
+            ]);
395
+
396
+            // Create/modify exception
397
+            if ($recurr->isException($basedate)) {
398
+                $recurr->modifyException($exception_props, $basedate);
399
+            }
400
+            else {
401
+                // When we are creating an exception we need copy recipients from main recurring item
402
+                $recipTable = mapi_message_getrecipienttable($calendarItem);
403
+                $recips = mapi_table_queryallrows($recipTable, $this->recipprops);
404
+
405
+                // Retrieve actual start/due dates from calendar item.
406
+                $exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
407
+                $exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
408
+
409
+                $recurr->createException($exception_props, $basedate, false, $recips);
410
+            }
411
+
412
+            mapi_savechanges($calendarItem);
413
+
414
+            $attach = $recurr->getExceptionAttachment($basedate);
415
+            if ($attach) {
416
+                $recurringItem = $calendarItem;
417
+                $calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
418
+            }
419
+            else {
420
+                return false;
421
+            }
422
+        }
423
+
424
+        // Get the recipients of the calendar item
425
+        $reciptable = mapi_message_getrecipienttable($calendarItem);
426
+        $recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
427
+
428
+        // FIXME we should look at the updatecounter property and compare it
429
+        // to the counter in the recipient to see if this update is actually
430
+        // newer than the status in the calendar item
431
+        $found = false;
432
+
433
+        $totalrecips = 0;
434
+        $acceptedrecips = 0;
435
+        foreach ($recipients as $recipient) {
436
+            ++$totalrecips;
437
+            if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
438
+                $found = true;
439
+
440
+                /*
441 441
 				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
442 442
 				 * on the corresponding recipientRow of meeting then we ignore this response mail.
443 443
 				 */
444
-				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
445
-					continue;
446
-				}
447
-
448
-				// The email address matches, update the row
449
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
450
-				$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
451
-
452
-				// If this is a counter proposal, set the proposal properties in the recipient row
453
-				if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
454
-					$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
455
-					$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
456
-					$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
457
-				}
458
-
459
-				mapi_message_modifyrecipients($calendarItem, MODRECIP_MODIFY, [$recipient]);
460
-			}
461
-			if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
462
-				++$acceptedrecips;
463
-			}
464
-		}
465
-
466
-		// If the recipient was not found in the original calendar item,
467
-		// then add the recpient as a new optional recipient
468
-		if (!$found) {
469
-			$recipient = [];
470
-			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
471
-			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
472
-			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
473
-			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
474
-			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
475
-			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
476
-			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
477
-			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
478
-
479
-			// If this is a counter proposal, set the proposal properties in the recipient row
480
-			if (isset($messageprops[$this->proptags['counter_proposal']])) {
481
-				$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
482
-				$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
483
-				$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
484
-			}
485
-
486
-			mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
487
-			++$totalrecips;
488
-			if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
489
-				++$acceptedrecips;
490
-			}
491
-		}
492
-
493
-		// TODO: Update counter proposal number property on message
494
-		/*
444
+                if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
445
+                    continue;
446
+                }
447
+
448
+                // The email address matches, update the row
449
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
450
+                $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
451
+
452
+                // If this is a counter proposal, set the proposal properties in the recipient row
453
+                if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
454
+                    $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
455
+                    $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
456
+                    $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
457
+                }
458
+
459
+                mapi_message_modifyrecipients($calendarItem, MODRECIP_MODIFY, [$recipient]);
460
+            }
461
+            if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
462
+                ++$acceptedrecips;
463
+            }
464
+        }
465
+
466
+        // If the recipient was not found in the original calendar item,
467
+        // then add the recpient as a new optional recipient
468
+        if (!$found) {
469
+            $recipient = [];
470
+            $recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
471
+            $recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
472
+            $recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
473
+            $recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
474
+            $recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
475
+            $recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
476
+            $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
477
+            $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
478
+
479
+            // If this is a counter proposal, set the proposal properties in the recipient row
480
+            if (isset($messageprops[$this->proptags['counter_proposal']])) {
481
+                $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
482
+                $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
483
+                $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
484
+            }
485
+
486
+            mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]);
487
+            ++$totalrecips;
488
+            if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
489
+                ++$acceptedrecips;
490
+            }
491
+        }
492
+
493
+        // TODO: Update counter proposal number property on message
494
+        /*
495 495
 		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.
496 496
 		*/
497
-		// If this is a counter proposal, set the counter proposal indicator boolean
498
-		if (isset($messageprops[$this->proptags['counter_proposal']])) {
499
-			$props = [];
500
-			if ($messageprops[$this->proptags['counter_proposal']]) {
501
-				$props[$this->proptags['counter_proposal']] = true;
502
-			}
503
-			else {
504
-				$props[$this->proptags['counter_proposal']] = false;
505
-			}
506
-
507
-			mapi_setprops($calendarItem, $props);
508
-		}
509
-
510
-		mapi_savechanges($calendarItem);
511
-		if (isset($attach)) {
512
-			mapi_savechanges($attach);
513
-			mapi_savechanges($recurringItem);
514
-		}
515
-	}
516
-
517
-	/**
518
-	 * Process an incoming meeting request cancellation. This updates the
519
-	 * appointment in your calendar to show that the meeting has been cancelled.
520
-	 */
521
-	public function processMeetingCancellation() {
522
-		if (!$this->isMeetingCancellation()) {
523
-			return;
524
-		}
525
-
526
-		if ($this->isLocalOrganiser()) {
527
-			return;
528
-		}
529
-
530
-		if (!$this->isInCalendar()) {
531
-			return;
532
-		}
533
-
534
-		$listProperties = $this->proptags;
535
-		$listProperties['subject'] = PR_SUBJECT;
536
-		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
537
-		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
538
-		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
539
-		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
540
-		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
541
-		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
542
-		$listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
543
-		$listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
544
-		$listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
545
-		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
546
-		$messageProps = mapi_getprops($this->message, $listProperties);
547
-
548
-		$goid = $messageProps[$this->proptags['goid']];    // GlobalID (0x3)
549
-		if (!isset($goid)) {
550
-			return;
551
-		}
552
-
553
-		// get delegator store, if delegate is processing this cancellation
554
-		if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
555
-			$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
556
-
557
-			$store = $delegatorStore['store'];
558
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
559
-		}
560
-		else {
561
-			$store = $this->store;
562
-			$calFolder = $this->openDefaultCalendar();
563
-		}
564
-
565
-		// check for calendar access
566
-		if ($this->checkCalendarWriteAccess($store) !== true) {
567
-			// Throw an exception that we don't have write permissions on calendar folder,
568
-			// allow caller to fill the error message
569
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
570
-		}
571
-
572
-		$calendarItem = $this->getCorrespondentCalendarItem(true);
573
-		$basedate = $this->getBasedateFromGlobalID($goid);
574
-
575
-		if ($calendarItem !== false) {
576
-			// if basedate is provided and we could not find the item then it could be that we are processing
577
-			// an exception so get the exception and process it
578
-			if ($basedate) {
579
-				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]);
580
-				if ($calendarItemProps[$this->proptags['recurring']] === true) {
581
-					$recurr = new Recurrence($store, $calendarItem);
582
-
583
-					// Set message class
584
-					$messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
585
-
586
-					if ($recurr->isException($basedate)) {
587
-						$recurr->modifyException($messageProps, $basedate);
588
-					}
589
-					else {
590
-						$recurr->createException($messageProps, $basedate);
591
-					}
592
-				}
593
-			}
594
-			else {
595
-				// set the properties of the cancellation object
596
-				mapi_setprops($calendarItem, $messageProps);
597
-			}
598
-
599
-			mapi_savechanges($calendarItem);
600
-		}
601
-	}
602
-
603
-	/**
604
-	 * Returns true if the corresponding calendar items exists in the celendar folder for this
605
-	 * meeting request/response/cancellation.
606
-	 */
607
-	public function isInCalendar() {
608
-		// @TODO check for deleted exceptions
609
-		return $this->getCorrespondentCalendarItem(false) !== false;
610
-	}
611
-
612
-	/**
613
-	 * Accepts the meeting request by moving the item to the calendar
614
-	 * and sending a confirmation message back to the sender. If $tentative
615
-	 * is TRUE, then the item is accepted tentatively. After accepting, you
616
-	 * can't use this class instance any more. The message is closed. If you
617
-	 * specify TRUE for 'move', then the item is actually moved (from your
618
-	 * inbox probably) to the calendar. If you don't, it is copied into
619
-	 * your calendar.
620
-	 *
621
-	 * @param bool   $tentative            true if user as tentative accepted the meeting
622
-	 * @param bool   $sendresponse         true if a response has to be send to organizer
623
-	 * @param bool   $move                 true if the meeting request should be moved to the deleted items after processing
624
-	 * @param string $newProposedStartTime contains starttime if user has proposed other time
625
-	 * @param string $newProposedEndTime   contains endtime if user has proposed other time
626
-	 * @param string $basedate             start of day of occurrence for which user has accepted the recurrent meeting
627
-	 * @param bool   $isImported           true to indicate that MR is imported from .ics or .vcs file else it false.
628
-	 * @param mixed  $body
629
-	 * @param mixed  $userAction
630
-	 * @param mixed  $store
631
-	 *
632
-	 * @return string $entryid entryid of item which created/updated in calendar
633
-	 */
634
-	public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) {
635
-		if ($this->isLocalOrganiser()) {
636
-			return false;
637
-		}
638
-
639
-		// Remove any previous calendar items with this goid and appt id
640
-		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['updatecounter'], PR_PROCESSED, PR_RCVD_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID, PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID]);
641
-
642
-		// If this meeting request is received by a delegate then open delegator's store.
643
-		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
644
-			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
645
-
646
-			$store = $delegatorStore['store'];
647
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
648
-		}
649
-		else {
650
-			$calFolder = $this->openDefaultCalendar();
651
-			$store = $this->store;
652
-		}
653
-
654
-		// check for calendar access
655
-		if ($this->checkCalendarWriteAccess($store) !== true) {
656
-			// Throw an exception that we don't have write permissions on calendar folder,
657
-			// allow caller to fill the error message
658
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
659
-		}
660
-
661
-		// if meeting is out dated then don't process it
662
-		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) {
663
-			return false;
664
-		}
665
-
666
-		/*
497
+        // If this is a counter proposal, set the counter proposal indicator boolean
498
+        if (isset($messageprops[$this->proptags['counter_proposal']])) {
499
+            $props = [];
500
+            if ($messageprops[$this->proptags['counter_proposal']]) {
501
+                $props[$this->proptags['counter_proposal']] = true;
502
+            }
503
+            else {
504
+                $props[$this->proptags['counter_proposal']] = false;
505
+            }
506
+
507
+            mapi_setprops($calendarItem, $props);
508
+        }
509
+
510
+        mapi_savechanges($calendarItem);
511
+        if (isset($attach)) {
512
+            mapi_savechanges($attach);
513
+            mapi_savechanges($recurringItem);
514
+        }
515
+    }
516
+
517
+    /**
518
+     * Process an incoming meeting request cancellation. This updates the
519
+     * appointment in your calendar to show that the meeting has been cancelled.
520
+     */
521
+    public function processMeetingCancellation() {
522
+        if (!$this->isMeetingCancellation()) {
523
+            return;
524
+        }
525
+
526
+        if ($this->isLocalOrganiser()) {
527
+            return;
528
+        }
529
+
530
+        if (!$this->isInCalendar()) {
531
+            return;
532
+        }
533
+
534
+        $listProperties = $this->proptags;
535
+        $listProperties['subject'] = PR_SUBJECT;
536
+        $listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
537
+        $listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
538
+        $listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
539
+        $listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
540
+        $listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
541
+        $listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
542
+        $listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE;
543
+        $listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
544
+        $listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID;
545
+        $listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
546
+        $messageProps = mapi_getprops($this->message, $listProperties);
547
+
548
+        $goid = $messageProps[$this->proptags['goid']];    // GlobalID (0x3)
549
+        if (!isset($goid)) {
550
+            return;
551
+        }
552
+
553
+        // get delegator store, if delegate is processing this cancellation
554
+        if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
555
+            $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
556
+
557
+            $store = $delegatorStore['store'];
558
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
559
+        }
560
+        else {
561
+            $store = $this->store;
562
+            $calFolder = $this->openDefaultCalendar();
563
+        }
564
+
565
+        // check for calendar access
566
+        if ($this->checkCalendarWriteAccess($store) !== true) {
567
+            // Throw an exception that we don't have write permissions on calendar folder,
568
+            // allow caller to fill the error message
569
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
570
+        }
571
+
572
+        $calendarItem = $this->getCorrespondentCalendarItem(true);
573
+        $basedate = $this->getBasedateFromGlobalID($goid);
574
+
575
+        if ($calendarItem !== false) {
576
+            // if basedate is provided and we could not find the item then it could be that we are processing
577
+            // an exception so get the exception and process it
578
+            if ($basedate) {
579
+                $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]);
580
+                if ($calendarItemProps[$this->proptags['recurring']] === true) {
581
+                    $recurr = new Recurrence($store, $calendarItem);
582
+
583
+                    // Set message class
584
+                    $messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
585
+
586
+                    if ($recurr->isException($basedate)) {
587
+                        $recurr->modifyException($messageProps, $basedate);
588
+                    }
589
+                    else {
590
+                        $recurr->createException($messageProps, $basedate);
591
+                    }
592
+                }
593
+            }
594
+            else {
595
+                // set the properties of the cancellation object
596
+                mapi_setprops($calendarItem, $messageProps);
597
+            }
598
+
599
+            mapi_savechanges($calendarItem);
600
+        }
601
+    }
602
+
603
+    /**
604
+     * Returns true if the corresponding calendar items exists in the celendar folder for this
605
+     * meeting request/response/cancellation.
606
+     */
607
+    public function isInCalendar() {
608
+        // @TODO check for deleted exceptions
609
+        return $this->getCorrespondentCalendarItem(false) !== false;
610
+    }
611
+
612
+    /**
613
+     * Accepts the meeting request by moving the item to the calendar
614
+     * and sending a confirmation message back to the sender. If $tentative
615
+     * is TRUE, then the item is accepted tentatively. After accepting, you
616
+     * can't use this class instance any more. The message is closed. If you
617
+     * specify TRUE for 'move', then the item is actually moved (from your
618
+     * inbox probably) to the calendar. If you don't, it is copied into
619
+     * your calendar.
620
+     *
621
+     * @param bool   $tentative            true if user as tentative accepted the meeting
622
+     * @param bool   $sendresponse         true if a response has to be send to organizer
623
+     * @param bool   $move                 true if the meeting request should be moved to the deleted items after processing
624
+     * @param string $newProposedStartTime contains starttime if user has proposed other time
625
+     * @param string $newProposedEndTime   contains endtime if user has proposed other time
626
+     * @param string $basedate             start of day of occurrence for which user has accepted the recurrent meeting
627
+     * @param bool   $isImported           true to indicate that MR is imported from .ics or .vcs file else it false.
628
+     * @param mixed  $body
629
+     * @param mixed  $userAction
630
+     * @param mixed  $store
631
+     *
632
+     * @return string $entryid entryid of item which created/updated in calendar
633
+     */
634
+    public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) {
635
+        if ($this->isLocalOrganiser()) {
636
+            return false;
637
+        }
638
+
639
+        // Remove any previous calendar items with this goid and appt id
640
+        $messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['updatecounter'], PR_PROCESSED, PR_RCVD_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID, PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID]);
641
+
642
+        // If this meeting request is received by a delegate then open delegator's store.
643
+        if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
644
+            $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
645
+
646
+            $store = $delegatorStore['store'];
647
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
648
+        }
649
+        else {
650
+            $calFolder = $this->openDefaultCalendar();
651
+            $store = $this->store;
652
+        }
653
+
654
+        // check for calendar access
655
+        if ($this->checkCalendarWriteAccess($store) !== true) {
656
+            // Throw an exception that we don't have write permissions on calendar folder,
657
+            // allow caller to fill the error message
658
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
659
+        }
660
+
661
+        // if meeting is out dated then don't process it
662
+        if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) {
663
+            return false;
664
+        }
665
+
666
+        /*
667 667
 		 *    if this function is called automatically with meeting request object then there will be
668 668
 		 *    two possibilitites
669 669
 		 *    1) meeting request is opened first time, in this case make a tentative appointment in
670 670
 		 * recipient's calendar
671 671
 		 *    2) after this every subsequent request to open meeting request will not do any processing
672 672
 		 */
673
-		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) {
674
-			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
675
-				// if meeting request is already processed then don't do anything
676
-				return false;
677
-			}
678
-
679
-			// if correspondent calendar item is already processed then don't do anything
680
-			$calendarItem = $this->getCorrespondentCalendarItem();
681
-			$calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]);
682
-			if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) {
683
-				// mark meeting-request mail as processed as well
684
-				mapi_setprops($this->message, [PR_PROCESSED => true]);
685
-				mapi_savechanges($this->message);
686
-
687
-				return false;
688
-			}
689
-		}
690
-
691
-		// Retrieve basedate from globalID, if it is not received as argument
692
-		if (!$basedate) {
693
-			$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
694
-		}
695
-
696
-		// set counter proposal properties in calendar item when proposing new time
697
-		$proposeNewTimeProps = [];
698
-		if ($newProposedStartTime && $newProposedEndTime) {
699
-			$proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
700
-			$proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
701
-			$proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
702
-			$proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
703
-		}
704
-
705
-		// While sender is receiver then we have to process the meeting request as per the intended busy status
706
-		// instead of tentative, and accept the same as per the intended busystatus.
707
-		$senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID];
708
-		if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) {
709
-			$entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate);
710
-		}
711
-		else {
712
-			$entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate);
713
-		}
714
-
715
-		// if we have first time processed this meeting then set PR_PROCESSED property
716
-		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) {
717
-			if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) {
718
-				// set processed flag
719
-				mapi_setprops($this->message, [PR_PROCESSED => true]);
720
-				mapi_savechanges($this->message);
721
-			}
722
-		}
723
-
724
-		return $entryid;
725
-	}
726
-
727
-	public function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = [], $body = false, $userAction = false, $store, $calFolder, $basedate = false) {
728
-		$messageprops = mapi_getprops($this->message);
729
-		$isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]);
730
-
731
-		if ($sendresponse) {
732
-			$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder);
733
-		}
734
-
735
-		/*
673
+        if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) {
674
+            if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
675
+                // if meeting request is already processed then don't do anything
676
+                return false;
677
+            }
678
+
679
+            // if correspondent calendar item is already processed then don't do anything
680
+            $calendarItem = $this->getCorrespondentCalendarItem();
681
+            $calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]);
682
+            if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) {
683
+                // mark meeting-request mail as processed as well
684
+                mapi_setprops($this->message, [PR_PROCESSED => true]);
685
+                mapi_savechanges($this->message);
686
+
687
+                return false;
688
+            }
689
+        }
690
+
691
+        // Retrieve basedate from globalID, if it is not received as argument
692
+        if (!$basedate) {
693
+            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
694
+        }
695
+
696
+        // set counter proposal properties in calendar item when proposing new time
697
+        $proposeNewTimeProps = [];
698
+        if ($newProposedStartTime && $newProposedEndTime) {
699
+            $proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
700
+            $proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
701
+            $proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
702
+            $proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
703
+        }
704
+
705
+        // While sender is receiver then we have to process the meeting request as per the intended busy status
706
+        // instead of tentative, and accept the same as per the intended busystatus.
707
+        $senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID];
708
+        if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) {
709
+            $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate);
710
+        }
711
+        else {
712
+            $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate);
713
+        }
714
+
715
+        // if we have first time processed this meeting then set PR_PROCESSED property
716
+        if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) {
717
+            if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) {
718
+                // set processed flag
719
+                mapi_setprops($this->message, [PR_PROCESSED => true]);
720
+                mapi_savechanges($this->message);
721
+            }
722
+        }
723
+
724
+        return $entryid;
725
+    }
726
+
727
+    public function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = [], $body = false, $userAction = false, $store, $calFolder, $basedate = false) {
728
+        $messageprops = mapi_getprops($this->message);
729
+        $isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]);
730
+
731
+        if ($sendresponse) {
732
+            $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder);
733
+        }
734
+
735
+        /*
736 736
 		 * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
737 737
 		 * 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.
738 738
 		 * 2) If single occurrence then find occurrence itself using globalID and if item is not found then use cleanGlobalID to find main recurring item
@@ -742,2394 +742,2394 @@  discard block
 block discarded – undo
742 742
 		 * 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.
743 743
 		 * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
744 744
 		 */
745
-		if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
746
-			// While processing the item mark it as read.
747
-			mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
748
-
749
-			// This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
750
-			if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true) {
751
-				$calendarItem = false;
752
-
753
-				// Find main recurring item based on GlobalID (0x3)
754
-				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
755
-				if (is_array($items)) {
756
-					foreach ($items as $key => $entryid) {
757
-						$calendarItem = mapi_msgstore_openentry($store, $entryid);
758
-					}
759
-				}
760
-
761
-				$processed = false;
762
-				if (!$calendarItem) {
763
-					// Recurring item not found, so create new meeting in Calendar
764
-					$calendarItem = mapi_folder_createmessage($calFolder);
765
-				}
766
-				else {
767
-					// we have found the main recurring item, check if this meeting request is already processed
768
-					if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
769
-						// only set required properties, other properties are already copied when processing this meeting request
770
-						// for the first time
771
-						$processed = true;
772
-					}
773
-				}
774
-
775
-				if (!$processed) {
776
-					// get all the properties and copy that to calendar item
777
-					$props = mapi_getprops($this->message);
778
-
779
-					/*
745
+        if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
746
+            // While processing the item mark it as read.
747
+            mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
748
+
749
+            // This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
750
+            if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true) {
751
+                $calendarItem = false;
752
+
753
+                // Find main recurring item based on GlobalID (0x3)
754
+                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
755
+                if (is_array($items)) {
756
+                    foreach ($items as $key => $entryid) {
757
+                        $calendarItem = mapi_msgstore_openentry($store, $entryid);
758
+                    }
759
+                }
760
+
761
+                $processed = false;
762
+                if (!$calendarItem) {
763
+                    // Recurring item not found, so create new meeting in Calendar
764
+                    $calendarItem = mapi_folder_createmessage($calFolder);
765
+                }
766
+                else {
767
+                    // we have found the main recurring item, check if this meeting request is already processed
768
+                    if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
769
+                        // only set required properties, other properties are already copied when processing this meeting request
770
+                        // for the first time
771
+                        $processed = true;
772
+                    }
773
+                }
774
+
775
+                if (!$processed) {
776
+                    // get all the properties and copy that to calendar item
777
+                    $props = mapi_getprops($this->message);
778
+
779
+                    /*
780 780
 					 * the client which has sent this meeting request can generate wrong flagdueby
781 781
 					 * time (mainly OL), so regenerate that property so we will always show reminder
782 782
 					 * on right time
783 783
 					 */
784
-					if (isset($props[$this->proptags['reminderminutes']])) {
785
-						$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
786
-					}
787
-				}
788
-				else {
789
-					// only get required properties so we will not overwrite existing updated properties from calendar
790
-					$props = mapi_getprops($this->message, [PR_ENTRYID]);
791
-				}
792
-
793
-				// While we applying updates of MR then all local categories will be removed,
794
-				// So get the local categories of all occurrence before applying update from organiser.
795
-				$localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
796
-
797
-				$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
798
-				// When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
799
-				if (!isset($props[$this->proptags['updatecounter']])) {
800
-					$props[$this->proptags['updatecounter']] = 0;
801
-				}
802
-				$props[$this->proptags['meetingstatus']] = olMeetingReceived;
803
-				// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
804
-				$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
805
-
806
-				if (isset($props[$this->proptags['intendedbusystatus']])) {
807
-					if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
808
-						$props[$this->proptags['busystatus']] = fbTentative;
809
-					}
810
-					else {
811
-						$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
812
-					}
813
-					// we already have intendedbusystatus value in $props so no need to copy it
814
-				}
815
-				else {
816
-					$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
817
-				}
818
-
819
-				if ($userAction) {
820
-					$addrInfo = $this->getOwnerAddress($this->store);
821
-
822
-					// if user has responded then set replytime and name
823
-					$props[$this->proptags['replytime']] = time();
824
-					if (!empty($addrInfo)) {
825
-						// @FIXME conditionally set this property only for delegation case
826
-						$props[$this->proptags['apptreplyname']] = $addrInfo[0];
827
-					}
828
-				}
829
-
830
-				mapi_setprops($calendarItem, $props);
831
-
832
-				// we have already processed attachments and recipients, so no need to do it again
833
-				if (!$processed) {
834
-					// Copy attachments too
835
-					$this->replaceAttachments($this->message, $calendarItem);
836
-					// Copy recipients too
837
-					$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
838
-				}
839
-
840
-				// Find all occurrences based on CleanGlobalID (0x23)
841
-				// there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck
842
-				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
843
-				if (is_array($items)) {
844
-					// Save all existing occurrence as exceptions
845
-					foreach ($items as $entryid) {
846
-						// Open occurrence
847
-						$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
848
-
849
-						// Save occurrence into main recurring item as exception
850
-						if ($occurrenceItem) {
851
-							$occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
852
-
853
-							// Find basedate of occurrence item
854
-							$basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
855
-							if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
856
-								$this->mergeException($calendarItem, $occurrenceItem, $basedate, $store);
857
-							}
858
-						}
859
-					}
860
-				}
861
-
862
-				mapi_savechanges($calendarItem);
863
-
864
-				// After applying update of organiser all local categories of occurrence was removed,
865
-				// So if local categories exist then apply it on respective occurrence.
866
-				if (!empty($localCategories)) {
867
-					$this->applyLocalCategories($calendarItem, $store, $localCategories);
868
-				}
869
-
870
-				if ($move) {
871
-					// open wastebasket of currently logged in user and move the meeting request to it
872
-					// for delegates this will be delegate's wastebasket folder
873
-					$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
874
-					mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
875
-				}
876
-
877
-				$entryid = $props[PR_ENTRYID];
878
-			}
879
-			else {
880
-				/**
881
-				 * This meeting request is not recurring, so can be an exception or normal meeting.
882
-				 * If exception then find main recurring item and update exception
883
-				 * If main recurring item is not found then put exception into Calendar as normal meeting.
884
-				 */
885
-				$calendarItem = false;
886
-
887
-				// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
888
-				if ($basedate) {
889
-					// Find main recurring item from CleanGlobalID of this meeting request
890
-					$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
891
-					if (is_array($items)) {
892
-						foreach ($items as $key => $entryid) {
893
-							$calendarItem = mapi_msgstore_openentry($store, $entryid);
894
-						}
895
-					}
896
-
897
-					// Main recurring item is found, so now update exception
898
-					if ($calendarItem) {
899
-						$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
900
-						$calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
901
-						$entryid = $calendarItemProps[PR_ENTRYID];
902
-					}
903
-				}
904
-
905
-				if (!$calendarItem) {
906
-					$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
907
-					if (is_array($items)) {
908
-						// Get local categories before deleting MR.
909
-						$message = mapi_msgstore_openentry($store, $items[0]);
910
-						$localCategories = mapi_getprops($message, [$this->proptags['categories']]);
911
-						mapi_folder_deletemessages($calFolder, $items);
912
-					}
913
-
914
-					if ($move) {
915
-						// All we have to do is open the default calendar,
916
-						// set the message class correctly to be an appointment item
917
-						// and move it to the calendar folder
918
-						$sourcefolder = $this->openParentFolder();
919
-
920
-						// create a new calendar message, and copy the message to there,
921
-						// since we want to delete (move to wastebasket) the original message
922
-						$old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
923
-						$calmsg = mapi_folder_createmessage($calFolder);
924
-						mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
925
-
926
-						// After creating new MR, If local categories exist then apply it on new MR.
927
-						if (!empty($localCategories)) {
928
-							mapi_setprops($calmsg, $localCategories);
929
-						}
930
-
931
-						$calItemProps = [];
932
-						$calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
933
-
934
-						/*
784
+                    if (isset($props[$this->proptags['reminderminutes']])) {
785
+                        $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
786
+                    }
787
+                }
788
+                else {
789
+                    // only get required properties so we will not overwrite existing updated properties from calendar
790
+                    $props = mapi_getprops($this->message, [PR_ENTRYID]);
791
+                }
792
+
793
+                // While we applying updates of MR then all local categories will be removed,
794
+                // So get the local categories of all occurrence before applying update from organiser.
795
+                $localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
796
+
797
+                $props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
798
+                // When meeting requests are generated by third-party solutions, we might be missing the updatecounter property.
799
+                if (!isset($props[$this->proptags['updatecounter']])) {
800
+                    $props[$this->proptags['updatecounter']] = 0;
801
+                }
802
+                $props[$this->proptags['meetingstatus']] = olMeetingReceived;
803
+                // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
804
+                $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
805
+
806
+                if (isset($props[$this->proptags['intendedbusystatus']])) {
807
+                    if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
808
+                        $props[$this->proptags['busystatus']] = fbTentative;
809
+                    }
810
+                    else {
811
+                        $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
812
+                    }
813
+                    // we already have intendedbusystatus value in $props so no need to copy it
814
+                }
815
+                else {
816
+                    $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
817
+                }
818
+
819
+                if ($userAction) {
820
+                    $addrInfo = $this->getOwnerAddress($this->store);
821
+
822
+                    // if user has responded then set replytime and name
823
+                    $props[$this->proptags['replytime']] = time();
824
+                    if (!empty($addrInfo)) {
825
+                        // @FIXME conditionally set this property only for delegation case
826
+                        $props[$this->proptags['apptreplyname']] = $addrInfo[0];
827
+                    }
828
+                }
829
+
830
+                mapi_setprops($calendarItem, $props);
831
+
832
+                // we have already processed attachments and recipients, so no need to do it again
833
+                if (!$processed) {
834
+                    // Copy attachments too
835
+                    $this->replaceAttachments($this->message, $calendarItem);
836
+                    // Copy recipients too
837
+                    $this->replaceRecipients($this->message, $calendarItem, $isDelegate);
838
+                }
839
+
840
+                // Find all occurrences based on CleanGlobalID (0x23)
841
+                // there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck
842
+                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
843
+                if (is_array($items)) {
844
+                    // Save all existing occurrence as exceptions
845
+                    foreach ($items as $entryid) {
846
+                        // Open occurrence
847
+                        $occurrenceItem = mapi_msgstore_openentry($store, $entryid);
848
+
849
+                        // Save occurrence into main recurring item as exception
850
+                        if ($occurrenceItem) {
851
+                            $occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
852
+
853
+                            // Find basedate of occurrence item
854
+                            $basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
855
+                            if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
856
+                                $this->mergeException($calendarItem, $occurrenceItem, $basedate, $store);
857
+                            }
858
+                        }
859
+                    }
860
+                }
861
+
862
+                mapi_savechanges($calendarItem);
863
+
864
+                // After applying update of organiser all local categories of occurrence was removed,
865
+                // So if local categories exist then apply it on respective occurrence.
866
+                if (!empty($localCategories)) {
867
+                    $this->applyLocalCategories($calendarItem, $store, $localCategories);
868
+                }
869
+
870
+                if ($move) {
871
+                    // open wastebasket of currently logged in user and move the meeting request to it
872
+                    // for delegates this will be delegate's wastebasket folder
873
+                    $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
874
+                    mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
875
+                }
876
+
877
+                $entryid = $props[PR_ENTRYID];
878
+            }
879
+            else {
880
+                /**
881
+                 * This meeting request is not recurring, so can be an exception or normal meeting.
882
+                 * If exception then find main recurring item and update exception
883
+                 * If main recurring item is not found then put exception into Calendar as normal meeting.
884
+                 */
885
+                $calendarItem = false;
886
+
887
+                // We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
888
+                if ($basedate) {
889
+                    // Find main recurring item from CleanGlobalID of this meeting request
890
+                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
891
+                    if (is_array($items)) {
892
+                        foreach ($items as $key => $entryid) {
893
+                            $calendarItem = mapi_msgstore_openentry($store, $entryid);
894
+                        }
895
+                    }
896
+
897
+                    // Main recurring item is found, so now update exception
898
+                    if ($calendarItem) {
899
+                        $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
900
+                        $calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
901
+                        $entryid = $calendarItemProps[PR_ENTRYID];
902
+                    }
903
+                }
904
+
905
+                if (!$calendarItem) {
906
+                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
907
+                    if (is_array($items)) {
908
+                        // Get local categories before deleting MR.
909
+                        $message = mapi_msgstore_openentry($store, $items[0]);
910
+                        $localCategories = mapi_getprops($message, [$this->proptags['categories']]);
911
+                        mapi_folder_deletemessages($calFolder, $items);
912
+                    }
913
+
914
+                    if ($move) {
915
+                        // All we have to do is open the default calendar,
916
+                        // set the message class correctly to be an appointment item
917
+                        // and move it to the calendar folder
918
+                        $sourcefolder = $this->openParentFolder();
919
+
920
+                        // create a new calendar message, and copy the message to there,
921
+                        // since we want to delete (move to wastebasket) the original message
922
+                        $old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
923
+                        $calmsg = mapi_folder_createmessage($calFolder);
924
+                        mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
925
+
926
+                        // After creating new MR, If local categories exist then apply it on new MR.
927
+                        if (!empty($localCategories)) {
928
+                            mapi_setprops($calmsg, $localCategories);
929
+                        }
930
+
931
+                        $calItemProps = [];
932
+                        $calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment';
933
+
934
+                        /*
935 935
 						 * the client which has sent this meeting request can generate wrong flagdueby
936 936
 						 * time (mainly OL), so regenerate that property so we will always show reminder
937 937
 						 * on right time
938 938
 						 */
939
-						if (isset($messageprops[$this->proptags['reminderminutes']])) {
940
-							$calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60);
941
-						}
942
-
943
-						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
944
-							if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
945
-								$calItemProps[$this->proptags['busystatus']] = fbTentative;
946
-							}
947
-							else {
948
-								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
949
-							}
950
-							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
951
-						}
952
-						else {
953
-							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
954
-						}
955
-
956
-						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
957
-						$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
958
-						if ($userAction) {
959
-							$addrInfo = $this->getOwnerAddress($this->store);
960
-
961
-							// if user has responded then set replytime and name
962
-							$calItemProps[$this->proptags['replytime']] = time();
963
-							if (!empty($addrInfo)) {
964
-								$calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0];
965
-							}
966
-						}
967
-
968
-						mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
969
-
970
-						// get properties which stores owner information in meeting request mails
971
-						$props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
972
-
973
-						// add owner to recipient table
974
-						$recips = [];
975
-						$this->addOrganizer($props, $recips);
976
-						mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
977
-						mapi_savechanges($calmsg);
978
-
979
-						// Move the message to the wastebasket
980
-						$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
981
-						mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
982
-
983
-						$messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
984
-						$entryid = $messageprops[PR_ENTRYID];
985
-					}
986
-					else {
987
-						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
988
-						$new = mapi_folder_createmessage($calFolder);
989
-						$props = mapi_getprops($this->message);
990
-
991
-						$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
992
-
993
-						// After creating new MR, If local categories exist then apply it on new MR.
994
-						if (!empty($localCategories)) {
995
-							mapi_setprops($new, $localCategories);
996
-						}
997
-
998
-						/*
939
+                        if (isset($messageprops[$this->proptags['reminderminutes']])) {
940
+                            $calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60);
941
+                        }
942
+
943
+                        if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
944
+                            if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
945
+                                $calItemProps[$this->proptags['busystatus']] = fbTentative;
946
+                            }
947
+                            else {
948
+                                $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
949
+                            }
950
+                            $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
951
+                        }
952
+                        else {
953
+                            $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
954
+                        }
955
+
956
+                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
957
+                        $calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
958
+                        if ($userAction) {
959
+                            $addrInfo = $this->getOwnerAddress($this->store);
960
+
961
+                            // if user has responded then set replytime and name
962
+                            $calItemProps[$this->proptags['replytime']] = time();
963
+                            if (!empty($addrInfo)) {
964
+                                $calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0];
965
+                            }
966
+                        }
967
+
968
+                        mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
969
+
970
+                        // get properties which stores owner information in meeting request mails
971
+                        $props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
972
+
973
+                        // add owner to recipient table
974
+                        $recips = [];
975
+                        $this->addOrganizer($props, $recips);
976
+                        mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
977
+                        mapi_savechanges($calmsg);
978
+
979
+                        // Move the message to the wastebasket
980
+                        $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
981
+                        mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
982
+
983
+                        $messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
984
+                        $entryid = $messageprops[PR_ENTRYID];
985
+                    }
986
+                    else {
987
+                        // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
988
+                        $new = mapi_folder_createmessage($calFolder);
989
+                        $props = mapi_getprops($this->message);
990
+
991
+                        $props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
992
+
993
+                        // After creating new MR, If local categories exist then apply it on new MR.
994
+                        if (!empty($localCategories)) {
995
+                            mapi_setprops($new, $localCategories);
996
+                        }
997
+
998
+                        /*
999 999
 						 * the client which has sent this meeting request can generate wrong flagdueby
1000 1000
 						 * time (mainly OL), so regenerate that property so we will always show reminder
1001 1001
 						 * on right time
1002 1002
 						 */
1003
-						if (isset($props[$this->proptags['reminderminutes']])) {
1004
-							$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
1005
-						}
1006
-
1007
-						// When meeting requests are generated by third-party solutions, we might be missing the properties.
1008
-						if (!isset($props[$this->proptags['updatecounter']])) {
1009
-							$props[$this->proptags['updatecounter']] = 0;
1010
-						}
1011
-						if (!isset($props[$this->proptags['recurring']])) {
1012
-							$props[$this->proptags['recurring']] = false;
1013
-						}
1014
-
1015
-						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
1016
-						$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
1017
-
1018
-						if (isset($props[$this->proptags['intendedbusystatus']])) {
1019
-							if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
1020
-								$props[$this->proptags['busystatus']] = fbTentative;
1021
-							}
1022
-							else {
1023
-								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
1024
-							}
1025
-							// we already have intendedbusystatus value in $props so no need to copy it
1026
-						}
1027
-						else {
1028
-							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1029
-						}
1030
-
1031
-						if ($userAction) {
1032
-							$addrInfo = $this->getOwnerAddress($this->store);
1033
-
1034
-							// if user has responded then set replytime and name
1035
-							$props[$this->proptags['replytime']] = time();
1036
-							if (!empty($addrInfo)) {
1037
-								$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1038
-							}
1039
-						}
1040
-
1041
-						mapi_setprops($new, $proposeNewTimeProps + $props);
1042
-
1043
-						// Copy attachments too
1044
-						$this->replaceAttachments($this->message, $new);
1045
-
1046
-						// get recipient table of source message
1047
-						$recipientTable = mapi_message_getrecipienttable($this->message);
1048
-
1049
-						// If delegate, then do not add the delegate in recipients
1050
-						if ($isDelegate) {
1051
-							$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
1052
-							$res = [RES_PROPERTY, [
1053
-								RELOP => RELOP_NE,
1054
-								ULPROPTAG => PR_EMAIL_ADDRESS,
1055
-								VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
1056
-							],
1057
-							];
1058
-							$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
1059
-						}
1060
-						else {
1061
-							$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
1062
-						}
1063
-						$this->addOrganizer($props, $recipients);
1064
-						mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
1065
-						mapi_savechanges($new);
1066
-
1067
-						$props = mapi_getprops($new, [PR_ENTRYID]);
1068
-						$entryid = $props[PR_ENTRYID];
1069
-					}
1070
-				}
1071
-			}
1072
-		}
1073
-		else {
1074
-			// Here only properties are set on calendaritem, because user is responding from calendar.
1075
-			$props = [];
1076
-			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1077
-
1078
-			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1079
-				if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1080
-					$props[$this->proptags['busystatus']] = fbTentative;
1081
-				}
1082
-				else {
1083
-					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1084
-				}
1085
-				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1086
-			}
1087
-			else {
1088
-				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1089
-			}
1090
-
1091
-			$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1092
-
1093
-			$addrInfo = $this->getOwnerAddress($this->store);
1094
-
1095
-			// if user has responded then set replytime and name
1096
-			$props[$this->proptags['replytime']] = time();
1097
-			if (!empty($addrInfo)) {
1098
-				$props[$this->proptags['apptreplyname']] = $addrInfo[0];
1099
-			}
1100
-
1101
-			if ($basedate) {
1102
-				$recurr = new Recurrence($store, $this->message);
1103
-
1104
-				// Copy recipients list
1105
-				$reciptable = mapi_message_getrecipienttable($this->message);
1106
-				$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1107
-
1108
-				if ($recurr->isException($basedate)) {
1109
-					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1110
-				}
1111
-				else {
1112
-					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1113
-					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1114
-
1115
-					$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1116
-					$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1117
-					$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1118
-					$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1119
-					$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1120
-
1121
-					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1122
-				}
1123
-			}
1124
-			else {
1125
-				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1126
-			}
1127
-			mapi_savechanges($this->message);
1128
-
1129
-			$entryid = $messageprops[PR_ENTRYID];
1130
-		}
1131
-
1132
-		return $entryid;
1133
-	}
1134
-
1135
-	/**
1136
-	 * Declines the meeting request by moving the item to the deleted
1137
-	 * items folder and sending a decline message. After declining, you
1138
-	 * can't use this class instance any more. The message is closed.
1139
-	 * When an occurrence is decline then false is returned because that
1140
-	 * occurrence is deleted not the recurring item.
1141
-	 *
1142
-	 * @param bool   $sendresponse true if a response has to be sent to organizer
1143
-	 * @param string $basedate     if specified contains starttime of day of an occurrence
1144
-	 * @param mixed  $body
1145
-	 *
1146
-	 * @return bool true if item is deleted from Calendar else false
1147
-	 */
1148
-	public function doDecline($sendresponse, $basedate = false, $body = false) {
1149
-		if ($this->isLocalOrganiser()) {
1150
-			return false;
1151
-		}
1152
-
1153
-		$result = false;
1154
-		$calendaritem = false;
1155
-
1156
-		// Remove any previous calendar items with this goid and appt id
1157
-		$messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
1158
-
1159
-		// If this meeting request is received by a delegate then open delegator's store.
1160
-		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1161
-			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1162
-
1163
-			$store = $delegatorStore['store'];
1164
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1165
-		}
1166
-		else {
1167
-			$calFolder = $this->openDefaultCalendar();
1168
-			$store = $this->store;
1169
-		}
1170
-
1171
-		// check for calendar access before deleting the calendar item
1172
-		if ($this->checkCalendarWriteAccess($store) !== true) {
1173
-			// Throw an exception that we don't have write permissions on calendar folder,
1174
-			// allow caller to fill the error message
1175
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1176
-		}
1177
-
1178
-		$goid = $messageprops[$this->proptags['goid']];
1179
-
1180
-		// First, find the items in the calendar by GlobalObjid (0x3)
1181
-		$entryids = $this->findCalendarItems($goid, $calFolder);
1182
-
1183
-		if (!$basedate) {
1184
-			$basedate = $this->getBasedateFromGlobalID($goid);
1185
-		}
1186
-
1187
-		if ($sendresponse) {
1188
-			$this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder);
1189
-		}
1190
-
1191
-		if ($basedate) {
1192
-			// use CleanGlobalObjid (0x23)
1193
-			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1194
-
1195
-			if (is_array($calendaritems)) {
1196
-				foreach ($calendaritems as $entryid) {
1197
-					// Open each calendar item and set the properties of the cancellation object
1198
-					$calendaritem = mapi_msgstore_openentry($store, $entryid);
1199
-
1200
-					// Recurring item is found, now delete exception
1201
-					if ($calendaritem) {
1202
-						$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1203
-						$result = true;
1204
-					}
1205
-				}
1206
-			}
1207
-
1208
-			if ($this->isMeetingRequest()) {
1209
-				$calendaritem = false;
1210
-			}
1211
-		}
1212
-
1213
-		if (!$calendaritem) {
1214
-			$calendar = $this->openDefaultCalendar($store);
1215
-
1216
-			if (!empty($entryids)) {
1217
-				mapi_folder_deletemessages($calendar, $entryids);
1218
-			}
1219
-
1220
-			// All we have to do to decline, is to move the item to the waste basket
1221
-			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1222
-			$sourcefolder = $this->openParentFolder();
1223
-
1224
-			$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1225
-
1226
-			// Release the message
1227
-			$this->message = null;
1228
-
1229
-			// Move the message to the waste basket
1230
-			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1231
-
1232
-			$result = true;
1233
-		}
1234
-
1235
-		return $result;
1236
-	}
1237
-
1238
-	/**
1239
-	 * Removes a meeting request from the calendar when the user presses the
1240
-	 * 'remove from calendar' button in response to a meeting cancellation.
1241
-	 *
1242
-	 * @param string $basedate if specified contains starttime of day of an occurrence
1243
-	 */
1244
-	public function doRemoveFromCalendar($basedate) {
1245
-		if ($this->isLocalOrganiser()) {
1246
-			return false;
1247
-		}
1248
-
1249
-		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
1250
-
1251
-		$goid = $messageprops[$this->proptags['goid']];
1252
-
1253
-		if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1254
-			$delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1255
-
1256
-			$store = $delegatorStore['store'];
1257
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1258
-		}
1259
-		else {
1260
-			$store = $this->store;
1261
-			$calFolder = $this->openDefaultCalendar();
1262
-		}
1263
-
1264
-		// check for calendar access before deleting the calendar item
1265
-		if ($this->checkCalendarWriteAccess($store) !== true) {
1266
-			// Throw an exception that we don't have write permissions on calendar folder,
1267
-			// allow caller to fill the error message
1268
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1269
-		}
1270
-
1271
-		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1272
-		// get the source folder of the meeting message
1273
-		$sourcefolder = $this->openParentFolder();
1274
-
1275
-		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1276
-		if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1277
-			// get the basedate to check for exception
1278
-			$basedate = $this->getBasedateFromGlobalID($goid);
1279
-
1280
-			$calendarItem = $this->getCorrespondentCalendarItem(true);
1281
-
1282
-			if ($calendarItem !== false) {
1283
-				// basedate is provided so open exception
1284
-				if ($basedate) {
1285
-					$exception = $this->getExceptionItem($calendarItem, $basedate);
1286
-
1287
-					if ($exception !== false) {
1288
-						// exception found, remove it from calendar
1289
-						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
1290
-					}
1291
-				}
1292
-				else {
1293
-					// remove normal / recurring series from calendar
1294
-					$entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1295
-
1296
-					$entryids = [$entryids[PR_ENTRYID]];
1297
-
1298
-					mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
1299
-				}
1300
-			}
1301
-
1302
-			// Release the message, because we are going to move it to wastebasket
1303
-			$this->message = null;
1304
-
1305
-			// Move the cancellation mail to wastebasket
1306
-			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1307
-		}
1308
-		else {
1309
-			// Here only properties are set on calendaritem, because user is responding from calendar.
1310
-			if ($basedate) {
1311
-				// remove the occurrence
1312
-				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1313
-			}
1314
-			else {
1315
-				// remove normal/recurring meeting item.
1316
-				// Move the message to the waste basket
1317
-				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1318
-			}
1319
-		}
1320
-	}
1321
-
1322
-	/**
1323
-	 * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1324
-	 * Should only be called from meeting object from calendar.
1325
-	 *
1326
-	 * @param string $basedate (optional) basedate of occurrence which should be cancelled
1327
-	 * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1328
-	 * @FIXME don't send canellation mail when cancelling meeting from past
1329
-	 */
1330
-	public function doCancelInvitation($basedate = false) {
1331
-		if (!$this->isLocalOrganiser()) {
1332
-			return;
1333
-		}
1334
-
1335
-		// check write access for delegate
1336
-		if ($this->checkCalendarWriteAccess($this->store) !== true) {
1337
-			// Throw an exception that we don't have write permissions on calendar folder,
1338
-			// error message will be filled by module
1339
-			throw new MAPIException(null, MAPI_E_NO_ACCESS);
1340
-		}
1341
-
1342
-		$messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
1343
-
1344
-		if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1345
-			// cancellation of recurring series or one occurrence
1346
-			$recurrence = new Recurrence($this->store, $this->message);
1347
-
1348
-			// if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1349
-			if ($basedate) {
1350
-				$recurrence->createException([], $basedate, true);
1351
-			}
1352
-
1353
-			// update the meeting request
1354
-			$this->updateMeetingRequest();
1355
-
1356
-			// send cancellation mails
1357
-			$this->sendMeetingRequest(true, _('Canceled: '), $basedate);
1358
-
1359
-			// save changes in the message
1360
-			mapi_savechanges($this->message);
1361
-		}
1362
-		else {
1363
-			// cancellation of normal meeting request
1364
-			// Send the cancellation
1365
-			$this->updateMeetingRequest();
1366
-			$this->sendMeetingRequest(true, _('Canceled: '));
1367
-
1368
-			// save changes in the message
1369
-			mapi_savechanges($this->message);
1370
-		}
1371
-
1372
-		// if basedate is specified then we have already created exception of it so nothing should be done now
1373
-		// but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1374
-		if ($basedate === false) {
1375
-			// get the wastebasket folder, for delegate this will give wastebasket of delegate
1376
-			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1377
-
1378
-			// get the source folder of the meeting message
1379
-			$sourcefolder = $this->openParentFolder();
1380
-
1381
-			// Move the message to the deleted items
1382
-			mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1383
-		}
1384
-	}
1385
-
1386
-	/**
1387
-	 * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1388
-	 * the start of January 1, 1601.
1389
-	 * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx.
1390
-	 *
1391
-	 * @param int the current epoch
1392
-	 * @param mixed $epoch
1393
-	 *
1394
-	 * @return the MAPI FileTime equalevent to the given epoch time
1395
-	 */
1396
-	public function epochToMapiFileTime($epoch) {
1397
-		$nanoseconds_between_epoch = 116444736000000000;
1398
-
1399
-		return ($epoch * 10000000) + $nanoseconds_between_epoch;
1400
-	}
1401
-
1402
-	/**
1403
-	 * Sets the properties in the message so that is can be sent
1404
-	 * as a meeting request. The caller has to submit the message. This
1405
-	 * is only used for new MeetingRequests. Pass the appointment item as $message
1406
-	 * in the constructor to do this.
1407
-	 *
1408
-	 * @param mixed $basedate
1409
-	 */
1410
-	public function setMeetingRequest($basedate = false) {
1411
-		$props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
1412
-
1413
-		// Create a new global id for this item
1414
-		// https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx
1415
-		$goid = pack('H*', '040000008200E00074C5B7101A82E00800000000');
1416
-		// Creation Time
1417
-		$time = $this->epochToMapiFileTime(time());
1418
-		$highdatetime = $time >> 32;
1419
-		$lowdatetime = $time & 0xFFFFFFFF;
1420
-		$goid .= pack('II', $lowdatetime, $highdatetime);
1421
-		// 8 Zeros
1422
-		$goid .= pack('P', 0);
1423
-		// Length of the random data
1424
-		$goid .= pack('V', 16);
1425
-		// Random data.
1426
-		for ($i = 0; $i < 16; ++$i) {
1427
-			$goid .= chr(rand(0, 255));
1428
-		}
1429
-
1430
-		// Create a new appointment id for this item
1431
-		$apptid = rand();
1432
-
1433
-		$props[PR_OWNER_APPT_ID] = $apptid;
1434
-		$props[PR_ICON_INDEX] = 1026;
1435
-		$props[$this->proptags['goid']] = $goid;
1436
-		$props[$this->proptags['goid2']] = $goid;
1437
-
1438
-		if (!isset($props[$this->proptags['updatecounter']])) {
1439
-			$props[$this->proptags['updatecounter']] = 0;            // OL also starts sequence no with zero.
1440
-			$props[$this->proptags['last_updatecounter']] = 0;
1441
-		}
1442
-
1443
-		mapi_setprops($this->message, $props);
1444
-	}
1445
-
1446
-	/**
1447
-	 * Sends a meeting request by copying it to the outbox, converting
1448
-	 * the message class, adding some properties that are required only
1449
-	 * for sending the message and submitting the message. Set cancel to
1450
-	 * true if you wish to completely cancel the meeting request. You can
1451
-	 * specify an optional 'prefix' to prefix the sent message, which is normally
1452
-	 * 'Canceled: '.
1453
-	 *
1454
-	 * @param mixed $cancel
1455
-	 * @param mixed $prefix
1456
-	 * @param mixed $basedate
1457
-	 * @param mixed $modifiedRecips
1458
-	 * @param mixed $deletedRecips
1459
-	 */
1460
-	public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) {
1461
-		$this->includesResources = false;
1462
-		$this->nonAcceptingResources = [];
1463
-
1464
-		// Get the properties of the message
1465
-		$messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
1466
-
1467
-		/*
1003
+                        if (isset($props[$this->proptags['reminderminutes']])) {
1004
+                            $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
1005
+                        }
1006
+
1007
+                        // When meeting requests are generated by third-party solutions, we might be missing the properties.
1008
+                        if (!isset($props[$this->proptags['updatecounter']])) {
1009
+                            $props[$this->proptags['updatecounter']] = 0;
1010
+                        }
1011
+                        if (!isset($props[$this->proptags['recurring']])) {
1012
+                            $props[$this->proptags['recurring']] = false;
1013
+                        }
1014
+
1015
+                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
1016
+                        $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
1017
+
1018
+                        if (isset($props[$this->proptags['intendedbusystatus']])) {
1019
+                            if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
1020
+                                $props[$this->proptags['busystatus']] = fbTentative;
1021
+                            }
1022
+                            else {
1023
+                                $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
1024
+                            }
1025
+                            // we already have intendedbusystatus value in $props so no need to copy it
1026
+                        }
1027
+                        else {
1028
+                            $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1029
+                        }
1030
+
1031
+                        if ($userAction) {
1032
+                            $addrInfo = $this->getOwnerAddress($this->store);
1033
+
1034
+                            // if user has responded then set replytime and name
1035
+                            $props[$this->proptags['replytime']] = time();
1036
+                            if (!empty($addrInfo)) {
1037
+                                $props[$this->proptags['apptreplyname']] = $addrInfo[0];
1038
+                            }
1039
+                        }
1040
+
1041
+                        mapi_setprops($new, $proposeNewTimeProps + $props);
1042
+
1043
+                        // Copy attachments too
1044
+                        $this->replaceAttachments($this->message, $new);
1045
+
1046
+                        // get recipient table of source message
1047
+                        $recipientTable = mapi_message_getrecipienttable($this->message);
1048
+
1049
+                        // If delegate, then do not add the delegate in recipients
1050
+                        if ($isDelegate) {
1051
+                            $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
1052
+                            $res = [RES_PROPERTY, [
1053
+                                RELOP => RELOP_NE,
1054
+                                ULPROPTAG => PR_EMAIL_ADDRESS,
1055
+                                VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
1056
+                            ],
1057
+                            ];
1058
+                            $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
1059
+                        }
1060
+                        else {
1061
+                            $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
1062
+                        }
1063
+                        $this->addOrganizer($props, $recipients);
1064
+                        mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
1065
+                        mapi_savechanges($new);
1066
+
1067
+                        $props = mapi_getprops($new, [PR_ENTRYID]);
1068
+                        $entryid = $props[PR_ENTRYID];
1069
+                    }
1070
+                }
1071
+            }
1072
+        }
1073
+        else {
1074
+            // Here only properties are set on calendaritem, because user is responding from calendar.
1075
+            $props = [];
1076
+            $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1077
+
1078
+            if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1079
+                if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1080
+                    $props[$this->proptags['busystatus']] = fbTentative;
1081
+                }
1082
+                else {
1083
+                    $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1084
+                }
1085
+                $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1086
+            }
1087
+            else {
1088
+                $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1089
+            }
1090
+
1091
+            $props[$this->proptags['meetingstatus']] = olMeetingReceived;
1092
+
1093
+            $addrInfo = $this->getOwnerAddress($this->store);
1094
+
1095
+            // if user has responded then set replytime and name
1096
+            $props[$this->proptags['replytime']] = time();
1097
+            if (!empty($addrInfo)) {
1098
+                $props[$this->proptags['apptreplyname']] = $addrInfo[0];
1099
+            }
1100
+
1101
+            if ($basedate) {
1102
+                $recurr = new Recurrence($store, $this->message);
1103
+
1104
+                // Copy recipients list
1105
+                $reciptable = mapi_message_getrecipienttable($this->message);
1106
+                $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1107
+
1108
+                if ($recurr->isException($basedate)) {
1109
+                    $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1110
+                }
1111
+                else {
1112
+                    $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1113
+                    $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1114
+
1115
+                    $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1116
+                    $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1117
+                    $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1118
+                    $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1119
+                    $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1120
+
1121
+                    $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1122
+                }
1123
+            }
1124
+            else {
1125
+                mapi_setprops($this->message, $proposeNewTimeProps + $props);
1126
+            }
1127
+            mapi_savechanges($this->message);
1128
+
1129
+            $entryid = $messageprops[PR_ENTRYID];
1130
+        }
1131
+
1132
+        return $entryid;
1133
+    }
1134
+
1135
+    /**
1136
+     * Declines the meeting request by moving the item to the deleted
1137
+     * items folder and sending a decline message. After declining, you
1138
+     * can't use this class instance any more. The message is closed.
1139
+     * When an occurrence is decline then false is returned because that
1140
+     * occurrence is deleted not the recurring item.
1141
+     *
1142
+     * @param bool   $sendresponse true if a response has to be sent to organizer
1143
+     * @param string $basedate     if specified contains starttime of day of an occurrence
1144
+     * @param mixed  $body
1145
+     *
1146
+     * @return bool true if item is deleted from Calendar else false
1147
+     */
1148
+    public function doDecline($sendresponse, $basedate = false, $body = false) {
1149
+        if ($this->isLocalOrganiser()) {
1150
+            return false;
1151
+        }
1152
+
1153
+        $result = false;
1154
+        $calendaritem = false;
1155
+
1156
+        // Remove any previous calendar items with this goid and appt id
1157
+        $messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
1158
+
1159
+        // If this meeting request is received by a delegate then open delegator's store.
1160
+        if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1161
+            $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1162
+
1163
+            $store = $delegatorStore['store'];
1164
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1165
+        }
1166
+        else {
1167
+            $calFolder = $this->openDefaultCalendar();
1168
+            $store = $this->store;
1169
+        }
1170
+
1171
+        // check for calendar access before deleting the calendar item
1172
+        if ($this->checkCalendarWriteAccess($store) !== true) {
1173
+            // Throw an exception that we don't have write permissions on calendar folder,
1174
+            // allow caller to fill the error message
1175
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
1176
+        }
1177
+
1178
+        $goid = $messageprops[$this->proptags['goid']];
1179
+
1180
+        // First, find the items in the calendar by GlobalObjid (0x3)
1181
+        $entryids = $this->findCalendarItems($goid, $calFolder);
1182
+
1183
+        if (!$basedate) {
1184
+            $basedate = $this->getBasedateFromGlobalID($goid);
1185
+        }
1186
+
1187
+        if ($sendresponse) {
1188
+            $this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder);
1189
+        }
1190
+
1191
+        if ($basedate) {
1192
+            // use CleanGlobalObjid (0x23)
1193
+            $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1194
+
1195
+            if (is_array($calendaritems)) {
1196
+                foreach ($calendaritems as $entryid) {
1197
+                    // Open each calendar item and set the properties of the cancellation object
1198
+                    $calendaritem = mapi_msgstore_openentry($store, $entryid);
1199
+
1200
+                    // Recurring item is found, now delete exception
1201
+                    if ($calendaritem) {
1202
+                        $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1203
+                        $result = true;
1204
+                    }
1205
+                }
1206
+            }
1207
+
1208
+            if ($this->isMeetingRequest()) {
1209
+                $calendaritem = false;
1210
+            }
1211
+        }
1212
+
1213
+        if (!$calendaritem) {
1214
+            $calendar = $this->openDefaultCalendar($store);
1215
+
1216
+            if (!empty($entryids)) {
1217
+                mapi_folder_deletemessages($calendar, $entryids);
1218
+            }
1219
+
1220
+            // All we have to do to decline, is to move the item to the waste basket
1221
+            $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1222
+            $sourcefolder = $this->openParentFolder();
1223
+
1224
+            $messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1225
+
1226
+            // Release the message
1227
+            $this->message = null;
1228
+
1229
+            // Move the message to the waste basket
1230
+            mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1231
+
1232
+            $result = true;
1233
+        }
1234
+
1235
+        return $result;
1236
+    }
1237
+
1238
+    /**
1239
+     * Removes a meeting request from the calendar when the user presses the
1240
+     * 'remove from calendar' button in response to a meeting cancellation.
1241
+     *
1242
+     * @param string $basedate if specified contains starttime of day of an occurrence
1243
+     */
1244
+    public function doRemoveFromCalendar($basedate) {
1245
+        if ($this->isLocalOrganiser()) {
1246
+            return false;
1247
+        }
1248
+
1249
+        $messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]);
1250
+
1251
+        $goid = $messageprops[$this->proptags['goid']];
1252
+
1253
+        if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) {
1254
+            $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
1255
+
1256
+            $store = $delegatorStore['store'];
1257
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1258
+        }
1259
+        else {
1260
+            $store = $this->store;
1261
+            $calFolder = $this->openDefaultCalendar();
1262
+        }
1263
+
1264
+        // check for calendar access before deleting the calendar item
1265
+        if ($this->checkCalendarWriteAccess($store) !== true) {
1266
+            // Throw an exception that we don't have write permissions on calendar folder,
1267
+            // allow caller to fill the error message
1268
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
1269
+        }
1270
+
1271
+        $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1272
+        // get the source folder of the meeting message
1273
+        $sourcefolder = $this->openParentFolder();
1274
+
1275
+        // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1276
+        if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) {
1277
+            // get the basedate to check for exception
1278
+            $basedate = $this->getBasedateFromGlobalID($goid);
1279
+
1280
+            $calendarItem = $this->getCorrespondentCalendarItem(true);
1281
+
1282
+            if ($calendarItem !== false) {
1283
+                // basedate is provided so open exception
1284
+                if ($basedate) {
1285
+                    $exception = $this->getExceptionItem($calendarItem, $basedate);
1286
+
1287
+                    if ($exception !== false) {
1288
+                        // exception found, remove it from calendar
1289
+                        $this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
1290
+                    }
1291
+                }
1292
+                else {
1293
+                    // remove normal / recurring series from calendar
1294
+                    $entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1295
+
1296
+                    $entryids = [$entryids[PR_ENTRYID]];
1297
+
1298
+                    mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE);
1299
+                }
1300
+            }
1301
+
1302
+            // Release the message, because we are going to move it to wastebasket
1303
+            $this->message = null;
1304
+
1305
+            // Move the cancellation mail to wastebasket
1306
+            mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1307
+        }
1308
+        else {
1309
+            // Here only properties are set on calendaritem, because user is responding from calendar.
1310
+            if ($basedate) {
1311
+                // remove the occurrence
1312
+                $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1313
+            }
1314
+            else {
1315
+                // remove normal/recurring meeting item.
1316
+                // Move the message to the waste basket
1317
+                mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1318
+            }
1319
+        }
1320
+    }
1321
+
1322
+    /**
1323
+     * Function can be used to cancel any existing meeting and send cancellation mails to attendees.
1324
+     * Should only be called from meeting object from calendar.
1325
+     *
1326
+     * @param string $basedate (optional) basedate of occurrence which should be cancelled
1327
+     * @FIXME cancellation mail is also sent to attendee which has declined the meeting
1328
+     * @FIXME don't send canellation mail when cancelling meeting from past
1329
+     */
1330
+    public function doCancelInvitation($basedate = false) {
1331
+        if (!$this->isLocalOrganiser()) {
1332
+            return;
1333
+        }
1334
+
1335
+        // check write access for delegate
1336
+        if ($this->checkCalendarWriteAccess($this->store) !== true) {
1337
+            // Throw an exception that we don't have write permissions on calendar folder,
1338
+            // error message will be filled by module
1339
+            throw new MAPIException(null, MAPI_E_NO_ACCESS);
1340
+        }
1341
+
1342
+        $messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]);
1343
+
1344
+        if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
1345
+            // cancellation of recurring series or one occurrence
1346
+            $recurrence = new Recurrence($this->store, $this->message);
1347
+
1348
+            // if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence
1349
+            if ($basedate) {
1350
+                $recurrence->createException([], $basedate, true);
1351
+            }
1352
+
1353
+            // update the meeting request
1354
+            $this->updateMeetingRequest();
1355
+
1356
+            // send cancellation mails
1357
+            $this->sendMeetingRequest(true, _('Canceled: '), $basedate);
1358
+
1359
+            // save changes in the message
1360
+            mapi_savechanges($this->message);
1361
+        }
1362
+        else {
1363
+            // cancellation of normal meeting request
1364
+            // Send the cancellation
1365
+            $this->updateMeetingRequest();
1366
+            $this->sendMeetingRequest(true, _('Canceled: '));
1367
+
1368
+            // save changes in the message
1369
+            mapi_savechanges($this->message);
1370
+        }
1371
+
1372
+        // if basedate is specified then we have already created exception of it so nothing should be done now
1373
+        // but when cancelling normal / recurring meeting request we need to remove meeting from calendar
1374
+        if ($basedate === false) {
1375
+            // get the wastebasket folder, for delegate this will give wastebasket of delegate
1376
+            $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
1377
+
1378
+            // get the source folder of the meeting message
1379
+            $sourcefolder = $this->openParentFolder();
1380
+
1381
+            // Move the message to the deleted items
1382
+            mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1383
+        }
1384
+    }
1385
+
1386
+    /**
1387
+     * Convert epoch to MAPI FileTime, number of 100-nanosecond units since
1388
+     * the start of January 1, 1601.
1389
+     * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx.
1390
+     *
1391
+     * @param int the current epoch
1392
+     * @param mixed $epoch
1393
+     *
1394
+     * @return the MAPI FileTime equalevent to the given epoch time
1395
+     */
1396
+    public function epochToMapiFileTime($epoch) {
1397
+        $nanoseconds_between_epoch = 116444736000000000;
1398
+
1399
+        return ($epoch * 10000000) + $nanoseconds_between_epoch;
1400
+    }
1401
+
1402
+    /**
1403
+     * Sets the properties in the message so that is can be sent
1404
+     * as a meeting request. The caller has to submit the message. This
1405
+     * is only used for new MeetingRequests. Pass the appointment item as $message
1406
+     * in the constructor to do this.
1407
+     *
1408
+     * @param mixed $basedate
1409
+     */
1410
+    public function setMeetingRequest($basedate = false) {
1411
+        $props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
1412
+
1413
+        // Create a new global id for this item
1414
+        // https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx
1415
+        $goid = pack('H*', '040000008200E00074C5B7101A82E00800000000');
1416
+        // Creation Time
1417
+        $time = $this->epochToMapiFileTime(time());
1418
+        $highdatetime = $time >> 32;
1419
+        $lowdatetime = $time & 0xFFFFFFFF;
1420
+        $goid .= pack('II', $lowdatetime, $highdatetime);
1421
+        // 8 Zeros
1422
+        $goid .= pack('P', 0);
1423
+        // Length of the random data
1424
+        $goid .= pack('V', 16);
1425
+        // Random data.
1426
+        for ($i = 0; $i < 16; ++$i) {
1427
+            $goid .= chr(rand(0, 255));
1428
+        }
1429
+
1430
+        // Create a new appointment id for this item
1431
+        $apptid = rand();
1432
+
1433
+        $props[PR_OWNER_APPT_ID] = $apptid;
1434
+        $props[PR_ICON_INDEX] = 1026;
1435
+        $props[$this->proptags['goid']] = $goid;
1436
+        $props[$this->proptags['goid2']] = $goid;
1437
+
1438
+        if (!isset($props[$this->proptags['updatecounter']])) {
1439
+            $props[$this->proptags['updatecounter']] = 0;            // OL also starts sequence no with zero.
1440
+            $props[$this->proptags['last_updatecounter']] = 0;
1441
+        }
1442
+
1443
+        mapi_setprops($this->message, $props);
1444
+    }
1445
+
1446
+    /**
1447
+     * Sends a meeting request by copying it to the outbox, converting
1448
+     * the message class, adding some properties that are required only
1449
+     * for sending the message and submitting the message. Set cancel to
1450
+     * true if you wish to completely cancel the meeting request. You can
1451
+     * specify an optional 'prefix' to prefix the sent message, which is normally
1452
+     * 'Canceled: '.
1453
+     *
1454
+     * @param mixed $cancel
1455
+     * @param mixed $prefix
1456
+     * @param mixed $basedate
1457
+     * @param mixed $modifiedRecips
1458
+     * @param mixed $deletedRecips
1459
+     */
1460
+    public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) {
1461
+        $this->includesResources = false;
1462
+        $this->nonAcceptingResources = [];
1463
+
1464
+        // Get the properties of the message
1465
+        $messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
1466
+
1467
+        /*
1468 1468
 		 * Submit message to non-resource recipients
1469 1469
 		 */
1470
-		// Set BusyStatus to olTentative (1)
1471
-		// Set MeetingStatus to olMeetingReceived
1472
-		// Set ResponseStatus to olResponseNotResponded
1470
+        // Set BusyStatus to olTentative (1)
1471
+        // Set MeetingStatus to olMeetingReceived
1472
+        // Set ResponseStatus to olResponseNotResponded
1473 1473
 
1474
-		/*
1474
+        /*
1475 1475
 		 * While sending recurrence meeting exceptions are not send as attachments
1476 1476
 		 * because first all exceptions are send and then recurrence meeting is sent.
1477 1477
 		 */
1478
-		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1479
-			// Book resource
1480
-			$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1481
-
1482
-			if (!$this->errorSetResource) {
1483
-				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1484
-
1485
-				// First send meetingrequest for recurring item
1486
-				$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
1487
-
1488
-				// Then send all meeting request for all exceptions
1489
-				$exceptions = $recurr->getAllExceptions();
1490
-				if ($exceptions) {
1491
-					foreach ($exceptions as $exceptionBasedate) {
1492
-						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1493
-
1494
-						if ($attach) {
1495
-							$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1496
-							$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
1497
-							mapi_savechanges($attach);
1498
-						}
1499
-					}
1500
-				}
1501
-			}
1502
-		}
1503
-		else {
1504
-			// Basedate found, an exception is to be send
1505
-			if ($basedate) {
1506
-				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1507
-
1508
-				if ($cancel) {
1509
-					// @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1510
-					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1511
-				}
1512
-				else {
1513
-					$attach = $recurr->getExceptionAttachment($basedate);
1514
-
1515
-					if ($attach) {
1516
-						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1517
-
1518
-						// Book resource for this occurrence
1519
-						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1520
-
1521
-						if (!$this->errorSetResource) {
1522
-							// Save all previous changes
1523
-							mapi_savechanges($this->message);
1524
-
1525
-							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1526
-							mapi_savechanges($occurrenceItem);
1527
-							mapi_savechanges($attach);
1528
-						}
1529
-					}
1530
-				}
1531
-			}
1532
-			else {
1533
-				// This is normal meeting
1534
-				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1535
-
1536
-				if (!$this->errorSetResource) {
1537
-					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
1538
-				}
1539
-			}
1540
-		}
1541
-
1542
-		if (isset($this->errorSetResource) && $this->errorSetResource) {
1543
-			return [
1544
-				'error' => $this->errorSetResource,
1545
-				'displayname' => $this->recipientDisplayname,
1546
-			];
1547
-		}
1548
-
1549
-		return true;
1550
-	}
1551
-
1552
-	/**
1553
-	 * This function will get freebusy data for user based on the timeframe passed in arguments.
1554
-	 *
1555
-	 * @param {HexString} $entryID Entryid of the user for which we need to get freebusy data
1556
-	 * @param {Number} $start start offset for freebusy publish range
1557
-	 * @param {Number} $end end offset for freebusy publish range
1558
-	 *
1559
-	 * @return {Array} freebusy blocks for passed publish range
1560
-	 */
1561
-	public function getFreeBusyInfo($entryID, $start, $end) {
1562
-		$result = [];
1563
-		$fbsupport = mapi_freebusysupport_open($this->session);
1564
-
1565
-		$fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]);
1566
-
1567
-		if ($fbDataArray[0] != null) {
1568
-			foreach ($fbDataArray as $fbDataUser) {
1569
-				$rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1570
-				if ($rangeuser1 == null) {
1571
-					return $result;
1572
-				}
1573
-
1574
-				$enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1575
-				mapi_freebusyenumblock_reset($enumblock);
1576
-
1577
-				while (true) {
1578
-					$blocks = mapi_freebusyenumblock_next($enumblock, 100);
1579
-					if (!$blocks) {
1580
-						break;
1581
-					}
1582
-
1583
-					foreach ($blocks as $blockItem) {
1584
-						$result[] = $blockItem;
1585
-					}
1586
-				}
1587
-			}
1588
-		}
1589
-
1590
-		mapi_freebusysupport_close($fbsupport);
1591
-
1592
-		return $result;
1593
-	}
1594
-
1595
-	/**
1596
-	 * Updates the message after an update has been performed (for example,
1597
-	 * changing the time of the meeting). This must be called before re-sending
1598
-	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1599
-	 * as it will automatically call setMeetingRequest on this object if it is the first
1600
-	 * call to this function.
1601
-	 *
1602
-	 * @param mixed $basedate
1603
-	 */
1604
-	public function updateMeetingRequest($basedate = false) {
1605
-		$messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1606
-
1607
-		if (!isset($messageprops[$this->proptags['goid']])) {
1608
-			$this->setMeetingRequest($basedate);
1609
-		}
1610
-		else {
1611
-			$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1612
-
1613
-			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1614
-			// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1615
-			// this way we can make sure that every time we will be using a uniwue number for every operation
1616
-			mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1617
-		}
1618
-	}
1619
-
1620
-	/**
1621
-	 * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1622
-	 */
1623
-	public function isLocalOrganiser() {
1624
-		$props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]);
1625
-
1626
-		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1627
-			// we are checking with calendar item
1628
-			$calendarItem = $this->message;
1629
-		}
1630
-		else {
1631
-			// we are checking with meeting request / response / cancellation mail
1632
-			// get calendar items
1633
-			$calendarItem = $this->getCorrespondentCalendarItem(true);
1634
-		}
1635
-
1636
-		// even if we have received request/response for exception/occurrence then also
1637
-		// we can check recurring series for organizer, no need to check with exception/occurrence
1638
-
1639
-		if ($calendarItem !== false) {
1640
-			$messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]);
1641
-
1642
-			if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1643
-				return true;
1644
-			}
1645
-		}
1646
-
1647
-		return false;
1648
-	}
1649
-
1650
-	/*
1478
+        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1479
+            // Book resource
1480
+            $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1481
+
1482
+            if (!$this->errorSetResource) {
1483
+                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1484
+
1485
+                // First send meetingrequest for recurring item
1486
+                $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips);
1487
+
1488
+                // Then send all meeting request for all exceptions
1489
+                $exceptions = $recurr->getAllExceptions();
1490
+                if ($exceptions) {
1491
+                    foreach ($exceptions as $exceptionBasedate) {
1492
+                        $attach = $recurr->getExceptionAttachment($exceptionBasedate);
1493
+
1494
+                        if ($attach) {
1495
+                            $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1496
+                            $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips);
1497
+                            mapi_savechanges($attach);
1498
+                        }
1499
+                    }
1500
+                }
1501
+            }
1502
+        }
1503
+        else {
1504
+            // Basedate found, an exception is to be send
1505
+            if ($basedate) {
1506
+                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1507
+
1508
+                if ($cancel) {
1509
+                    // @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1510
+                    $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1511
+                }
1512
+                else {
1513
+                    $attach = $recurr->getExceptionAttachment($basedate);
1514
+
1515
+                    if ($attach) {
1516
+                        $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1517
+
1518
+                        // Book resource for this occurrence
1519
+                        $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1520
+
1521
+                        if (!$this->errorSetResource) {
1522
+                            // Save all previous changes
1523
+                            mapi_savechanges($this->message);
1524
+
1525
+                            $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips);
1526
+                            mapi_savechanges($occurrenceItem);
1527
+                            mapi_savechanges($attach);
1528
+                        }
1529
+                    }
1530
+                }
1531
+            }
1532
+            else {
1533
+                // This is normal meeting
1534
+                $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1535
+
1536
+                if (!$this->errorSetResource) {
1537
+                    $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips);
1538
+                }
1539
+            }
1540
+        }
1541
+
1542
+        if (isset($this->errorSetResource) && $this->errorSetResource) {
1543
+            return [
1544
+                'error' => $this->errorSetResource,
1545
+                'displayname' => $this->recipientDisplayname,
1546
+            ];
1547
+        }
1548
+
1549
+        return true;
1550
+    }
1551
+
1552
+    /**
1553
+     * This function will get freebusy data for user based on the timeframe passed in arguments.
1554
+     *
1555
+     * @param {HexString} $entryID Entryid of the user for which we need to get freebusy data
1556
+     * @param {Number} $start start offset for freebusy publish range
1557
+     * @param {Number} $end end offset for freebusy publish range
1558
+     *
1559
+     * @return {Array} freebusy blocks for passed publish range
1560
+     */
1561
+    public function getFreeBusyInfo($entryID, $start, $end) {
1562
+        $result = [];
1563
+        $fbsupport = mapi_freebusysupport_open($this->session);
1564
+
1565
+        $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]);
1566
+
1567
+        if ($fbDataArray[0] != null) {
1568
+            foreach ($fbDataArray as $fbDataUser) {
1569
+                $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1570
+                if ($rangeuser1 == null) {
1571
+                    return $result;
1572
+                }
1573
+
1574
+                $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1575
+                mapi_freebusyenumblock_reset($enumblock);
1576
+
1577
+                while (true) {
1578
+                    $blocks = mapi_freebusyenumblock_next($enumblock, 100);
1579
+                    if (!$blocks) {
1580
+                        break;
1581
+                    }
1582
+
1583
+                    foreach ($blocks as $blockItem) {
1584
+                        $result[] = $blockItem;
1585
+                    }
1586
+                }
1587
+            }
1588
+        }
1589
+
1590
+        mapi_freebusysupport_close($fbsupport);
1591
+
1592
+        return $result;
1593
+    }
1594
+
1595
+    /**
1596
+     * Updates the message after an update has been performed (for example,
1597
+     * changing the time of the meeting). This must be called before re-sending
1598
+     * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1599
+     * as it will automatically call setMeetingRequest on this object if it is the first
1600
+     * call to this function.
1601
+     *
1602
+     * @param mixed $basedate
1603
+     */
1604
+    public function updateMeetingRequest($basedate = false) {
1605
+        $messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1606
+
1607
+        if (!isset($messageprops[$this->proptags['goid']])) {
1608
+            $this->setMeetingRequest($basedate);
1609
+        }
1610
+        else {
1611
+            $counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1612
+
1613
+            // increment value of last_updatecounter, last_updatecounter will be common for recurring series
1614
+            // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1615
+            // this way we can make sure that every time we will be using a uniwue number for every operation
1616
+            mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1617
+        }
1618
+    }
1619
+
1620
+    /**
1621
+     * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object.
1622
+     */
1623
+    public function isLocalOrganiser() {
1624
+        $props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]);
1625
+
1626
+        if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1627
+            // we are checking with calendar item
1628
+            $calendarItem = $this->message;
1629
+        }
1630
+        else {
1631
+            // we are checking with meeting request / response / cancellation mail
1632
+            // get calendar items
1633
+            $calendarItem = $this->getCorrespondentCalendarItem(true);
1634
+        }
1635
+
1636
+        // even if we have received request/response for exception/occurrence then also
1637
+        // we can check recurring series for organizer, no need to check with exception/occurrence
1638
+
1639
+        if ($calendarItem !== false) {
1640
+            $messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]);
1641
+
1642
+            if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) {
1643
+                return true;
1644
+            }
1645
+        }
1646
+
1647
+        return false;
1648
+    }
1649
+
1650
+    /*
1651 1651
 	 * Support functions - INTERNAL ONLY
1652 1652
 	 ***************************************************************************************************
1653 1653
 	 */
1654 1654
 
1655
-	/**
1656
-	 * Return the tracking status of a recipient based on the IPM class (passed).
1657
-	 *
1658
-	 * @param mixed $class
1659
-	 */
1660
-	public function getTrackStatus($class) {
1661
-		$status = olRecipientTrackStatusNone;
1662
-
1663
-		switch ($class) {
1664
-			case 'IPM.Schedule.Meeting.Resp.Pos':
1665
-				$status = olRecipientTrackStatusAccepted;
1666
-
1667
-				break;
1668
-
1669
-			case 'IPM.Schedule.Meeting.Resp.Tent':
1670
-				$status = olRecipientTrackStatusTentative;
1671
-
1672
-				break;
1673
-
1674
-			case 'IPM.Schedule.Meeting.Resp.Neg':
1675
-				$status = olRecipientTrackStatusDeclined;
1676
-
1677
-				break;
1678
-		}
1679
-
1680
-		return $status;
1681
-	}
1682
-
1683
-	/**
1684
-	 * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1685
-	 * object.
1686
-	 */
1687
-	public function openParentFolder() {
1688
-		$messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1689
-
1690
-		return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1691
-	}
1692
-
1693
-	/**
1694
-	 * Function will return resource of the default calendar folder of store.
1695
-	 *
1696
-	 * @param MAPIStore $store {optional} user store whose default calendar should be opened
1697
-	 *
1698
-	 * @return MAPIFolder default calendar folder of store
1699
-	 */
1700
-	public function openDefaultCalendar($store = false) {
1701
-		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
1702
-	}
1703
-
1704
-	/**
1705
-	 * Function will return resource of the default outbox folder of store.
1706
-	 *
1707
-	 * @param MAPIStore $store {optional} user store whose default outbox should be opened
1708
-	 *
1709
-	 * @return MAPIFolder default outbox folder of store
1710
-	 */
1711
-	public function openDefaultOutbox($store = false) {
1712
-		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1713
-	}
1714
-
1715
-	/**
1716
-	 * Function will return resource of the default wastebasket folder of store.
1717
-	 *
1718
-	 * @param MAPIStore $store {optional} user store whose default wastebasket should be opened
1719
-	 *
1720
-	 * @return MAPIFolder default wastebasket folder of store
1721
-	 */
1722
-	public function openDefaultWastebasket($store = false) {
1723
-		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
1724
-	}
1725
-
1726
-	/**
1727
-	 * Function will return resource of the default calendar folder of store.
1728
-	 *
1729
-	 * @param MAPIStore $store {optional} user store whose default calendar should be opened
1730
-	 *
1731
-	 * @return MAPIFolder default calendar folder of store
1732
-	 */
1733
-	public function getDefaultWastebasketEntryID($store = false) {
1734
-		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
1735
-	}
1736
-
1737
-	/**
1738
-	 * Function will return resource of the default sent mail folder of store.
1739
-	 *
1740
-	 * @param MAPIStore $store {optional} user store whose default sent mail should be opened
1741
-	 *
1742
-	 * @return MAPIFolder default sent mail folder of store
1743
-	 */
1744
-	public function getDefaultSentmailEntryID($store = false) {
1745
-		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1746
-	}
1747
-
1748
-	/**
1749
-	 * Function will return entryid of any default folder of store. This method is useful when you want
1750
-	 * to get entryid of folder which is stored as properties of inbox folder
1751
-	 * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1752
-	 *
1753
-	 * @param PropTag   $prop  proptag of the folder for which we want to get entryid
1754
-	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1755
-	 *
1756
-	 * @return BinString entryid of folder pointed by $prop
1757
-	 */
1758
-	public function getDefaultFolderEntryID($prop, $store = false) {
1759
-		try {
1760
-			$inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
1761
-			$inboxprops = mapi_getprops($inbox, [$prop]);
1762
-			if (isset($inboxprops[$prop])) {
1763
-				return $inboxprops[$prop];
1764
-			}
1765
-		}
1766
-		catch (MAPIException $e) {
1767
-			// public store doesn't support this method
1768
-			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1769
-				// don't propagate this error to parent handlers, if store doesn't support it
1770
-				$e->setHandled();
1771
-			}
1772
-		}
1773
-
1774
-		return false;
1775
-	}
1776
-
1777
-	/**
1778
-	 * Function will return resource of any default folder of store.
1779
-	 *
1780
-	 * @param PropTag   $prop  proptag of the folder that we want to open
1781
-	 * @param MAPIStore $store {optional} user store from which we need to open default folder
1782
-	 *
1783
-	 * @return MAPIFolder default folder of store
1784
-	 */
1785
-	public function openDefaultFolder($prop, $store = false) {
1786
-		$folder = false;
1787
-		$entryid = $this->getDefaultFolderEntryID($prop, $store);
1788
-
1789
-		if ($entryid !== false) {
1790
-			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1791
-		}
1792
-
1793
-		return $folder;
1794
-	}
1795
-
1796
-	/**
1797
-	 * Function will return entryid of default folder from store. This method is useful when you want
1798
-	 * to get entryid of folder which is stored as store properties
1799
-	 * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1800
-	 *
1801
-	 * @param PropTag   $prop  proptag of the folder whose entryid we want to get
1802
-	 * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1803
-	 *
1804
-	 * @return BinString entryid of default folder from store
1805
-	 */
1806
-	public function getBaseEntryID($prop, $store = false) {
1807
-		$storeprops = mapi_getprops($store ? $store : $this->store, [$prop]);
1808
-		if (!isset($storeprops[$prop])) {
1809
-			return false;
1810
-		}
1811
-
1812
-		return $storeprops[$prop];
1813
-	}
1814
-
1815
-	/**
1816
-	 * Function will return resource of any default folder of store.
1817
-	 *
1818
-	 * @param PropTag   $prop  proptag of the folder that we want to open
1819
-	 * @param MAPIStore $store {optional} user store from which we need to open default folder
1820
-	 *
1821
-	 * @return MAPIFolder default folder of store
1822
-	 */
1823
-	public function openBaseFolder($prop, $store = false) {
1824
-		$folder = false;
1825
-		$entryid = $this->getBaseEntryID($prop, $store);
1826
-
1827
-		if ($entryid !== false) {
1828
-			$folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1829
-		}
1830
-
1831
-		return $folder;
1832
-	}
1833
-
1834
-	/**
1835
-	 * Function checks whether user has access over the specified folder or not.
1836
-	 *
1837
-	 * @param Binary    $entryid entryid The entryid of the folder to check
1838
-	 * @param MAPIStore $store   (optional) store from which folder should be opened
1839
-	 *
1840
-	 * @return bool true if user has an access over the folder, false if not
1841
-	 */
1842
-	public function checkFolderWriteAccess($entryid, $store = false) {
1843
-		$accessToFolder = false;
1844
-
1845
-		if (!empty($entryid)) {
1846
-			if ($store === false) {
1847
-				$store = $this->store;
1848
-			}
1849
-
1850
-			try {
1851
-				$folder = mapi_msgstore_openentry($store, $entryid);
1852
-				$folderProps = mapi_getprops($folder, [PR_ACCESS]);
1853
-				if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1854
-					$accessToFolder = true;
1855
-				}
1856
-			}
1857
-			catch (MAPIException $e) {
1858
-				// we don't have rights to open folder, so return false
1859
-				if ($e->getCode() == MAPI_E_NO_ACCESS) {
1860
-					return $accessToFolder;
1861
-				}
1862
-
1863
-				// rethrow other errors
1864
-				throw $e;
1865
-			}
1866
-		}
1867
-
1868
-		return $accessToFolder;
1869
-	}
1870
-
1871
-	/**
1872
-	 * Function checks whether user has access over the specified folder or not.
1873
-	 *
1874
-	 * @param object MAPI Message Store Object
1875
-	 * @param mixed $store
1876
-	 *
1877
-	 * @return bool true if user has an access over the folder, false if not
1878
-	 */
1879
-	public function checkCalendarWriteAccess($store = false) {
1880
-		if ($store === false) {
1881
-			// If this meeting request is received by a delegate then open delegator's store.
1882
-			$messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
1883
-			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1884
-				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1885
-
1886
-				$store = $delegatorStore['store'];
1887
-			}
1888
-			else {
1889
-				$store = $this->store;
1890
-			}
1891
-		}
1892
-
1893
-		// If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1894
-		$provider = mapi_getprops($store, [PR_MDB_PROVIDER]);
1895
-		if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1896
-			$entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1897
-			$entryid = $entryid[PR_PARENT_ENTRYID];
1898
-		}
1899
-		else {
1900
-			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1901
-			if ($entryid === false) {
1902
-				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1903
-			}
1904
-
1905
-			if ($entryid === false) {
1906
-				return false;
1907
-			}
1908
-		}
1909
-
1910
-		return $this->checkFolderWriteAccess($entryid, $store);
1911
-	}
1912
-
1913
-	/**
1914
-	 * Function will resolve the user and open its store.
1915
-	 *
1916
-	 * @param string $ownerentryid the entryid of the user
1917
-	 *
1918
-	 * @return MAPIStore store of the user
1919
-	 */
1920
-	public function openCustomUserStore($ownerentryid) {
1921
-		$ab = mapi_openaddressbook($this->session);
1922
-
1923
-		try {
1924
-			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
1925
-		}
1926
-		catch (MAPIException $e) {
1927
-			return;
1928
-		}
1929
-
1930
-		$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
1931
-		$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
1932
-
1933
-		return mapi_openmsgstore($this->session, $storeid);
1934
-	}
1935
-
1936
-	/**
1937
-	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1938
-	 *
1939
-	 * @param int   $status              response status of attendee
1940
-	 * @param array $proposeNewTimeProps properties of attendee's proposal
1941
-	 * @param int   $basedate            date of occurrence which attendee has responded
1942
-	 * @param mixed $body
1943
-	 * @param mixed $store
1944
-	 * @param mixed $calFolder
1945
-	 */
1946
-	public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) {
1947
-		$messageprops = mapi_getprops($this->message, [
1948
-			PR_SENT_REPRESENTING_ENTRYID,
1949
-			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1950
-			PR_SENT_REPRESENTING_ADDRTYPE,
1951
-			PR_SENT_REPRESENTING_NAME,
1952
-			PR_SENT_REPRESENTING_SEARCH_KEY,
1953
-			$this->proptags['goid'],
1954
-			$this->proptags['goid2'],
1955
-			$this->proptags['location'],
1956
-			$this->proptags['startdate'],
1957
-			$this->proptags['duedate'],
1958
-			$this->proptags['recurring'],
1959
-			$this->proptags['recurring_pattern'],
1960
-			$this->proptags['recurrence_data'],
1961
-			$this->proptags['timezone_data'],
1962
-			$this->proptags['timezone'],
1963
-			$this->proptags['updatecounter'],
1964
-			PR_SUBJECT,
1965
-			PR_MESSAGE_CLASS,
1966
-			PR_OWNER_APPT_ID,
1967
-			$this->proptags['is_exception'],
1968
-		]);
1969
-
1970
-		if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
1971
-			// we are creating response from a recurring calendar item object
1972
-			// We found basedate,so opened occurrence and get properties.
1973
-			$recurr = new Recurrence($store, $this->message);
1974
-			$exception = $recurr->getExceptionAttachment($basedate);
1975
-
1976
-			if ($exception) {
1977
-				// Exception found, Now retrieve properties
1978
-				$imessage = mapi_attach_openobj($exception, 0);
1979
-				$imsgprops = mapi_getprops($imessage);
1980
-
1981
-				// If location is provided, copy it to the response
1982
-				if (isset($imsgprops[$this->proptags['location']])) {
1983
-					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1984
-				}
1985
-
1986
-				// Update $messageprops with timings of occurrence
1987
-				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1988
-				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1989
-
1990
-				// Meeting related properties
1991
-				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1992
-				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1993
-				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1994
-			}
1995
-			else {
1996
-				// Exceptions is deleted.
1997
-				// Update $messageprops with timings of occurrence
1998
-				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1999
-				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
2000
-
2001
-				$props[$this->proptags['meetingstatus']] = olNonMeeting;
2002
-				$props[$this->proptags['responsestatus']] = olResponseNone;
2003
-			}
2004
-
2005
-			$props[$this->proptags['recurring']] = false;
2006
-			$props[$this->proptags['is_exception']] = true;
2007
-		}
2008
-		else {
2009
-			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
2010
-			// Send all recurrence info in response, if this is a recurrence meeting.
2011
-			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
2012
-			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
2013
-			if ($isRecurring || $isException) {
2014
-				if ($isRecurring) {
2015
-					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
2016
-				}
2017
-				if ($isException) {
2018
-					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
2019
-				}
2020
-				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
2021
-
2022
-				$calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
2023
-				$recurr = new Recurrence($store, $calendaritem);
2024
-			}
2025
-		}
2026
-
2027
-		// we are sending a response for recurring meeting request (or exception), so set some required properties
2028
-		if (isset($recurr) && $recurr) {
2029
-			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2030
-				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2031
-			}
2032
-
2033
-			if (!empty($messageprops[$this->proptags['recurrence_data']])) {
2034
-				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2035
-			}
2036
-
2037
-			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2038
-			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2039
-
2040
-			$this->generateRecurDates($recurr, $messageprops, $props);
2041
-		}
2042
-
2043
-		// Create a response message
2044
-		$recip = [];
2045
-		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
2046
-		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2047
-		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
2048
-		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
2049
-		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
2050
-		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
2051
-
2052
-		switch ($status) {
2053
-			case olResponseAccepted:
2054
-				$classpostfix = 'Pos';
2055
-				$subjectprefix = _('Accepted');
2056
-
2057
-				break;
2058
-
2059
-			case olResponseDeclined:
2060
-				$classpostfix = 'Neg';
2061
-				$subjectprefix = _('Declined');
2062
-
2063
-				break;
2064
-
2065
-			case olResponseTentative:
2066
-				$classpostfix = 'Tent';
2067
-				$subjectprefix = _('Tentatively accepted');
2068
-
2069
-				break;
2070
-		}
2071
-
2072
-		if (!empty($proposeNewTimeProps)) {
2073
-			// if attendee has proposed new time then change subject prefix
2074
-			$subjectprefix = _('New Time Proposed');
2075
-		}
2076
-
2077
-		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
2078
-
2079
-		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
2080
-		if (isset($messageprops[PR_OWNER_APPT_ID])) {
2081
-			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2082
-		}
2083
-
2084
-		// Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
2085
-		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2086
-		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2087
-		$props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
2088
-
2089
-		if (!empty($proposeNewTimeProps)) {
2090
-			// merge proposal properties to message properties which will be sent to organizer
2091
-			$props = $proposeNewTimeProps + $props;
2092
-		}
2093
-
2094
-		// Set body message in Appointment
2095
-		if (isset($body)) {
2096
-			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
2097
-		}
2098
-
2099
-		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
2100
-		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2101
-		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2102
-
2103
-		// Set startdate and duedate in response mail.
2104
-		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
2105
-		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
2106
-
2107
-		// responselocation is used in the UI in Outlook on the response message
2108
-		if (isset($messageprops[$this->proptags['location']])) {
2109
-			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2110
-			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2111
-		}
2112
-
2113
-		$message = $this->createOutgoingMessage($store);
2114
-
2115
-		mapi_setprops($message, $props);
2116
-		mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
2117
-		mapi_savechanges($message);
2118
-		mapi_message_submitmessage($message);
2119
-	}
2120
-
2121
-	/**
2122
-	 * Function which finds items in calendar based on globalId and cleanGlobalId.
2123
-	 *
2124
-	 * @param binary     $goid             GlobalID(0x3) of item
2125
-	 * @param MAPIFolder $calendar         MAPI_folder of user (optional)
2126
-	 * @param bool       $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2127
-	 */
2128
-	public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) {
2129
-		if ($calendar === false) {
2130
-			// Open the Calendar
2131
-			$calendar = $this->openDefaultCalendar();
2132
-		}
2133
-
2134
-		// Find the item by restricting all items to the correct ID
2135
-		$restrict = [
2136
-			RES_AND,
2137
-			[
2138
-				[
2139
-					RES_PROPERTY,
2140
-					[
2141
-						RELOP => RELOP_EQ,
2142
-						ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2143
-						VALUE => $goid,
2144
-					],
2145
-				],
2146
-			], ];
2147
-
2148
-		$calendarcontents = mapi_folder_getcontentstable($calendar);
2149
-
2150
-		$rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
2151
-
2152
-		if (empty($rows)) {
2153
-			return;
2154
-		}
2155
-
2156
-		$calendaritems = [];
2157
-
2158
-		// In principle, there should only be one row, but we'll handle them all just in case
2159
-		foreach ($rows as $row) {
2160
-			$calendaritems[] = $row[PR_ENTRYID];
2161
-		}
2162
-
2163
-		return $calendaritems;
2164
-	}
2165
-
2166
-	// Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2167
-	// same SMTP address when converted to SMTP
2168
-	public function compareABEntryIDs($entryid1, $entryid2) {
2169
-		// If the session was not passed, just do a 'normal' compare.
2170
-		if (!$this->session) {
2171
-			return $entryid1 == $entryid2;
2172
-		}
2173
-
2174
-		$smtp1 = $this->getSMTPAddress($entryid1);
2175
-		$smtp2 = $this->getSMTPAddress($entryid2);
2176
-
2177
-		if ($smtp1 == $smtp2) {
2178
-			return true;
2179
-		}
2180
-
2181
-		return false;
2182
-	}
2183
-
2184
-	// Gets the SMTP address of the passed addressbook entryid
2185
-	public function getSMTPAddress($entryid) {
2186
-		if (!$this->session) {
2187
-			return false;
2188
-		}
2189
-
2190
-		$ab = mapi_openaddressbook($this->session);
2191
-
2192
-		$abitem = mapi_ab_openentry($ab, $entryid);
2193
-
2194
-		if (!$abitem) {
2195
-			return '';
2196
-		}
2197
-
2198
-		$props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
2199
-
2200
-		if ($props[PR_ADDRTYPE] == 'SMTP') {
2201
-			return $props[PR_EMAIL_ADDRESS];
2202
-		}
2203
-
2204
-		return $props[PR_SMTP_ADDRESS];
2205
-	}
2206
-
2207
-	/**
2208
-	 * Gets the properties associated with the owner of the passed store:
2209
-	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
2210
-	 *
2211
-	 * @param $store message store
2212
-	 * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
2213
-	 * not used when passed store is public store. for public store we are always returning logged in user's info.
2214
-	 *
2215
-	 * @return properties of logged in user in an array in sequence of display_name, email address, address type,
2216
-	 *                    entryid and search key
2217
-	 */
2218
-	public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
2219
-		if (!$this->session) {
2220
-			return false;
2221
-		}
2222
-
2223
-		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
2224
-
2225
-		$ownerEntryId = false;
2226
-		if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2227
-			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
2228
-		}
2229
-
2230
-		if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2231
-			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2232
-		}
2233
-
2234
-		if ($ownerEntryId) {
2235
-			$ab = mapi_openaddressbook($this->session);
2236
-
2237
-			$zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
2238
-			if (!$zarafaUser) {
2239
-				return false;
2240
-			}
2241
-
2242
-			$ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
2243
-
2244
-			$addrType = $ownerProps[PR_ADDRTYPE];
2245
-			$name = $ownerProps[PR_DISPLAY_NAME];
2246
-			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2247
-			$searchKey = $ownerProps[PR_SEARCH_KEY];
2248
-			$entryId = $ownerEntryId;
2249
-
2250
-			return [$name, $emailAddr, $addrType, $entryId, $searchKey];
2251
-		}
2252
-
2253
-		return false;
2254
-	}
2255
-
2256
-	// Opens this session's default message store
2257
-	public function openDefaultStore() {
2258
-		$storestable = mapi_getmsgstorestable($this->session);
2259
-		$rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
2260
-
2261
-		foreach ($rows as $row) {
2262
-			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2263
-				$entryid = $row[PR_ENTRYID];
2264
-
2265
-				break;
2266
-			}
2267
-		}
2268
-
2269
-		if (!$entryid) {
2270
-			return false;
2271
-		}
2272
-
2273
-		return mapi_openmsgstore($this->session, $entryid);
2274
-	}
2275
-
2276
-	/**
2277
-	 *  Function which adds organizer to recipient list which is passed.
2278
-	 *  This function also checks if it has organizer.
2279
-	 *
2280
-	 * @param array $messageProps message properties
2281
-	 * @param array $recipients   recipients list of message
2282
-	 * @param bool  $isException  true if we are processing recipient of exception
2283
-	 */
2284
-	public function addOrganizer($messageProps, &$recipients, $isException = false) {
2285
-		$hasOrganizer = false;
2286
-		// Check if meeting already has an organizer.
2287
-		foreach ($recipients as $key => $recipient) {
2288
-			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2289
-				$hasOrganizer = true;
2290
-			}
2291
-			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2292
-				// Recipients for an occurrence
2293
-				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2294
-			}
2295
-		}
2296
-
2297
-		if (!$hasOrganizer) {
2298
-			// Create organizer.
2299
-			$organizer = [];
2300
-			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2301
-			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2302
-			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2303
-			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2304
-			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2305
-			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2306
-			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2307
-			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2308
-			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2309
-
2310
-			// Add organizer to recipients list.
2311
-			array_unshift($recipients, $organizer);
2312
-		}
2313
-	}
2314
-
2315
-	/**
2316
-	 * Function which removes an exception/occurrence from recurrencing meeting
2317
-	 * when a meeting cancellation of an occurrence is processed.
2318
-	 *
2319
-	 * @param string   $basedate basedate of an occurrence
2320
-	 * @param resource $message  recurring item from which occurrence has to be deleted
2321
-	 * @param resource $store    MAPI_MSG_Store which contains the item
2322
-	 */
2323
-	public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2324
-		$recurr = new Recurrence($store, $message);
2325
-		$recurr->createException([], $basedate, true);
2326
-		mapi_savechanges($message);
2327
-	}
2328
-
2329
-	/**
2330
-	 * Function which returns basedate of an changed occurrence from globalID of meeting request.
2331
-	 *
2332
-	 *@param binary $goid globalID
2333
-	 *
2334
-	 *@return bool true if basedate is found else false it not found
2335
-	 */
2336
-	public function getBasedateFromGlobalID($goid) {
2337
-		$hexguid = bin2hex($goid);
2338
-		$hexbase = substr($hexguid, 32, 8);
2339
-		$day = hexdec(substr($hexbase, 6, 2));
2340
-		$month = hexdec(substr($hexbase, 4, 2));
2341
-		$year = hexdec(substr($hexbase, 0, 4));
2342
-
2343
-		if ($day && $month && $year) {
2344
-			return gmmktime(0, 0, 0, $month, $day, $year);
2345
-		}
2346
-
2347
-		return false;
2348
-	}
2349
-
2350
-	/**
2351
-	 * Function which sets basedate in globalID of changed occurrence which is to be send.
2352
-	 *
2353
-	 *@param binary $goid globalID
2354
-	 *@param string basedate of changed occurrence
2355
-	 * @param mixed $basedate
2356
-	 *
2357
-	 *@return binary globalID with basedate in it
2358
-	 */
2359
-	public function setBasedateInGlobalID($goid, $basedate = false) {
2360
-		$hexguid = bin2hex($goid);
2361
-		$year = $basedate ? sprintf('%04s', dechex(gmdate('Y', $basedate))) : '0000';
2362
-		$month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00';
2363
-		$day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00';
2364
-
2365
-		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2366
-	}
2367
-
2368
-	/**
2369
-	 * Function which replaces attachments with copy_from in copy_to.
2370
-	 *
2371
-	 * @param MAPIMessage $copy_from      MAPI_message from which attachments are to be copied
2372
-	 * @param MAPIMessage $copy_to        MAPI_message to which attachment are to be copied
2373
-	 * @param bool        $copyExceptions if true then all exceptions should also be sent as attachments
2374
-	 * @param mixed       $copyFrom
2375
-	 * @param mixed       $copyTo
2376
-	 */
2377
-	public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) {
2378
-		/* remove all old attachments */
2379
-		$attachmentTable = mapi_message_getattachmenttable($copyTo);
2380
-		if ($attachmentTable) {
2381
-			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2382
-
2383
-			foreach ($attachments as $attachProps) {
2384
-				/* remove exceptions too? */
2385
-				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2386
-					continue;
2387
-				}
2388
-				mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
2389
-			}
2390
-		}
2391
-		$attachmentTable = false;
2392
-
2393
-		/* copy new attachments */
2394
-		$attachmentTable = mapi_message_getattachmenttable($copyFrom);
2395
-		if ($attachmentTable) {
2396
-			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2397
-
2398
-			foreach ($attachments as $attachProps) {
2399
-				if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2400
-					continue;
2401
-				}
2402
-
2403
-				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2404
-				$attachNewResourceMsg = mapi_message_createattach($copyTo);
2405
-				mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
2406
-				mapi_savechanges($attachNewResourceMsg);
2407
-			}
2408
-		}
2409
-	}
2410
-
2411
-	/**
2412
-	 * Function which replaces recipients in copy_to with recipients from copyFrom.
2413
-	 *
2414
-	 * @param MAPIMessage $copyFrom   MAPI_message from which recipients are to be copied
2415
-	 * @param MAPIMessage $copyTo     MAPI_message to which recipients are to be copied
2416
-	 * @param bool        $isDelegate indicates delegate is processing
2417
-	 *                                so don't copy delegate information to recipient table
2418
-	 */
2419
-	public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) {
2420
-		$recipientTable = mapi_message_getrecipienttable($copyFrom);
2421
-
2422
-		// If delegate, then do not add the delegate in recipients
2423
-		if ($isDelegate) {
2424
-			$delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2425
-			$res = [RES_PROPERTY, [
2426
-				RELOP => RELOP_NE,
2427
-				ULPROPTAG => PR_EMAIL_ADDRESS,
2428
-				VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2429
-			],
2430
-			];
2431
-			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2432
-		}
2433
-		else {
2434
-			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2435
-		}
2436
-
2437
-		$copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2438
-		$copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]);
2439
-
2440
-		mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
2441
-		mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2442
-	}
2443
-
2444
-	/**
2445
-	 * Function creates meeting item in resource's calendar.
2446
-	 *
2447
-	 * @param resource $message  MAPI_message which is to create in resource's calendar
2448
-	 * @param bool     $cancel   cancel meeting
2449
-	 * @param string   $prefix   prefix for subject of meeting
2450
-	 * @param mixed    $basedate
2451
-	 */
2452
-	public function bookResources($message, $cancel, $prefix, $basedate = false) {
2453
-		if (!$this->enableDirectBooking) {
2454
-			return [];
2455
-		}
2456
-
2457
-		// Get the properties of the message
2458
-		$messageprops = mapi_getprops($message);
2459
-
2460
-		if ($basedate) {
2461
-			$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
2462
-
2463
-			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2464
-			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2465
-
2466
-			// Delete properties which are not needed.
2467
-			$deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
2468
-			foreach ($deleteProps as $propID) {
2469
-				if (isset($messageprops[$propID])) {
2470
-					unset($messageprops[$propID]);
2471
-				}
2472
-			}
2473
-
2474
-			if (isset($messageprops[$this->proptags['recurring']])) {
2475
-				$messageprops[$this->proptags['recurring']] = false;
2476
-			}
2477
-
2478
-			// Set Outlook properties
2479
-			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2480
-			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2481
-			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2482
-			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2483
-			$messageprops[$this->proptags['attendee_critical_change']] = time();
2484
-			$messageprops[$this->proptags['owner_critical_change']] = time();
2485
-		}
2486
-
2487
-		// Get resource recipients
2488
-		$getResourcesRestriction = [
2489
-			RES_AND,
2490
-			[
2491
-				[
2492
-					RES_PROPERTY,
2493
-					[
2494
-						RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2495
-						ULPROPTAG => PR_RECIPIENT_TYPE,
2496
-						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2497
-					],
2498
-				], ],
2499
-		];
2500
-		$recipienttable = mapi_message_getrecipienttable($message);
2501
-		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2502
-
2503
-		$this->errorSetResource = false;
2504
-		$resourceRecipData = [];
2505
-
2506
-		// Put appointment into store resource users
2507
-		$i = 0;
2508
-		$len = count($resourceRecipients);
2509
-		while (!$this->errorSetResource && $i < $len) {
2510
-			$userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
2511
-
2512
-			// Open root folder
2513
-			$userRoot = mapi_msgstore_openentry($userStore, null);
2514
-
2515
-			// Get calendar entryID
2516
-			$userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
2517
-
2518
-			// Open Calendar folder
2519
-			$accessToFolder = false;
2520
-
2521
-			try {
2522
-				// @FIXME this checks delegate has access to resource's calendar folder
2523
-				// but it should use boss' credentials
2524
-
2525
-				$accessToFolder = $this->checkCalendarWriteAccess($this->store);
2526
-				if ($accessToFolder) {
2527
-					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2528
-				}
2529
-			}
2530
-			catch (MAPIException $e) {
2531
-				$e->setHandled();
2532
-				$this->errorSetResource = 1; // No access
2533
-			}
2534
-
2535
-			if ($accessToFolder) {
2536
-				/**
2537
-				 * Get the LocalFreebusy message that contains the properties that
2538
-				 * are set to accept or decline resource meeting requests.
2539
-				 */
2540
-				// Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
2541
-				$localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
2542
-				if ($localFreebusyMsg) {
2543
-					$props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]);
2544
-
2545
-					$acceptMeetingRequests = isset($props[PR_PROCESS_MEETING_REQUESTS]) ? $props[PR_PROCESS_MEETING_REQUESTS] : false;
2546
-					$declineRecurringMeetingRequests = isset($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? $props[PR_DECLINE_RECURRING_MEETING_REQUESTS] : false;
2547
-					$declineConflictingMeetingRequests = isset($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? $props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS] : false;
2548
-
2549
-					if (!$acceptMeetingRequests) {
2550
-						/*
1655
+    /**
1656
+     * Return the tracking status of a recipient based on the IPM class (passed).
1657
+     *
1658
+     * @param mixed $class
1659
+     */
1660
+    public function getTrackStatus($class) {
1661
+        $status = olRecipientTrackStatusNone;
1662
+
1663
+        switch ($class) {
1664
+            case 'IPM.Schedule.Meeting.Resp.Pos':
1665
+                $status = olRecipientTrackStatusAccepted;
1666
+
1667
+                break;
1668
+
1669
+            case 'IPM.Schedule.Meeting.Resp.Tent':
1670
+                $status = olRecipientTrackStatusTentative;
1671
+
1672
+                break;
1673
+
1674
+            case 'IPM.Schedule.Meeting.Resp.Neg':
1675
+                $status = olRecipientTrackStatusDeclined;
1676
+
1677
+                break;
1678
+        }
1679
+
1680
+        return $status;
1681
+    }
1682
+
1683
+    /**
1684
+     * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request
1685
+     * object.
1686
+     */
1687
+    public function openParentFolder() {
1688
+        $messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1689
+
1690
+        return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1691
+    }
1692
+
1693
+    /**
1694
+     * Function will return resource of the default calendar folder of store.
1695
+     *
1696
+     * @param MAPIStore $store {optional} user store whose default calendar should be opened
1697
+     *
1698
+     * @return MAPIFolder default calendar folder of store
1699
+     */
1700
+    public function openDefaultCalendar($store = false) {
1701
+        return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store);
1702
+    }
1703
+
1704
+    /**
1705
+     * Function will return resource of the default outbox folder of store.
1706
+     *
1707
+     * @param MAPIStore $store {optional} user store whose default outbox should be opened
1708
+     *
1709
+     * @return MAPIFolder default outbox folder of store
1710
+     */
1711
+    public function openDefaultOutbox($store = false) {
1712
+        return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1713
+    }
1714
+
1715
+    /**
1716
+     * Function will return resource of the default wastebasket folder of store.
1717
+     *
1718
+     * @param MAPIStore $store {optional} user store whose default wastebasket should be opened
1719
+     *
1720
+     * @return MAPIFolder default wastebasket folder of store
1721
+     */
1722
+    public function openDefaultWastebasket($store = false) {
1723
+        return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store);
1724
+    }
1725
+
1726
+    /**
1727
+     * Function will return resource of the default calendar folder of store.
1728
+     *
1729
+     * @param MAPIStore $store {optional} user store whose default calendar should be opened
1730
+     *
1731
+     * @return MAPIFolder default calendar folder of store
1732
+     */
1733
+    public function getDefaultWastebasketEntryID($store = false) {
1734
+        return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store);
1735
+    }
1736
+
1737
+    /**
1738
+     * Function will return resource of the default sent mail folder of store.
1739
+     *
1740
+     * @param MAPIStore $store {optional} user store whose default sent mail should be opened
1741
+     *
1742
+     * @return MAPIFolder default sent mail folder of store
1743
+     */
1744
+    public function getDefaultSentmailEntryID($store = false) {
1745
+        return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1746
+    }
1747
+
1748
+    /**
1749
+     * Function will return entryid of any default folder of store. This method is useful when you want
1750
+     * to get entryid of folder which is stored as properties of inbox folder
1751
+     * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID).
1752
+     *
1753
+     * @param PropTag   $prop  proptag of the folder for which we want to get entryid
1754
+     * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1755
+     *
1756
+     * @return BinString entryid of folder pointed by $prop
1757
+     */
1758
+    public function getDefaultFolderEntryID($prop, $store = false) {
1759
+        try {
1760
+            $inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store);
1761
+            $inboxprops = mapi_getprops($inbox, [$prop]);
1762
+            if (isset($inboxprops[$prop])) {
1763
+                return $inboxprops[$prop];
1764
+            }
1765
+        }
1766
+        catch (MAPIException $e) {
1767
+            // public store doesn't support this method
1768
+            if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1769
+                // don't propagate this error to parent handlers, if store doesn't support it
1770
+                $e->setHandled();
1771
+            }
1772
+        }
1773
+
1774
+        return false;
1775
+    }
1776
+
1777
+    /**
1778
+     * Function will return resource of any default folder of store.
1779
+     *
1780
+     * @param PropTag   $prop  proptag of the folder that we want to open
1781
+     * @param MAPIStore $store {optional} user store from which we need to open default folder
1782
+     *
1783
+     * @return MAPIFolder default folder of store
1784
+     */
1785
+    public function openDefaultFolder($prop, $store = false) {
1786
+        $folder = false;
1787
+        $entryid = $this->getDefaultFolderEntryID($prop, $store);
1788
+
1789
+        if ($entryid !== false) {
1790
+            $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1791
+        }
1792
+
1793
+        return $folder;
1794
+    }
1795
+
1796
+    /**
1797
+     * Function will return entryid of default folder from store. This method is useful when you want
1798
+     * to get entryid of folder which is stored as store properties
1799
+     * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID).
1800
+     *
1801
+     * @param PropTag   $prop  proptag of the folder whose entryid we want to get
1802
+     * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder
1803
+     *
1804
+     * @return BinString entryid of default folder from store
1805
+     */
1806
+    public function getBaseEntryID($prop, $store = false) {
1807
+        $storeprops = mapi_getprops($store ? $store : $this->store, [$prop]);
1808
+        if (!isset($storeprops[$prop])) {
1809
+            return false;
1810
+        }
1811
+
1812
+        return $storeprops[$prop];
1813
+    }
1814
+
1815
+    /**
1816
+     * Function will return resource of any default folder of store.
1817
+     *
1818
+     * @param PropTag   $prop  proptag of the folder that we want to open
1819
+     * @param MAPIStore $store {optional} user store from which we need to open default folder
1820
+     *
1821
+     * @return MAPIFolder default folder of store
1822
+     */
1823
+    public function openBaseFolder($prop, $store = false) {
1824
+        $folder = false;
1825
+        $entryid = $this->getBaseEntryID($prop, $store);
1826
+
1827
+        if ($entryid !== false) {
1828
+            $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid);
1829
+        }
1830
+
1831
+        return $folder;
1832
+    }
1833
+
1834
+    /**
1835
+     * Function checks whether user has access over the specified folder or not.
1836
+     *
1837
+     * @param Binary    $entryid entryid The entryid of the folder to check
1838
+     * @param MAPIStore $store   (optional) store from which folder should be opened
1839
+     *
1840
+     * @return bool true if user has an access over the folder, false if not
1841
+     */
1842
+    public function checkFolderWriteAccess($entryid, $store = false) {
1843
+        $accessToFolder = false;
1844
+
1845
+        if (!empty($entryid)) {
1846
+            if ($store === false) {
1847
+                $store = $this->store;
1848
+            }
1849
+
1850
+            try {
1851
+                $folder = mapi_msgstore_openentry($store, $entryid);
1852
+                $folderProps = mapi_getprops($folder, [PR_ACCESS]);
1853
+                if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1854
+                    $accessToFolder = true;
1855
+                }
1856
+            }
1857
+            catch (MAPIException $e) {
1858
+                // we don't have rights to open folder, so return false
1859
+                if ($e->getCode() == MAPI_E_NO_ACCESS) {
1860
+                    return $accessToFolder;
1861
+                }
1862
+
1863
+                // rethrow other errors
1864
+                throw $e;
1865
+            }
1866
+        }
1867
+
1868
+        return $accessToFolder;
1869
+    }
1870
+
1871
+    /**
1872
+     * Function checks whether user has access over the specified folder or not.
1873
+     *
1874
+     * @param object MAPI Message Store Object
1875
+     * @param mixed $store
1876
+     *
1877
+     * @return bool true if user has an access over the folder, false if not
1878
+     */
1879
+    public function checkCalendarWriteAccess($store = false) {
1880
+        if ($store === false) {
1881
+            // If this meeting request is received by a delegate then open delegator's store.
1882
+            $messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
1883
+            if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
1884
+                $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1885
+
1886
+                $store = $delegatorStore['store'];
1887
+            }
1888
+            else {
1889
+                $store = $this->store;
1890
+            }
1891
+        }
1892
+
1893
+        // If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item
1894
+        $provider = mapi_getprops($store, [PR_MDB_PROVIDER]);
1895
+        if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1896
+            $entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1897
+            $entryid = $entryid[PR_PARENT_ENTRYID];
1898
+        }
1899
+        else {
1900
+            $entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1901
+            if ($entryid === false) {
1902
+                $entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1903
+            }
1904
+
1905
+            if ($entryid === false) {
1906
+                return false;
1907
+            }
1908
+        }
1909
+
1910
+        return $this->checkFolderWriteAccess($entryid, $store);
1911
+    }
1912
+
1913
+    /**
1914
+     * Function will resolve the user and open its store.
1915
+     *
1916
+     * @param string $ownerentryid the entryid of the user
1917
+     *
1918
+     * @return MAPIStore store of the user
1919
+     */
1920
+    public function openCustomUserStore($ownerentryid) {
1921
+        $ab = mapi_openaddressbook($this->session);
1922
+
1923
+        try {
1924
+            $mailuser = mapi_ab_openentry($ab, $ownerentryid);
1925
+        }
1926
+        catch (MAPIException $e) {
1927
+            return;
1928
+        }
1929
+
1930
+        $mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
1931
+        $storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
1932
+
1933
+        return mapi_openmsgstore($this->session, $storeid);
1934
+    }
1935
+
1936
+    /**
1937
+     * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1938
+     *
1939
+     * @param int   $status              response status of attendee
1940
+     * @param array $proposeNewTimeProps properties of attendee's proposal
1941
+     * @param int   $basedate            date of occurrence which attendee has responded
1942
+     * @param mixed $body
1943
+     * @param mixed $store
1944
+     * @param mixed $calFolder
1945
+     */
1946
+    public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) {
1947
+        $messageprops = mapi_getprops($this->message, [
1948
+            PR_SENT_REPRESENTING_ENTRYID,
1949
+            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1950
+            PR_SENT_REPRESENTING_ADDRTYPE,
1951
+            PR_SENT_REPRESENTING_NAME,
1952
+            PR_SENT_REPRESENTING_SEARCH_KEY,
1953
+            $this->proptags['goid'],
1954
+            $this->proptags['goid2'],
1955
+            $this->proptags['location'],
1956
+            $this->proptags['startdate'],
1957
+            $this->proptags['duedate'],
1958
+            $this->proptags['recurring'],
1959
+            $this->proptags['recurring_pattern'],
1960
+            $this->proptags['recurrence_data'],
1961
+            $this->proptags['timezone_data'],
1962
+            $this->proptags['timezone'],
1963
+            $this->proptags['updatecounter'],
1964
+            PR_SUBJECT,
1965
+            PR_MESSAGE_CLASS,
1966
+            PR_OWNER_APPT_ID,
1967
+            $this->proptags['is_exception'],
1968
+        ]);
1969
+
1970
+        if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) {
1971
+            // we are creating response from a recurring calendar item object
1972
+            // We found basedate,so opened occurrence and get properties.
1973
+            $recurr = new Recurrence($store, $this->message);
1974
+            $exception = $recurr->getExceptionAttachment($basedate);
1975
+
1976
+            if ($exception) {
1977
+                // Exception found, Now retrieve properties
1978
+                $imessage = mapi_attach_openobj($exception, 0);
1979
+                $imsgprops = mapi_getprops($imessage);
1980
+
1981
+                // If location is provided, copy it to the response
1982
+                if (isset($imsgprops[$this->proptags['location']])) {
1983
+                    $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1984
+                }
1985
+
1986
+                // Update $messageprops with timings of occurrence
1987
+                $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1988
+                $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1989
+
1990
+                // Meeting related properties
1991
+                $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1992
+                $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1993
+                $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1994
+            }
1995
+            else {
1996
+                // Exceptions is deleted.
1997
+                // Update $messageprops with timings of occurrence
1998
+                $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1999
+                $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
2000
+
2001
+                $props[$this->proptags['meetingstatus']] = olNonMeeting;
2002
+                $props[$this->proptags['responsestatus']] = olResponseNone;
2003
+            }
2004
+
2005
+            $props[$this->proptags['recurring']] = false;
2006
+            $props[$this->proptags['is_exception']] = true;
2007
+        }
2008
+        else {
2009
+            // we are creating a response from meeting request mail (it could be recurring or non-recurring)
2010
+            // Send all recurrence info in response, if this is a recurrence meeting.
2011
+            $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
2012
+            $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
2013
+            if ($isRecurring || $isException) {
2014
+                if ($isRecurring) {
2015
+                    $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
2016
+                }
2017
+                if ($isException) {
2018
+                    $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
2019
+                }
2020
+                $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
2021
+
2022
+                $calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]);
2023
+                $recurr = new Recurrence($store, $calendaritem);
2024
+            }
2025
+        }
2026
+
2027
+        // we are sending a response for recurring meeting request (or exception), so set some required properties
2028
+        if (isset($recurr) && $recurr) {
2029
+            if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2030
+                $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2031
+            }
2032
+
2033
+            if (!empty($messageprops[$this->proptags['recurrence_data']])) {
2034
+                $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2035
+            }
2036
+
2037
+            $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2038
+            $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2039
+
2040
+            $this->generateRecurDates($recurr, $messageprops, $props);
2041
+        }
2042
+
2043
+        // Create a response message
2044
+        $recip = [];
2045
+        $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
2046
+        $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2047
+        $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
2048
+        $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
2049
+        $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
2050
+        $recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
2051
+
2052
+        switch ($status) {
2053
+            case olResponseAccepted:
2054
+                $classpostfix = 'Pos';
2055
+                $subjectprefix = _('Accepted');
2056
+
2057
+                break;
2058
+
2059
+            case olResponseDeclined:
2060
+                $classpostfix = 'Neg';
2061
+                $subjectprefix = _('Declined');
2062
+
2063
+                break;
2064
+
2065
+            case olResponseTentative:
2066
+                $classpostfix = 'Tent';
2067
+                $subjectprefix = _('Tentatively accepted');
2068
+
2069
+                break;
2070
+        }
2071
+
2072
+        if (!empty($proposeNewTimeProps)) {
2073
+            // if attendee has proposed new time then change subject prefix
2074
+            $subjectprefix = _('New Time Proposed');
2075
+        }
2076
+
2077
+        $props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
2078
+
2079
+        $props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
2080
+        if (isset($messageprops[PR_OWNER_APPT_ID])) {
2081
+            $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2082
+        }
2083
+
2084
+        // Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3).
2085
+        $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2086
+        $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2087
+        $props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0;
2088
+
2089
+        if (!empty($proposeNewTimeProps)) {
2090
+            // merge proposal properties to message properties which will be sent to organizer
2091
+            $props = $proposeNewTimeProps + $props;
2092
+        }
2093
+
2094
+        // Set body message in Appointment
2095
+        if (isset($body)) {
2096
+            $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
2097
+        }
2098
+
2099
+        // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
2100
+        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2101
+        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2102
+
2103
+        // Set startdate and duedate in response mail.
2104
+        $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
2105
+        $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
2106
+
2107
+        // responselocation is used in the UI in Outlook on the response message
2108
+        if (isset($messageprops[$this->proptags['location']])) {
2109
+            $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
2110
+            $props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
2111
+        }
2112
+
2113
+        $message = $this->createOutgoingMessage($store);
2114
+
2115
+        mapi_setprops($message, $props);
2116
+        mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
2117
+        mapi_savechanges($message);
2118
+        mapi_message_submitmessage($message);
2119
+    }
2120
+
2121
+    /**
2122
+     * Function which finds items in calendar based on globalId and cleanGlobalId.
2123
+     *
2124
+     * @param binary     $goid             GlobalID(0x3) of item
2125
+     * @param MAPIFolder $calendar         MAPI_folder of user (optional)
2126
+     * @param bool       $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3)
2127
+     */
2128
+    public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) {
2129
+        if ($calendar === false) {
2130
+            // Open the Calendar
2131
+            $calendar = $this->openDefaultCalendar();
2132
+        }
2133
+
2134
+        // Find the item by restricting all items to the correct ID
2135
+        $restrict = [
2136
+            RES_AND,
2137
+            [
2138
+                [
2139
+                    RES_PROPERTY,
2140
+                    [
2141
+                        RELOP => RELOP_EQ,
2142
+                        ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']),
2143
+                        VALUE => $goid,
2144
+                    ],
2145
+                ],
2146
+            ], ];
2147
+
2148
+        $calendarcontents = mapi_folder_getcontentstable($calendar);
2149
+
2150
+        $rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
2151
+
2152
+        if (empty($rows)) {
2153
+            return;
2154
+        }
2155
+
2156
+        $calendaritems = [];
2157
+
2158
+        // In principle, there should only be one row, but we'll handle them all just in case
2159
+        foreach ($rows as $row) {
2160
+            $calendaritems[] = $row[PR_ENTRYID];
2161
+        }
2162
+
2163
+        return $calendaritems;
2164
+    }
2165
+
2166
+    // Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the
2167
+    // same SMTP address when converted to SMTP
2168
+    public function compareABEntryIDs($entryid1, $entryid2) {
2169
+        // If the session was not passed, just do a 'normal' compare.
2170
+        if (!$this->session) {
2171
+            return $entryid1 == $entryid2;
2172
+        }
2173
+
2174
+        $smtp1 = $this->getSMTPAddress($entryid1);
2175
+        $smtp2 = $this->getSMTPAddress($entryid2);
2176
+
2177
+        if ($smtp1 == $smtp2) {
2178
+            return true;
2179
+        }
2180
+
2181
+        return false;
2182
+    }
2183
+
2184
+    // Gets the SMTP address of the passed addressbook entryid
2185
+    public function getSMTPAddress($entryid) {
2186
+        if (!$this->session) {
2187
+            return false;
2188
+        }
2189
+
2190
+        $ab = mapi_openaddressbook($this->session);
2191
+
2192
+        $abitem = mapi_ab_openentry($ab, $entryid);
2193
+
2194
+        if (!$abitem) {
2195
+            return '';
2196
+        }
2197
+
2198
+        $props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
2199
+
2200
+        if ($props[PR_ADDRTYPE] == 'SMTP') {
2201
+            return $props[PR_EMAIL_ADDRESS];
2202
+        }
2203
+
2204
+        return $props[PR_SMTP_ADDRESS];
2205
+    }
2206
+
2207
+    /**
2208
+     * Gets the properties associated with the owner of the passed store:
2209
+     * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
2210
+     *
2211
+     * @param $store message store
2212
+     * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
2213
+     * not used when passed store is public store. for public store we are always returning logged in user's info.
2214
+     *
2215
+     * @return properties of logged in user in an array in sequence of display_name, email address, address type,
2216
+     *                    entryid and search key
2217
+     */
2218
+    public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
2219
+        if (!$this->session) {
2220
+            return false;
2221
+        }
2222
+
2223
+        $storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
2224
+
2225
+        $ownerEntryId = false;
2226
+        if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
2227
+            $ownerEntryId = $storeProps[PR_USER_ENTRYID];
2228
+        }
2229
+
2230
+        if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
2231
+            $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
2232
+        }
2233
+
2234
+        if ($ownerEntryId) {
2235
+            $ab = mapi_openaddressbook($this->session);
2236
+
2237
+            $zarafaUser = mapi_ab_openentry($ab, $ownerEntryId);
2238
+            if (!$zarafaUser) {
2239
+                return false;
2240
+            }
2241
+
2242
+            $ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
2243
+
2244
+            $addrType = $ownerProps[PR_ADDRTYPE];
2245
+            $name = $ownerProps[PR_DISPLAY_NAME];
2246
+            $emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
2247
+            $searchKey = $ownerProps[PR_SEARCH_KEY];
2248
+            $entryId = $ownerEntryId;
2249
+
2250
+            return [$name, $emailAddr, $addrType, $entryId, $searchKey];
2251
+        }
2252
+
2253
+        return false;
2254
+    }
2255
+
2256
+    // Opens this session's default message store
2257
+    public function openDefaultStore() {
2258
+        $storestable = mapi_getmsgstorestable($this->session);
2259
+        $rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
2260
+
2261
+        foreach ($rows as $row) {
2262
+            if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2263
+                $entryid = $row[PR_ENTRYID];
2264
+
2265
+                break;
2266
+            }
2267
+        }
2268
+
2269
+        if (!$entryid) {
2270
+            return false;
2271
+        }
2272
+
2273
+        return mapi_openmsgstore($this->session, $entryid);
2274
+    }
2275
+
2276
+    /**
2277
+     *  Function which adds organizer to recipient list which is passed.
2278
+     *  This function also checks if it has organizer.
2279
+     *
2280
+     * @param array $messageProps message properties
2281
+     * @param array $recipients   recipients list of message
2282
+     * @param bool  $isException  true if we are processing recipient of exception
2283
+     */
2284
+    public function addOrganizer($messageProps, &$recipients, $isException = false) {
2285
+        $hasOrganizer = false;
2286
+        // Check if meeting already has an organizer.
2287
+        foreach ($recipients as $key => $recipient) {
2288
+            if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2289
+                $hasOrganizer = true;
2290
+            }
2291
+            elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2292
+                // Recipients for an occurrence
2293
+                $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2294
+            }
2295
+        }
2296
+
2297
+        if (!$hasOrganizer) {
2298
+            // Create organizer.
2299
+            $organizer = [];
2300
+            $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2301
+            $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2302
+            $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2303
+            $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2304
+            $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2305
+            $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2306
+            $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2307
+            $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2308
+            $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2309
+
2310
+            // Add organizer to recipients list.
2311
+            array_unshift($recipients, $organizer);
2312
+        }
2313
+    }
2314
+
2315
+    /**
2316
+     * Function which removes an exception/occurrence from recurrencing meeting
2317
+     * when a meeting cancellation of an occurrence is processed.
2318
+     *
2319
+     * @param string   $basedate basedate of an occurrence
2320
+     * @param resource $message  recurring item from which occurrence has to be deleted
2321
+     * @param resource $store    MAPI_MSG_Store which contains the item
2322
+     */
2323
+    public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2324
+        $recurr = new Recurrence($store, $message);
2325
+        $recurr->createException([], $basedate, true);
2326
+        mapi_savechanges($message);
2327
+    }
2328
+
2329
+    /**
2330
+     * Function which returns basedate of an changed occurrence from globalID of meeting request.
2331
+     *
2332
+     *@param binary $goid globalID
2333
+     *
2334
+     *@return bool true if basedate is found else false it not found
2335
+     */
2336
+    public function getBasedateFromGlobalID($goid) {
2337
+        $hexguid = bin2hex($goid);
2338
+        $hexbase = substr($hexguid, 32, 8);
2339
+        $day = hexdec(substr($hexbase, 6, 2));
2340
+        $month = hexdec(substr($hexbase, 4, 2));
2341
+        $year = hexdec(substr($hexbase, 0, 4));
2342
+
2343
+        if ($day && $month && $year) {
2344
+            return gmmktime(0, 0, 0, $month, $day, $year);
2345
+        }
2346
+
2347
+        return false;
2348
+    }
2349
+
2350
+    /**
2351
+     * Function which sets basedate in globalID of changed occurrence which is to be send.
2352
+     *
2353
+     *@param binary $goid globalID
2354
+     *@param string basedate of changed occurrence
2355
+     * @param mixed $basedate
2356
+     *
2357
+     *@return binary globalID with basedate in it
2358
+     */
2359
+    public function setBasedateInGlobalID($goid, $basedate = false) {
2360
+        $hexguid = bin2hex($goid);
2361
+        $year = $basedate ? sprintf('%04s', dechex(gmdate('Y', $basedate))) : '0000';
2362
+        $month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00';
2363
+        $day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00';
2364
+
2365
+        return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2366
+    }
2367
+
2368
+    /**
2369
+     * Function which replaces attachments with copy_from in copy_to.
2370
+     *
2371
+     * @param MAPIMessage $copy_from      MAPI_message from which attachments are to be copied
2372
+     * @param MAPIMessage $copy_to        MAPI_message to which attachment are to be copied
2373
+     * @param bool        $copyExceptions if true then all exceptions should also be sent as attachments
2374
+     * @param mixed       $copyFrom
2375
+     * @param mixed       $copyTo
2376
+     */
2377
+    public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) {
2378
+        /* remove all old attachments */
2379
+        $attachmentTable = mapi_message_getattachmenttable($copyTo);
2380
+        if ($attachmentTable) {
2381
+            $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2382
+
2383
+            foreach ($attachments as $attachProps) {
2384
+                /* remove exceptions too? */
2385
+                if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2386
+                    continue;
2387
+                }
2388
+                mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]);
2389
+            }
2390
+        }
2391
+        $attachmentTable = false;
2392
+
2393
+        /* copy new attachments */
2394
+        $attachmentTable = mapi_message_getattachmenttable($copyFrom);
2395
+        if ($attachmentTable) {
2396
+            $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2397
+
2398
+            foreach ($attachments as $attachProps) {
2399
+                if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) {
2400
+                    continue;
2401
+                }
2402
+
2403
+                $attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2404
+                $attachNewResourceMsg = mapi_message_createattach($copyTo);
2405
+                mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
2406
+                mapi_savechanges($attachNewResourceMsg);
2407
+            }
2408
+        }
2409
+    }
2410
+
2411
+    /**
2412
+     * Function which replaces recipients in copy_to with recipients from copyFrom.
2413
+     *
2414
+     * @param MAPIMessage $copyFrom   MAPI_message from which recipients are to be copied
2415
+     * @param MAPIMessage $copyTo     MAPI_message to which recipients are to be copied
2416
+     * @param bool        $isDelegate indicates delegate is processing
2417
+     *                                so don't copy delegate information to recipient table
2418
+     */
2419
+    public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) {
2420
+        $recipientTable = mapi_message_getrecipienttable($copyFrom);
2421
+
2422
+        // If delegate, then do not add the delegate in recipients
2423
+        if ($isDelegate) {
2424
+            $delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2425
+            $res = [RES_PROPERTY, [
2426
+                RELOP => RELOP_NE,
2427
+                ULPROPTAG => PR_EMAIL_ADDRESS,
2428
+                VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2429
+            ],
2430
+            ];
2431
+            $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2432
+        }
2433
+        else {
2434
+            $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2435
+        }
2436
+
2437
+        $copyToRecipientTable = mapi_message_getrecipienttable($copyTo);
2438
+        $copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]);
2439
+
2440
+        mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows);
2441
+        mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients);
2442
+    }
2443
+
2444
+    /**
2445
+     * Function creates meeting item in resource's calendar.
2446
+     *
2447
+     * @param resource $message  MAPI_message which is to create in resource's calendar
2448
+     * @param bool     $cancel   cancel meeting
2449
+     * @param string   $prefix   prefix for subject of meeting
2450
+     * @param mixed    $basedate
2451
+     */
2452
+    public function bookResources($message, $cancel, $prefix, $basedate = false) {
2453
+        if (!$this->enableDirectBooking) {
2454
+            return [];
2455
+        }
2456
+
2457
+        // Get the properties of the message
2458
+        $messageprops = mapi_getprops($message);
2459
+
2460
+        if ($basedate) {
2461
+            $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
2462
+
2463
+            $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2464
+            $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2465
+
2466
+            // Delete properties which are not needed.
2467
+            $deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
2468
+            foreach ($deleteProps as $propID) {
2469
+                if (isset($messageprops[$propID])) {
2470
+                    unset($messageprops[$propID]);
2471
+                }
2472
+            }
2473
+
2474
+            if (isset($messageprops[$this->proptags['recurring']])) {
2475
+                $messageprops[$this->proptags['recurring']] = false;
2476
+            }
2477
+
2478
+            // Set Outlook properties
2479
+            $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2480
+            $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2481
+            $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2482
+            $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2483
+            $messageprops[$this->proptags['attendee_critical_change']] = time();
2484
+            $messageprops[$this->proptags['owner_critical_change']] = time();
2485
+        }
2486
+
2487
+        // Get resource recipients
2488
+        $getResourcesRestriction = [
2489
+            RES_AND,
2490
+            [
2491
+                [
2492
+                    RES_PROPERTY,
2493
+                    [
2494
+                        RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2495
+                        ULPROPTAG => PR_RECIPIENT_TYPE,
2496
+                        VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2497
+                    ],
2498
+                ], ],
2499
+        ];
2500
+        $recipienttable = mapi_message_getrecipienttable($message);
2501
+        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2502
+
2503
+        $this->errorSetResource = false;
2504
+        $resourceRecipData = [];
2505
+
2506
+        // Put appointment into store resource users
2507
+        $i = 0;
2508
+        $len = count($resourceRecipients);
2509
+        while (!$this->errorSetResource && $i < $len) {
2510
+            $userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]);
2511
+
2512
+            // Open root folder
2513
+            $userRoot = mapi_msgstore_openentry($userStore, null);
2514
+
2515
+            // Get calendar entryID
2516
+            $userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
2517
+
2518
+            // Open Calendar folder
2519
+            $accessToFolder = false;
2520
+
2521
+            try {
2522
+                // @FIXME this checks delegate has access to resource's calendar folder
2523
+                // but it should use boss' credentials
2524
+
2525
+                $accessToFolder = $this->checkCalendarWriteAccess($this->store);
2526
+                if ($accessToFolder) {
2527
+                    $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2528
+                }
2529
+            }
2530
+            catch (MAPIException $e) {
2531
+                $e->setHandled();
2532
+                $this->errorSetResource = 1; // No access
2533
+            }
2534
+
2535
+            if ($accessToFolder) {
2536
+                /**
2537
+                 * Get the LocalFreebusy message that contains the properties that
2538
+                 * are set to accept or decline resource meeting requests.
2539
+                 */
2540
+                // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
2541
+                $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
2542
+                if ($localFreebusyMsg) {
2543
+                    $props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]);
2544
+
2545
+                    $acceptMeetingRequests = isset($props[PR_PROCESS_MEETING_REQUESTS]) ? $props[PR_PROCESS_MEETING_REQUESTS] : false;
2546
+                    $declineRecurringMeetingRequests = isset($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? $props[PR_DECLINE_RECURRING_MEETING_REQUESTS] : false;
2547
+                    $declineConflictingMeetingRequests = isset($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? $props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS] : false;
2548
+
2549
+                    if (!$acceptMeetingRequests) {
2550
+                        /*
2551 2551
 						 * When a resource has not been set to automatically accept meeting requests,
2552 2552
 						 * the meeting request has to be sent to him rather than being put directly into
2553 2553
 						 * his calendar. No error should be returned.
2554 2554
 						 */
2555
-						// $errorSetResource = 2;
2556
-						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2557
-					}
2558
-					else {
2559
-						if ($declineRecurringMeetingRequests && !$cancel) {
2560
-							// Check if appointment is recurring
2561
-							if ($messageprops[$this->proptags['recurring']]) {
2562
-								$this->errorSetResource = 3;
2563
-							}
2564
-						}
2565
-						if ($declineConflictingMeetingRequests && !$cancel) {
2566
-							// Check for conflicting items
2567
-							if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
2568
-								$this->errorSetResource = 4; // Conflict
2569
-							}
2570
-						}
2571
-					}
2572
-				}
2573
-			}
2574
-
2575
-			if (!$this->errorSetResource && $accessToFolder) {
2576
-				/**
2577
-				 * First search on GlobalID(0x3)
2578
-				 * 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.
2579
-				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2580
-				 */
2581
-				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2582
-
2583
-				/*
2555
+                        // $errorSetResource = 2;
2556
+                        $this->nonAcceptingResources[] = $resourceRecipients[$i];
2557
+                    }
2558
+                    else {
2559
+                        if ($declineRecurringMeetingRequests && !$cancel) {
2560
+                            // Check if appointment is recurring
2561
+                            if ($messageprops[$this->proptags['recurring']]) {
2562
+                                $this->errorSetResource = 3;
2563
+                            }
2564
+                        }
2565
+                        if ($declineConflictingMeetingRequests && !$cancel) {
2566
+                            // Check for conflicting items
2567
+                            if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) {
2568
+                                $this->errorSetResource = 4; // Conflict
2569
+                            }
2570
+                        }
2571
+                    }
2572
+                }
2573
+            }
2574
+
2575
+            if (!$this->errorSetResource && $accessToFolder) {
2576
+                /**
2577
+                 * First search on GlobalID(0x3)
2578
+                 * 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.
2579
+                 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2580
+                 */
2581
+                $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2582
+
2583
+                /*
2584 2584
 				 * If no entry is found then
2585 2585
 				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2586 2586
 				 * OR
2587 2587
 				 * 2) We were looking for occurrence item but Resource has whole series
2588 2588
 				 */
2589
-				if (empty($rows)) {
2590
-					/**
2591
-					 * Now search on CleanGlobalID(0x23) WHY???
2592
-					 * Because we are looking recurring item.
2593
-					 *
2594
-					 * Possible results of this search
2595
-					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2596
-					 * 2) If Resource was booked for whole series then it should return series.
2597
-					 */
2598
-					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2599
-
2600
-					$newResourceMsg = false;
2601
-					if (!empty($rows)) {
2602
-						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2603
-						foreach ($rows as $row) {
2604
-							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2605
-							$ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2606
-
2607
-							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2608
-								$newResourceMsg = $ResourceMsg;
2609
-
2610
-								break;
2611
-							}
2612
-						}
2613
-					}
2614
-
2615
-					// Still no results found. I giveup, create new message.
2616
-					if (!$newResourceMsg) {
2617
-						$newResourceMsg = mapi_folder_createmessage($calFolder);
2618
-					}
2619
-				}
2620
-				else {
2621
-					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2622
-				}
2623
-
2624
-				// Prefix the subject if needed
2625
-				if ($prefix && isset($messageprops[PR_SUBJECT])) {
2626
-					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2627
-				}
2628
-
2629
-				// Set status to cancelled if needed
2630
-				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2631
-				if ($cancel) {
2632
-					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2633
-					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2634
-				}
2635
-				else {
2636
-					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2637
-				}
2638
-				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2639
-
2640
-				$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
2641
-
2642
-				// Remove the PR_ICON_INDEX as it is not needed in the sent message.
2643
-				$messageprops[PR_ICON_INDEX] = null;
2644
-				$messageprops[PR_RESPONSE_REQUESTED] = true;
2645
-
2646
-				// get the store of organizer, in case of delegates it will be delegate store
2647
-				$defaultStore = $this->openDefaultStore();
2648
-
2649
-				$storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
2650
-				$defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
2651
-
2652
-				// @FIXME use entryid comparison functions here
2653
-				if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2654
-					// get delegate information
2655
-					$addrInfo = $this->getOwnerAddress($defaultStore, false);
2656
-
2657
-					if ($addrInfo) {
2658
-						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2659
-
2660
-						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2661
-						$messageprops[PR_SENDER_NAME] = $ownername;
2662
-						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2663
-						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2664
-						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2665
-					}
2666
-
2667
-					// get delegator information
2668
-					$addrInfo = $this->getOwnerAddress($this->store, false);
2669
-
2670
-					if ($addrInfo) {
2671
-						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2672
-
2673
-						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2674
-						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2675
-						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2676
-						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2677
-						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2678
-					}
2679
-				}
2680
-				else {
2681
-					// get organizer information
2682
-					$addrinfo = $this->getOwnerAddress($this->store);
2683
-
2684
-					if ($addrinfo) {
2685
-						list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2686
-
2687
-						$messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2688
-						$messageprops[PR_SENDER_NAME] = $ownername;
2689
-						$messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2690
-						$messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2691
-						$messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2692
-
2693
-						$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2694
-						$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2695
-						$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2696
-						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2697
-						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2698
-					}
2699
-				}
2700
-
2701
-				$messageprops[$this->proptags['replytime']] = time();
2702
-
2703
-				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2704
-					$recurr = new Recurrence($userStore, $newResourceMsg);
2705
-
2706
-					// Copy recipients list
2707
-					$reciptable = mapi_message_getrecipienttable($message);
2708
-					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2709
-
2710
-					// add owner to recipient table
2711
-					$this->addOrganizer($messageprops, $recips, true);
2712
-
2713
-					// Update occurrence
2714
-					if ($recurr->isException($basedate)) {
2715
-						$recurr->modifyException($messageprops, $basedate, $recips);
2716
-					}
2717
-					else {
2718
-						$recurr->createException($messageprops, $basedate, false, $recips);
2719
-					}
2720
-				}
2721
-				else {
2722
-					mapi_setprops($newResourceMsg, $messageprops);
2723
-
2724
-					// Copy attachments
2725
-					$this->replaceAttachments($message, $newResourceMsg);
2726
-
2727
-					// Copy all recipients too
2728
-					$this->replaceRecipients($message, $newResourceMsg);
2729
-
2730
-					// Now add organizer also to recipient table
2731
-					$recips = [];
2732
-					$this->addOrganizer($messageprops, $recips);
2733
-
2734
-					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2735
-				}
2736
-
2737
-				mapi_savechanges($newResourceMsg);
2738
-
2739
-				$resourceRecipData[] = [
2740
-					'store' => $userStore,
2741
-					'folder' => $calFolder,
2742
-					'msg' => $newResourceMsg,
2743
-				];
2744
-				$this->includesResources = true;
2745
-			}
2746
-			else {
2747
-				/*
2589
+                if (empty($rows)) {
2590
+                    /**
2591
+                     * Now search on CleanGlobalID(0x23) WHY???
2592
+                     * Because we are looking recurring item.
2593
+                     *
2594
+                     * Possible results of this search
2595
+                     * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2596
+                     * 2) If Resource was booked for whole series then it should return series.
2597
+                     */
2598
+                    $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2599
+
2600
+                    $newResourceMsg = false;
2601
+                    if (!empty($rows)) {
2602
+                        // Since we are looking for recurring item, open every result and check for 'recurring' property.
2603
+                        foreach ($rows as $row) {
2604
+                            $ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2605
+                            $ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2606
+
2607
+                            if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2608
+                                $newResourceMsg = $ResourceMsg;
2609
+
2610
+                                break;
2611
+                            }
2612
+                        }
2613
+                    }
2614
+
2615
+                    // Still no results found. I giveup, create new message.
2616
+                    if (!$newResourceMsg) {
2617
+                        $newResourceMsg = mapi_folder_createmessage($calFolder);
2618
+                    }
2619
+                }
2620
+                else {
2621
+                    $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2622
+                }
2623
+
2624
+                // Prefix the subject if needed
2625
+                if ($prefix && isset($messageprops[PR_SUBJECT])) {
2626
+                    $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2627
+                }
2628
+
2629
+                // Set status to cancelled if needed
2630
+                $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2631
+                if ($cancel) {
2632
+                    $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2633
+                    $messageprops[$this->proptags['busystatus']] = fbFree; // Free
2634
+                }
2635
+                else {
2636
+                    $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2637
+                }
2638
+                $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2639
+
2640
+                $messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
2641
+
2642
+                // Remove the PR_ICON_INDEX as it is not needed in the sent message.
2643
+                $messageprops[PR_ICON_INDEX] = null;
2644
+                $messageprops[PR_RESPONSE_REQUESTED] = true;
2645
+
2646
+                // get the store of organizer, in case of delegates it will be delegate store
2647
+                $defaultStore = $this->openDefaultStore();
2648
+
2649
+                $storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
2650
+                $defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
2651
+
2652
+                // @FIXME use entryid comparison functions here
2653
+                if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
2654
+                    // get delegate information
2655
+                    $addrInfo = $this->getOwnerAddress($defaultStore, false);
2656
+
2657
+                    if ($addrInfo) {
2658
+                        list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2659
+
2660
+                        $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2661
+                        $messageprops[PR_SENDER_NAME] = $ownername;
2662
+                        $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2663
+                        $messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2664
+                        $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2665
+                    }
2666
+
2667
+                    // get delegator information
2668
+                    $addrInfo = $this->getOwnerAddress($this->store, false);
2669
+
2670
+                    if ($addrInfo) {
2671
+                        list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo;
2672
+
2673
+                        $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2674
+                        $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2675
+                        $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2676
+                        $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2677
+                        $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2678
+                    }
2679
+                }
2680
+                else {
2681
+                    // get organizer information
2682
+                    $addrinfo = $this->getOwnerAddress($this->store);
2683
+
2684
+                    if ($addrinfo) {
2685
+                        list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2686
+
2687
+                        $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
2688
+                        $messageprops[PR_SENDER_NAME] = $ownername;
2689
+                        $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
2690
+                        $messageprops[PR_SENDER_ENTRYID] = $ownerentryid;
2691
+                        $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
2692
+
2693
+                        $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2694
+                        $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2695
+                        $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2696
+                        $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2697
+                        $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2698
+                    }
2699
+                }
2700
+
2701
+                $messageprops[$this->proptags['replytime']] = time();
2702
+
2703
+                if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2704
+                    $recurr = new Recurrence($userStore, $newResourceMsg);
2705
+
2706
+                    // Copy recipients list
2707
+                    $reciptable = mapi_message_getrecipienttable($message);
2708
+                    $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2709
+
2710
+                    // add owner to recipient table
2711
+                    $this->addOrganizer($messageprops, $recips, true);
2712
+
2713
+                    // Update occurrence
2714
+                    if ($recurr->isException($basedate)) {
2715
+                        $recurr->modifyException($messageprops, $basedate, $recips);
2716
+                    }
2717
+                    else {
2718
+                        $recurr->createException($messageprops, $basedate, false, $recips);
2719
+                    }
2720
+                }
2721
+                else {
2722
+                    mapi_setprops($newResourceMsg, $messageprops);
2723
+
2724
+                    // Copy attachments
2725
+                    $this->replaceAttachments($message, $newResourceMsg);
2726
+
2727
+                    // Copy all recipients too
2728
+                    $this->replaceRecipients($message, $newResourceMsg);
2729
+
2730
+                    // Now add organizer also to recipient table
2731
+                    $recips = [];
2732
+                    $this->addOrganizer($messageprops, $recips);
2733
+
2734
+                    mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2735
+                }
2736
+
2737
+                mapi_savechanges($newResourceMsg);
2738
+
2739
+                $resourceRecipData[] = [
2740
+                    'store' => $userStore,
2741
+                    'folder' => $calFolder,
2742
+                    'msg' => $newResourceMsg,
2743
+                ];
2744
+                $this->includesResources = true;
2745
+            }
2746
+            else {
2747
+                /*
2748 2748
 				 * If no other errors occurred and you have no access to the
2749 2749
 				 * folder of the resource, throw an error=1.
2750 2750
 				 */
2751
-				if (!$this->errorSetResource) {
2752
-					$this->errorSetResource = 1;
2753
-				}
2754
-
2755
-				for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2756
-					// Get the EntryID
2757
-					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2758
-
2759
-					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2760
-				}
2761
-				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2762
-			}
2763
-			++$i;
2764
-		}
2765
-
2766
-		/*
2751
+                if (!$this->errorSetResource) {
2752
+                    $this->errorSetResource = 1;
2753
+                }
2754
+
2755
+                for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2756
+                    // Get the EntryID
2757
+                    $props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2758
+
2759
+                    mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2760
+                }
2761
+                $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2762
+            }
2763
+            ++$i;
2764
+        }
2765
+
2766
+        /*
2767 2767
 		 * Set the BCC-recipients (resources) tackstatus to accepted.
2768 2768
 		 */
2769
-		// Get resource recipients
2770
-		$getResourcesRestriction = [
2771
-			RES_AND,
2772
-			[
2773
-				[
2774
-					RES_PROPERTY,
2775
-					[
2776
-						RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2777
-						ULPROPTAG => PR_RECIPIENT_TYPE,
2778
-						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2779
-					],
2780
-				], ],
2781
-		];
2782
-		$recipienttable = mapi_message_getrecipienttable($message);
2783
-		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2784
-		if (!empty($resourceRecipients)) {
2785
-			// Set Tracking status of resource recipients to olResponseAccepted (3)
2786
-			for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2787
-				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2788
-				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2789
-			}
2790
-			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2791
-		}
2792
-
2793
-		// Publish updated free/busy information
2794
-		if (!$this->errorSetResource) {
2795
-			for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) {
2796
-				$storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]);
2797
-				if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) {
2798
-					$start = time() - 7 * 24 * 60 * 60;
2799
-					$range = strtotime("+6 month");
2800
-					$range = $range - (7 * 24 * 60 * 60);
2801
-
2802
-					$pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2803
-					$pub->publishFB($start, $range); // publish from one week ago, 6 months ahead
2804
-				}
2805
-			}
2806
-		}
2807
-
2808
-		return $resourceRecipData;
2809
-	}
2810
-
2811
-	/**
2812
-	 * Function which save an exception into recurring item.
2813
-	 *
2814
-	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2815
-	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2816
-	 * @param string   $basedate       basedate of occurrence
2817
-	 * @param bool     $move           if true then occurrence item is deleted
2818
-	 * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2819
-	 * @param bool     $userAction     true if user has manually responded to meeting request
2820
-	 * @param resource $store          user store
2821
-	 * @param bool     $isDelegate     true if delegate is processing this meeting request
2822
-	 */
2823
-	public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2824
-		$recurr = new Recurrence($store, $recurringItem);
2825
-
2826
-		// Copy properties from meeting request
2827
-		$exception_props = mapi_getprops($occurrenceItem);
2828
-
2829
-		// Copy recipients list
2830
-		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2831
-		// If delegate, then do not add the delegate in recipients
2832
-		if ($isDelegate) {
2833
-			$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2834
-			$res = [RES_PROPERTY, [
2835
-				RELOP => RELOP_NE,
2836
-				ULPROPTAG => PR_EMAIL_ADDRESS,
2837
-				VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2838
-			],
2839
-			];
2840
-			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2841
-		}
2842
-		else {
2843
-			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2844
-		}
2845
-
2846
-		// add owner to recipient table
2847
-		$this->addOrganizer($exception_props, $recips, true);
2848
-
2849
-		// add delegator to meetings
2850
-		if ($isDelegate) {
2851
-			$this->addDelegator($exception_props, $recips);
2852
-		}
2853
-
2854
-		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2855
-		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2856
-
2857
-		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2858
-			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2859
-				$exception_props[$this->proptags['busystatus']] = fbTentative;
2860
-			}
2861
-			else {
2862
-				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2863
-			}
2864
-			// we already have intendedbusystatus value in $exception_props so no need to copy it
2865
-		}
2866
-		else {
2867
-			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2868
-		}
2869
-
2870
-		if ($userAction) {
2871
-			$addrInfo = $this->getOwnerAddress($this->store);
2872
-
2873
-			// if user has responded then set replytime and name
2874
-			$exception_props[$this->proptags['replytime']] = time();
2875
-			if (!empty($addrInfo)) {
2876
-				$exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2877
-			}
2878
-		}
2879
-
2880
-		if ($recurr->isException($basedate)) {
2881
-			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2882
-		}
2883
-		else {
2884
-			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2885
-		}
2886
-
2887
-		// Move the occurrenceItem to the waste basket
2888
-		if ($move) {
2889
-			$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2890
-			$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2891
-			mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2892
-		}
2893
-
2894
-		mapi_savechanges($recurringItem);
2895
-	}
2896
-
2897
-	/**
2898
-	 * Function which merges an exception mapi message to recurring message.
2899
-	 * This will be used when we receive recurring meeting request and we already have an exception message
2900
-	 * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2901
-	 * of recurring meeting.
2902
-	 *
2903
-	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2904
-	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2905
-	 * @param string   $basedate       basedate of occurrence
2906
-	 * @param resource $store          user store
2907
-	 */
2908
-	public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) {
2909
-		$recurr = new Recurrence($store, $recurringItem);
2910
-
2911
-		// Copy properties from meeting request
2912
-		$exception_props = mapi_getprops($occurrenceItem);
2913
-
2914
-		// Get recipient list from message and add it to exception attachment
2915
-		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2916
-		$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2917
-
2918
-		if ($recurr->isException($basedate)) {
2919
-			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2920
-		}
2921
-		else {
2922
-			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2923
-		}
2924
-
2925
-		// Move the occurrenceItem to the waste basket
2926
-		$wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2927
-		$sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2928
-		mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2929
-
2930
-		mapi_savechanges($recurringItem);
2931
-	}
2932
-
2933
-	/**
2934
-	 * Function which submits meeting request based on arguments passed to it.
2935
-	 *
2936
-	 * @param resource $message        MAPI_message whose meeting request is to be send
2937
-	 * @param bool     $cancel         if true send request, else send cancellation
2938
-	 * @param string   $prefix         subject prefix
2939
-	 * @param int      $basedate       basedate for an occurrence
2940
-	 * @param object   $recurObject    recurrence object of mr
2941
-	 * @param bool     $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2942
-	 * @param mixed    $modifiedRecips
2943
-	 * @param mixed    $deletedRecips
2944
-	 */
2945
-	public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) {
2946
-		$newmessageprops = $messageprops = mapi_getprops($this->message);
2947
-		$new = $this->createOutgoingMessage();
2948
-
2949
-		// Copy the entire message into the new meeting request message
2950
-		if ($basedate) {
2951
-			// messageprops contains properties of whole recurring series
2952
-			// and newmessageprops contains properties of exception item
2953
-			$newmessageprops = mapi_getprops($message);
2954
-
2955
-			// Ensure that the correct basedate is set in the new message
2956
-			$newmessageprops[$this->proptags['basedate']] = $basedate;
2957
-
2958
-			// Set isRecurring to false, because this is an exception
2959
-			$newmessageprops[$this->proptags['recurring']] = false;
2960
-
2961
-			// set LID_IS_EXCEPTION to true
2962
-			$newmessageprops[$this->proptags['is_exception']] = true;
2963
-
2964
-			// Set to high importance
2965
-			if ($cancel) {
2966
-				$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2967
-			}
2968
-
2969
-			// Set startdate and enddate of exception
2970
-			if ($cancel && $recurObject) {
2971
-				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2972
-				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2973
-			}
2974
-
2975
-			// Set basedate in guid (0x3)
2976
-			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2977
-			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2978
-			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2979
-
2980
-			// Get deleted recipiets from exception msg
2981
-			$restriction = [
2982
-				RES_AND,
2983
-				[
2984
-					[
2985
-						RES_BITMASK,
2986
-						[
2987
-							ULTYPE => BMR_NEZ,
2988
-							ULPROPTAG => PR_RECIPIENT_FLAGS,
2989
-							ULMASK => recipExceptionalDeleted,
2990
-						],
2991
-					],
2992
-					[
2993
-						RES_BITMASK,
2994
-						[
2995
-							ULTYPE => BMR_EQZ,
2996
-							ULPROPTAG => PR_RECIPIENT_FLAGS,
2997
-							ULMASK => recipOrganizer,
2998
-						],
2999
-					],
3000
-				],
3001
-			];
3002
-
3003
-			// In direct-booking mode, we don't need to send cancellations to resources
3004
-			if ($this->enableDirectBooking) {
3005
-				$restriction[1][] = [
3006
-					RES_PROPERTY,
3007
-					[
3008
-						RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3009
-						ULPROPTAG => PR_RECIPIENT_TYPE,
3010
-						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3011
-					],
3012
-				];
3013
-			}
3014
-
3015
-			$recipienttable = mapi_message_getrecipienttable($message);
3016
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
3017
-
3018
-			if (!$deletedRecips) {
3019
-				$deletedRecips = array_merge([], $recipients);
3020
-			}
3021
-			else {
3022
-				$deletedRecips = array_merge($deletedRecips, $recipients);
3023
-			}
3024
-		}
3025
-
3026
-		// Remove the PR_ICON_INDEX as it is not needed in the sent message.
3027
-		$newmessageprops[PR_ICON_INDEX] = null;
3028
-		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
3029
-
3030
-		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3031
-		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
3032
-		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
3033
-
3034
-		// Set updatecounter/AppointmentSequenceNumber
3035
-		// get the value of latest updatecounter for the whole series and use it
3036
-		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
3037
-
3038
-		$meetingTimeInfo = $this->getMeetingTimeInfo();
3039
-
3040
-		if ($meetingTimeInfo) {
3041
-			$newmessageprops[PR_BODY] = $meetingTimeInfo;
3042
-		}
3043
-
3044
-		// Send all recurrence info in mail, if this is a recurrence meeting.
3045
-		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
3046
-			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
3047
-				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
3048
-			}
3049
-			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
3050
-			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
3051
-			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
3052
-
3053
-			if ($recurObject) {
3054
-				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
3055
-			}
3056
-		}
3057
-
3058
-		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
3059
-			unset($newmessageprops[$this->proptags['counter_proposal']]);
3060
-		}
3061
-
3062
-		// Prefix the subject if needed
3063
-		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
3064
-			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3065
-		}
3066
-
3067
-		if (isset($newmessageprops[$this->proptags['categories']]) &&
3068
-			!empty($newmessageprops[$this->proptags['categories']])) {
3069
-			unset($newmessageprops[$this->proptags['categories']]);
3070
-		}
3071
-		mapi_setprops($new, $newmessageprops);
3072
-
3073
-		// Copy attachments
3074
-		$this->replaceAttachments($message, $new, $copyExceptions);
3075
-
3076
-		// Retrieve only those recipient who should receive this meeting request.
3077
-		$stripResourcesRestriction = [
3078
-			RES_AND,
3079
-			[
3080
-				[
3081
-					RES_BITMASK,
3082
-					[
3083
-						ULTYPE => BMR_EQZ,
3084
-						ULPROPTAG => PR_RECIPIENT_FLAGS,
3085
-						ULMASK => recipExceptionalDeleted,
3086
-					],
3087
-				],
3088
-				[
3089
-					RES_BITMASK,
3090
-					[
3091
-						ULTYPE => BMR_EQZ,
3092
-						ULPROPTAG => PR_RECIPIENT_FLAGS,
3093
-						ULMASK => recipOrganizer,
3094
-					],
3095
-				],
3096
-			],
3097
-		];
3098
-
3099
-		// In direct-booking mode, resources do not receive a meeting request
3100
-		if ($this->enableDirectBooking) {
3101
-			$stripResourcesRestriction[1][] =
3102
-									[
3103
-										RES_PROPERTY,
3104
-										[
3105
-											RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3106
-											ULPROPTAG => PR_RECIPIENT_TYPE,
3107
-											VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3108
-										],
3109
-									];
3110
-		}
3111
-
3112
-		// If no recipients were explicitly provided, we will send the update to all
3113
-		// recipients from the meeting.
3114
-		if ($modifiedRecips === false) {
3115
-			$recipienttable = mapi_message_getrecipienttable($message);
3116
-			$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3117
-
3118
-			if ($basedate && empty($modifiedRecips)) {
3119
-				// Retrieve full list
3120
-				$recipienttable = mapi_message_getrecipienttable($this->message);
3121
-				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
3122
-
3123
-				// Save recipients in exceptions
3124
-				mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
3125
-
3126
-				// Now retrieve only those recipient who should receive this meeting request.
3127
-				$modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3128
-			}
3129
-		}
3130
-
3131
-		// @TODO: handle nonAcceptingResources
3132
-		/*
2769
+        // Get resource recipients
2770
+        $getResourcesRestriction = [
2771
+            RES_AND,
2772
+            [
2773
+                [
2774
+                    RES_PROPERTY,
2775
+                    [
2776
+                        RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2777
+                        ULPROPTAG => PR_RECIPIENT_TYPE,
2778
+                        VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2779
+                    ],
2780
+                ], ],
2781
+        ];
2782
+        $recipienttable = mapi_message_getrecipienttable($message);
2783
+        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2784
+        if (!empty($resourceRecipients)) {
2785
+            // Set Tracking status of resource recipients to olResponseAccepted (3)
2786
+            for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2787
+                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2788
+                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2789
+            }
2790
+            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2791
+        }
2792
+
2793
+        // Publish updated free/busy information
2794
+        if (!$this->errorSetResource) {
2795
+            for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) {
2796
+                $storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]);
2797
+                if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) {
2798
+                    $start = time() - 7 * 24 * 60 * 60;
2799
+                    $range = strtotime("+6 month");
2800
+                    $range = $range - (7 * 24 * 60 * 60);
2801
+
2802
+                    $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2803
+                    $pub->publishFB($start, $range); // publish from one week ago, 6 months ahead
2804
+                }
2805
+            }
2806
+        }
2807
+
2808
+        return $resourceRecipData;
2809
+    }
2810
+
2811
+    /**
2812
+     * Function which save an exception into recurring item.
2813
+     *
2814
+     * @param resource $recurringItem  reference to MAPI_message of recurring item
2815
+     * @param resource $occurrenceItem reference to MAPI_message of occurrence
2816
+     * @param string   $basedate       basedate of occurrence
2817
+     * @param bool     $move           if true then occurrence item is deleted
2818
+     * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2819
+     * @param bool     $userAction     true if user has manually responded to meeting request
2820
+     * @param resource $store          user store
2821
+     * @param bool     $isDelegate     true if delegate is processing this meeting request
2822
+     */
2823
+    public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2824
+        $recurr = new Recurrence($store, $recurringItem);
2825
+
2826
+        // Copy properties from meeting request
2827
+        $exception_props = mapi_getprops($occurrenceItem);
2828
+
2829
+        // Copy recipients list
2830
+        $reciptable = mapi_message_getrecipienttable($occurrenceItem);
2831
+        // If delegate, then do not add the delegate in recipients
2832
+        if ($isDelegate) {
2833
+            $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2834
+            $res = [RES_PROPERTY, [
2835
+                RELOP => RELOP_NE,
2836
+                ULPROPTAG => PR_EMAIL_ADDRESS,
2837
+                VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]],
2838
+            ],
2839
+            ];
2840
+            $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2841
+        }
2842
+        else {
2843
+            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2844
+        }
2845
+
2846
+        // add owner to recipient table
2847
+        $this->addOrganizer($exception_props, $recips, true);
2848
+
2849
+        // add delegator to meetings
2850
+        if ($isDelegate) {
2851
+            $this->addDelegator($exception_props, $recips);
2852
+        }
2853
+
2854
+        $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2855
+        $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2856
+
2857
+        if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2858
+            if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2859
+                $exception_props[$this->proptags['busystatus']] = fbTentative;
2860
+            }
2861
+            else {
2862
+                $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2863
+            }
2864
+            // we already have intendedbusystatus value in $exception_props so no need to copy it
2865
+        }
2866
+        else {
2867
+            $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2868
+        }
2869
+
2870
+        if ($userAction) {
2871
+            $addrInfo = $this->getOwnerAddress($this->store);
2872
+
2873
+            // if user has responded then set replytime and name
2874
+            $exception_props[$this->proptags['replytime']] = time();
2875
+            if (!empty($addrInfo)) {
2876
+                $exception_props[$this->proptags['apptreplyname']] = $addrInfo[0];
2877
+            }
2878
+        }
2879
+
2880
+        if ($recurr->isException($basedate)) {
2881
+            $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2882
+        }
2883
+        else {
2884
+            $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2885
+        }
2886
+
2887
+        // Move the occurrenceItem to the waste basket
2888
+        if ($move) {
2889
+            $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2890
+            $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2891
+            mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2892
+        }
2893
+
2894
+        mapi_savechanges($recurringItem);
2895
+    }
2896
+
2897
+    /**
2898
+     * Function which merges an exception mapi message to recurring message.
2899
+     * This will be used when we receive recurring meeting request and we already have an exception message
2900
+     * of same meeting in calendar and we need to remove that exception message and add it to attachment table
2901
+     * of recurring meeting.
2902
+     *
2903
+     * @param resource $recurringItem  reference to MAPI_message of recurring item
2904
+     * @param resource $occurrenceItem reference to MAPI_message of occurrence
2905
+     * @param string   $basedate       basedate of occurrence
2906
+     * @param resource $store          user store
2907
+     */
2908
+    public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) {
2909
+        $recurr = new Recurrence($store, $recurringItem);
2910
+
2911
+        // Copy properties from meeting request
2912
+        $exception_props = mapi_getprops($occurrenceItem);
2913
+
2914
+        // Get recipient list from message and add it to exception attachment
2915
+        $reciptable = mapi_message_getrecipienttable($occurrenceItem);
2916
+        $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2917
+
2918
+        if ($recurr->isException($basedate)) {
2919
+            $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2920
+        }
2921
+        else {
2922
+            $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2923
+        }
2924
+
2925
+        // Move the occurrenceItem to the waste basket
2926
+        $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore());
2927
+        $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]);
2928
+        mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2929
+
2930
+        mapi_savechanges($recurringItem);
2931
+    }
2932
+
2933
+    /**
2934
+     * Function which submits meeting request based on arguments passed to it.
2935
+     *
2936
+     * @param resource $message        MAPI_message whose meeting request is to be send
2937
+     * @param bool     $cancel         if true send request, else send cancellation
2938
+     * @param string   $prefix         subject prefix
2939
+     * @param int      $basedate       basedate for an occurrence
2940
+     * @param object   $recurObject    recurrence object of mr
2941
+     * @param bool     $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2942
+     * @param mixed    $modifiedRecips
2943
+     * @param mixed    $deletedRecips
2944
+     */
2945
+    public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) {
2946
+        $newmessageprops = $messageprops = mapi_getprops($this->message);
2947
+        $new = $this->createOutgoingMessage();
2948
+
2949
+        // Copy the entire message into the new meeting request message
2950
+        if ($basedate) {
2951
+            // messageprops contains properties of whole recurring series
2952
+            // and newmessageprops contains properties of exception item
2953
+            $newmessageprops = mapi_getprops($message);
2954
+
2955
+            // Ensure that the correct basedate is set in the new message
2956
+            $newmessageprops[$this->proptags['basedate']] = $basedate;
2957
+
2958
+            // Set isRecurring to false, because this is an exception
2959
+            $newmessageprops[$this->proptags['recurring']] = false;
2960
+
2961
+            // set LID_IS_EXCEPTION to true
2962
+            $newmessageprops[$this->proptags['is_exception']] = true;
2963
+
2964
+            // Set to high importance
2965
+            if ($cancel) {
2966
+                $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2967
+            }
2968
+
2969
+            // Set startdate and enddate of exception
2970
+            if ($cancel && $recurObject) {
2971
+                $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2972
+                $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2973
+            }
2974
+
2975
+            // Set basedate in guid (0x3)
2976
+            $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2977
+            $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2978
+            $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2979
+
2980
+            // Get deleted recipiets from exception msg
2981
+            $restriction = [
2982
+                RES_AND,
2983
+                [
2984
+                    [
2985
+                        RES_BITMASK,
2986
+                        [
2987
+                            ULTYPE => BMR_NEZ,
2988
+                            ULPROPTAG => PR_RECIPIENT_FLAGS,
2989
+                            ULMASK => recipExceptionalDeleted,
2990
+                        ],
2991
+                    ],
2992
+                    [
2993
+                        RES_BITMASK,
2994
+                        [
2995
+                            ULTYPE => BMR_EQZ,
2996
+                            ULPROPTAG => PR_RECIPIENT_FLAGS,
2997
+                            ULMASK => recipOrganizer,
2998
+                        ],
2999
+                    ],
3000
+                ],
3001
+            ];
3002
+
3003
+            // In direct-booking mode, we don't need to send cancellations to resources
3004
+            if ($this->enableDirectBooking) {
3005
+                $restriction[1][] = [
3006
+                    RES_PROPERTY,
3007
+                    [
3008
+                        RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3009
+                        ULPROPTAG => PR_RECIPIENT_TYPE,
3010
+                        VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3011
+                    ],
3012
+                ];
3013
+            }
3014
+
3015
+            $recipienttable = mapi_message_getrecipienttable($message);
3016
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
3017
+
3018
+            if (!$deletedRecips) {
3019
+                $deletedRecips = array_merge([], $recipients);
3020
+            }
3021
+            else {
3022
+                $deletedRecips = array_merge($deletedRecips, $recipients);
3023
+            }
3024
+        }
3025
+
3026
+        // Remove the PR_ICON_INDEX as it is not needed in the sent message.
3027
+        $newmessageprops[PR_ICON_INDEX] = null;
3028
+        $newmessageprops[PR_RESPONSE_REQUESTED] = true;
3029
+
3030
+        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3031
+        $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
3032
+        $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
3033
+
3034
+        // Set updatecounter/AppointmentSequenceNumber
3035
+        // get the value of latest updatecounter for the whole series and use it
3036
+        $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
3037
+
3038
+        $meetingTimeInfo = $this->getMeetingTimeInfo();
3039
+
3040
+        if ($meetingTimeInfo) {
3041
+            $newmessageprops[PR_BODY] = $meetingTimeInfo;
3042
+        }
3043
+
3044
+        // Send all recurrence info in mail, if this is a recurrence meeting.
3045
+        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
3046
+            if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
3047
+                $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
3048
+            }
3049
+            $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
3050
+            $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
3051
+            $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
3052
+
3053
+            if ($recurObject) {
3054
+                $this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
3055
+            }
3056
+        }
3057
+
3058
+        if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
3059
+            unset($newmessageprops[$this->proptags['counter_proposal']]);
3060
+        }
3061
+
3062
+        // Prefix the subject if needed
3063
+        if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
3064
+            $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3065
+        }
3066
+
3067
+        if (isset($newmessageprops[$this->proptags['categories']]) &&
3068
+            !empty($newmessageprops[$this->proptags['categories']])) {
3069
+            unset($newmessageprops[$this->proptags['categories']]);
3070
+        }
3071
+        mapi_setprops($new, $newmessageprops);
3072
+
3073
+        // Copy attachments
3074
+        $this->replaceAttachments($message, $new, $copyExceptions);
3075
+
3076
+        // Retrieve only those recipient who should receive this meeting request.
3077
+        $stripResourcesRestriction = [
3078
+            RES_AND,
3079
+            [
3080
+                [
3081
+                    RES_BITMASK,
3082
+                    [
3083
+                        ULTYPE => BMR_EQZ,
3084
+                        ULPROPTAG => PR_RECIPIENT_FLAGS,
3085
+                        ULMASK => recipExceptionalDeleted,
3086
+                    ],
3087
+                ],
3088
+                [
3089
+                    RES_BITMASK,
3090
+                    [
3091
+                        ULTYPE => BMR_EQZ,
3092
+                        ULPROPTAG => PR_RECIPIENT_FLAGS,
3093
+                        ULMASK => recipOrganizer,
3094
+                    ],
3095
+                ],
3096
+            ],
3097
+        ];
3098
+
3099
+        // In direct-booking mode, resources do not receive a meeting request
3100
+        if ($this->enableDirectBooking) {
3101
+            $stripResourcesRestriction[1][] =
3102
+                                    [
3103
+                                        RES_PROPERTY,
3104
+                                        [
3105
+                                            RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3106
+                                            ULPROPTAG => PR_RECIPIENT_TYPE,
3107
+                                            VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3108
+                                        ],
3109
+                                    ];
3110
+        }
3111
+
3112
+        // If no recipients were explicitly provided, we will send the update to all
3113
+        // recipients from the meeting.
3114
+        if ($modifiedRecips === false) {
3115
+            $recipienttable = mapi_message_getrecipienttable($message);
3116
+            $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3117
+
3118
+            if ($basedate && empty($modifiedRecips)) {
3119
+                // Retrieve full list
3120
+                $recipienttable = mapi_message_getrecipienttable($this->message);
3121
+                $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops);
3122
+
3123
+                // Save recipients in exceptions
3124
+                mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips);
3125
+
3126
+                // Now retrieve only those recipient who should receive this meeting request.
3127
+                $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
3128
+            }
3129
+        }
3130
+
3131
+        // @TODO: handle nonAcceptingResources
3132
+        /*
3133 3133
 		 * Add resource recipients that did not automatically accept the meeting request.
3134 3134
 		 * (note: meaning that they did not decline the meeting request)
3135 3135
 		 */ /*
@@ -3137,803 +3137,803 @@  discard block
 block discarded – undo
3137 3137
 			$recipients[] = $this->nonAcceptingResources[$i];
3138 3138
 		}*/
3139 3139
 
3140
-		if (!empty($modifiedRecips)) {
3141
-			// Strip out the sender/'owner' recipient
3142
-			mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
3143
-
3144
-			// Set some properties that are different in the sent request than
3145
-			// in the item in our calendar
3146
-
3147
-			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
3148
-			// should always be fbTentative
3149
-			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
3150
-			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
3151
-			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
3152
-			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
3153
-			$newmessageprops[$this->proptags['owner_critical_change']] = time();
3154
-			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
3155
-
3156
-			if ($cancel) {
3157
-				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3158
-				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3159
-				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3160
-			}
3161
-			else {
3162
-				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3163
-				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3164
-			}
3165
-
3166
-			mapi_setprops($new, $newmessageprops);
3167
-			mapi_savechanges($new);
3168
-
3169
-			// Submit message to non-resource recipients
3170
-			mapi_message_submitmessage($new);
3171
-		}
3172
-
3173
-		// Search through the deleted recipients, and see if any of them is also
3174
-		// listed as a recipient to whom we have send an update. As we don't
3175
-		// want to send a cancellation message to recipients who will also receive
3176
-		// an meeting update, we have to filter those recipients out.
3177
-		if ($deletedRecips) {
3178
-			$tmp = [];
3179
-
3180
-			foreach ($deletedRecips as $delRecip) {
3181
-				$found = false;
3182
-
3183
-				// Search if the deleted recipient can be found inside
3184
-				// the updated recipients as well.
3185
-				foreach ($modifiedRecips as $recip) {
3186
-					if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
3187
-						$found = true;
3188
-
3189
-						break;
3190
-					}
3191
-				}
3192
-
3193
-				// If the recipient was not found, it truly is deleted,
3194
-				// and we can safely send a cancellation message
3195
-				if (!$found) {
3196
-					$tmp[] = $delRecip;
3197
-				}
3198
-			}
3199
-
3200
-			$deletedRecips = $tmp;
3201
-		}
3202
-
3203
-		// Send cancellation to deleted attendees
3204
-		if ($deletedRecips && !empty($deletedRecips)) {
3205
-			$new = $this->createOutgoingMessage();
3206
-
3207
-			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3208
-
3209
-			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3210
-			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3211
-			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3212
-			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;    // HIGH Importance
3213
-			if (isset($newmessageprops[PR_SUBJECT])) {
3214
-				$newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
3215
-			}
3216
-
3217
-			mapi_setprops($new, $newmessageprops);
3218
-			mapi_savechanges($new);
3219
-
3220
-			// Submit message to non-resource recipients
3221
-			mapi_message_submitmessage($new);
3222
-		}
3223
-
3224
-		// Set properties on meeting object in calendar
3225
-		// Set requestsent to 'true' (turns on 'tracking', etc)
3226
-		$props = [];
3227
-		$props[$this->proptags['meetingstatus']] = olMeeting;
3228
-		$props[$this->proptags['responsestatus']] = olResponseOrganized;
3229
-		// Only set the 'requestsent' property if it wasn't set previously yet,
3230
-		// this ensures we will not accidentally set it from true to false.
3231
-		if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3232
-			$props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3233
-		}
3234
-		$props[$this->proptags['attendee_critical_change']] = time();
3235
-		$props[$this->proptags['owner_critical_change']] = time();
3236
-		$props[$this->proptags['meetingtype']] = mtgRequest;
3237
-		// save the new updatecounter to exception/recurring series/normal meeting
3238
-		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3239
-
3240
-		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3241
-		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3242
-		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3243
-
3244
-		mapi_setprops($message, $props);
3245
-
3246
-		// saving of these properties on calendar item should be handled by caller function
3247
-		// based on sending meeting request was successful or not
3248
-	}
3249
-
3250
-	/**
3251
-	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
3252
-	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3253
-	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3254
-	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3255
-	 * also additionally we are sending these properties.
3256
-	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
3257
-	 *
3258
-	 * @param object $recurObject     instance of recurrence class for this message
3259
-	 * @param array  $messageprops    properties of meeting object that is going to be send
3260
-	 * @param array  $newmessageprops properties of meeting request/response that is going to be send
3261
-	 */
3262
-	public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
3263
-		if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3264
-			$startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3265
-			$endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3266
-
3267
-			$startDate = explode(':', $startDate);
3268
-			$endDate = explode(':', $endDate);
3269
-
3270
-			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3271
-			// RecurStartDate = year * 512 + month_number * 32 + day_number
3272
-			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3273
-			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3274
-			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3275
-
3276
-			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3277
-			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3278
-		}
3279
-	}
3280
-
3281
-	/**
3282
-	 * Function will create a new outgoing message that will be used to send meeting mail.
3283
-	 *
3284
-	 * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3285
-	 *                         then this would point to delegate store
3286
-	 *
3287
-	 * @return MAPIMessage outgoing mail that is created and can be used for sending it
3288
-	 */
3289
-	public function createOutgoingMessage($store = false) {
3290
-		// get logged in user's store that will be used to send mail, for delegate this will be
3291
-		// delegate store
3292
-		$userStore = $this->openDefaultStore();
3293
-
3294
-		$sentprops = [];
3295
-		$outbox = $this->openDefaultOutbox($userStore);
3296
-
3297
-		$outgoing = mapi_folder_createmessage($outbox);
3298
-
3299
-		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3300
-		if ($store !== false) {
3301
-			$storeProps = mapi_getprops($store, [PR_ENTRYID]);
3302
-			$userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]);
3303
-
3304
-			// @FIXME use entryid comparison functions here
3305
-			if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3306
-				// get the delegator properties and set it into outgoing mail
3307
-				$delegatorDetails = $this->getOwnerAddress($store, false);
3308
-
3309
-				if ($delegatorDetails) {
3310
-					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3311
-					$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3312
-					$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3313
-					$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3314
-					$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3315
-					$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3316
-				}
3317
-
3318
-				// get the delegate properties and set it into outgoing mail
3319
-				$delegateDetails = $this->getOwnerAddress($userStore, false);
3320
-
3321
-				if ($delegateDetails) {
3322
-					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3323
-					$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3324
-					$sentprops[PR_SENDER_NAME] = $ownername;
3325
-					$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3326
-					$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3327
-					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3328
-				}
3329
-			}
3330
-		}
3331
-		else {
3332
-			// normal user is sending mail, so both set of properties will be same
3333
-			$userDetails = $this->getOwnerAddress($userStore);
3334
-
3335
-			if ($userDetails) {
3336
-				list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3337
-				$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3338
-				$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3339
-				$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3340
-				$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3341
-				$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3342
-
3343
-				$sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3344
-				$sentprops[PR_SENDER_NAME] = $ownername;
3345
-				$sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3346
-				$sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3347
-				$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3348
-			}
3349
-		}
3350
-
3351
-		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
3352
-
3353
-		mapi_setprops($outgoing, $sentprops);
3354
-
3355
-		return $outgoing;
3356
-	}
3357
-
3358
-	/**
3359
-	 * Function which checks that meeting in attendee's calendar is already updated
3360
-	 * and we are checking an old meeting request. This function also will update property
3361
-	 * meetingtype to indicate that its out of date meeting request.
3362
-	 *
3363
-	 * @return bool true if meeting request is outofdate else false if it is new
3364
-	 */
3365
-	public function isMeetingOutOfDate() {
3366
-		$result = false;
3367
-
3368
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
3369
-
3370
-		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3371
-			return $result;
3372
-		}
3373
-
3374
-		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3375
-			return true;
3376
-		}
3377
-
3378
-		// get the basedate to check for exception
3379
-		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3380
-
3381
-		$calendarItem = $this->getCorrespondentCalendarItem(true);
3382
-
3383
-		// if basedate is provided and we could not find the item then it could be that we are checking
3384
-		// an exception so get the exception and check it
3385
-		if ($basedate && $calendarItem !== false) {
3386
-			$exception = $this->getExceptionItem($calendarItem, $basedate);
3387
-
3388
-			if ($exception !== false) {
3389
-				// we are able to find the exception compare with it
3390
-				$calendarItem = $exception;
3391
-			}
3392
-			// we are not able to find exception, could mean that a significant change has occurred on series
3393
-				// and it deleted all exceptions, so compare with series
3394
-				// $calendarItem already contains reference to series
3395
-		}
3396
-
3397
-		if ($calendarItem !== false) {
3398
-			$calendarItemProps = mapi_getprops($calendarItem, [
3399
-				$this->proptags['owner_critical_change'],
3400
-				$this->proptags['updatecounter'],
3401
-			]);
3402
-
3403
-			$updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3404
-
3405
-			$criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3406
-
3407
-			if ($updateCounter || $criticalChange) {
3408
-				// meeting request is out of date, set properties to indicate this
3409
-				mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
3410
-				mapi_savechanges($this->message);
3411
-
3412
-				$result = true;
3413
-			}
3414
-		}
3415
-
3416
-		return $result;
3417
-	}
3418
-
3419
-	/**
3420
-	 * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3421
-	 *
3422
-	 * @param Number $basedate basedate of the exception if we want to compare with exception
3423
-	 *
3424
-	 * @return bool true if meeting request is updated later
3425
-	 */
3426
-	public function isMeetingUpdated($basedate = false) {
3427
-		$result = false;
3428
-
3429
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
3430
-
3431
-		if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3432
-			return $result;
3433
-		}
3434
-
3435
-		$calendarItem = $this->getCorrespondentCalendarItem(true);
3436
-
3437
-		if ($calendarItem !== false) {
3438
-			// basedate is provided so open exception
3439
-			if ($basedate !== false) {
3440
-				$exception = $this->getExceptionItem($calendarItem, $basedate);
3441
-
3442
-				if ($exception !== false) {
3443
-					// we are able to find the exception compare with it
3444
-					$calendarItem = $exception;
3445
-				}
3446
-				// we are not able to find exception, could mean that a significant change has occurred on series
3447
-					// and it deleted all exceptions, so compare with series
3448
-					// $calendarItem already contains reference to series
3449
-			}
3450
-
3451
-			if ($calendarItem !== false) {
3452
-				$calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]);
3453
-
3454
-				/*
3140
+        if (!empty($modifiedRecips)) {
3141
+            // Strip out the sender/'owner' recipient
3142
+            mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips);
3143
+
3144
+            // Set some properties that are different in the sent request than
3145
+            // in the item in our calendar
3146
+
3147
+            // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
3148
+            // should always be fbTentative
3149
+            $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
3150
+            $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
3151
+            $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
3152
+            $newmessageprops[$this->proptags['attendee_critical_change']] = time();
3153
+            $newmessageprops[$this->proptags['owner_critical_change']] = time();
3154
+            $newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
3155
+
3156
+            if ($cancel) {
3157
+                $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3158
+                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3159
+                $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3160
+            }
3161
+            else {
3162
+                $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3163
+                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3164
+            }
3165
+
3166
+            mapi_setprops($new, $newmessageprops);
3167
+            mapi_savechanges($new);
3168
+
3169
+            // Submit message to non-resource recipients
3170
+            mapi_message_submitmessage($new);
3171
+        }
3172
+
3173
+        // Search through the deleted recipients, and see if any of them is also
3174
+        // listed as a recipient to whom we have send an update. As we don't
3175
+        // want to send a cancellation message to recipients who will also receive
3176
+        // an meeting update, we have to filter those recipients out.
3177
+        if ($deletedRecips) {
3178
+            $tmp = [];
3179
+
3180
+            foreach ($deletedRecips as $delRecip) {
3181
+                $found = false;
3182
+
3183
+                // Search if the deleted recipient can be found inside
3184
+                // the updated recipients as well.
3185
+                foreach ($modifiedRecips as $recip) {
3186
+                    if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) {
3187
+                        $found = true;
3188
+
3189
+                        break;
3190
+                    }
3191
+                }
3192
+
3193
+                // If the recipient was not found, it truly is deleted,
3194
+                // and we can safely send a cancellation message
3195
+                if (!$found) {
3196
+                    $tmp[] = $delRecip;
3197
+                }
3198
+            }
3199
+
3200
+            $deletedRecips = $tmp;
3201
+        }
3202
+
3203
+        // Send cancellation to deleted attendees
3204
+        if ($deletedRecips && !empty($deletedRecips)) {
3205
+            $new = $this->createOutgoingMessage();
3206
+
3207
+            mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
3208
+
3209
+            $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3210
+            $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3211
+            $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3212
+            $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;    // HIGH Importance
3213
+            if (isset($newmessageprops[PR_SUBJECT])) {
3214
+                $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
3215
+            }
3216
+
3217
+            mapi_setprops($new, $newmessageprops);
3218
+            mapi_savechanges($new);
3219
+
3220
+            // Submit message to non-resource recipients
3221
+            mapi_message_submitmessage($new);
3222
+        }
3223
+
3224
+        // Set properties on meeting object in calendar
3225
+        // Set requestsent to 'true' (turns on 'tracking', etc)
3226
+        $props = [];
3227
+        $props[$this->proptags['meetingstatus']] = olMeeting;
3228
+        $props[$this->proptags['responsestatus']] = olResponseOrganized;
3229
+        // Only set the 'requestsent' property if it wasn't set previously yet,
3230
+        // this ensures we will not accidentally set it from true to false.
3231
+        if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) {
3232
+            $props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource);
3233
+        }
3234
+        $props[$this->proptags['attendee_critical_change']] = time();
3235
+        $props[$this->proptags['owner_critical_change']] = time();
3236
+        $props[$this->proptags['meetingtype']] = mtgRequest;
3237
+        // save the new updatecounter to exception/recurring series/normal meeting
3238
+        $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
3239
+
3240
+        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
3241
+        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
3242
+        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
3243
+
3244
+        mapi_setprops($message, $props);
3245
+
3246
+        // saving of these properties on calendar item should be handled by caller function
3247
+        // based on sending meeting request was successful or not
3248
+    }
3249
+
3250
+    /**
3251
+     * OL2007 uses these 4 properties to specify occurrence that should be updated.
3252
+     * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
3253
+     * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
3254
+     * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
3255
+     * also additionally we are sending these properties.
3256
+     * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
3257
+     *
3258
+     * @param object $recurObject     instance of recurrence class for this message
3259
+     * @param array  $messageprops    properties of meeting object that is going to be send
3260
+     * @param array  $newmessageprops properties of meeting request/response that is going to be send
3261
+     */
3262
+    public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
3263
+        if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
3264
+            $startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
3265
+            $endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
3266
+
3267
+            $startDate = explode(':', $startDate);
3268
+            $endDate = explode(':', $endDate);
3269
+
3270
+            // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3271
+            // RecurStartDate = year * 512 + month_number * 32 + day_number
3272
+            $newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3273
+            // RecurStartTime = hour * 4096 + minutes * 64 + seconds
3274
+            $newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3275
+
3276
+            $newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3277
+            $newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3278
+        }
3279
+    }
3280
+
3281
+    /**
3282
+     * Function will create a new outgoing message that will be used to send meeting mail.
3283
+     *
3284
+     * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail
3285
+     *                         then this would point to delegate store
3286
+     *
3287
+     * @return MAPIMessage outgoing mail that is created and can be used for sending it
3288
+     */
3289
+    public function createOutgoingMessage($store = false) {
3290
+        // get logged in user's store that will be used to send mail, for delegate this will be
3291
+        // delegate store
3292
+        $userStore = $this->openDefaultStore();
3293
+
3294
+        $sentprops = [];
3295
+        $outbox = $this->openDefaultOutbox($userStore);
3296
+
3297
+        $outgoing = mapi_folder_createmessage($outbox);
3298
+
3299
+        // check if $store is set and it is not equal to $defaultStore (means its the delegation case)
3300
+        if ($store !== false) {
3301
+            $storeProps = mapi_getprops($store, [PR_ENTRYID]);
3302
+            $userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]);
3303
+
3304
+            // @FIXME use entryid comparison functions here
3305
+            if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) {
3306
+                // get the delegator properties and set it into outgoing mail
3307
+                $delegatorDetails = $this->getOwnerAddress($store, false);
3308
+
3309
+                if ($delegatorDetails) {
3310
+                    list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails;
3311
+                    $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3312
+                    $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3313
+                    $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3314
+                    $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3315
+                    $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3316
+                }
3317
+
3318
+                // get the delegate properties and set it into outgoing mail
3319
+                $delegateDetails = $this->getOwnerAddress($userStore, false);
3320
+
3321
+                if ($delegateDetails) {
3322
+                    list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails;
3323
+                    $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3324
+                    $sentprops[PR_SENDER_NAME] = $ownername;
3325
+                    $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3326
+                    $sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3327
+                    $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3328
+                }
3329
+            }
3330
+        }
3331
+        else {
3332
+            // normal user is sending mail, so both set of properties will be same
3333
+            $userDetails = $this->getOwnerAddress($userStore);
3334
+
3335
+            if ($userDetails) {
3336
+                list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails;
3337
+                $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
3338
+                $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
3339
+                $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
3340
+                $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
3341
+                $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
3342
+
3343
+                $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr;
3344
+                $sentprops[PR_SENDER_NAME] = $ownername;
3345
+                $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype;
3346
+                $sentprops[PR_SENDER_ENTRYID] = $ownerentryid;
3347
+                $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3348
+            }
3349
+        }
3350
+
3351
+        $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore);
3352
+
3353
+        mapi_setprops($outgoing, $sentprops);
3354
+
3355
+        return $outgoing;
3356
+    }
3357
+
3358
+    /**
3359
+     * Function which checks that meeting in attendee's calendar is already updated
3360
+     * and we are checking an old meeting request. This function also will update property
3361
+     * meetingtype to indicate that its out of date meeting request.
3362
+     *
3363
+     * @return bool true if meeting request is outofdate else false if it is new
3364
+     */
3365
+    public function isMeetingOutOfDate() {
3366
+        $result = false;
3367
+
3368
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
3369
+
3370
+        if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) {
3371
+            return $result;
3372
+        }
3373
+
3374
+        if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
3375
+            return true;
3376
+        }
3377
+
3378
+        // get the basedate to check for exception
3379
+        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3380
+
3381
+        $calendarItem = $this->getCorrespondentCalendarItem(true);
3382
+
3383
+        // if basedate is provided and we could not find the item then it could be that we are checking
3384
+        // an exception so get the exception and check it
3385
+        if ($basedate && $calendarItem !== false) {
3386
+            $exception = $this->getExceptionItem($calendarItem, $basedate);
3387
+
3388
+            if ($exception !== false) {
3389
+                // we are able to find the exception compare with it
3390
+                $calendarItem = $exception;
3391
+            }
3392
+            // we are not able to find exception, could mean that a significant change has occurred on series
3393
+                // and it deleted all exceptions, so compare with series
3394
+                // $calendarItem already contains reference to series
3395
+        }
3396
+
3397
+        if ($calendarItem !== false) {
3398
+            $calendarItemProps = mapi_getprops($calendarItem, [
3399
+                $this->proptags['owner_critical_change'],
3400
+                $this->proptags['updatecounter'],
3401
+            ]);
3402
+
3403
+            $updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]);
3404
+
3405
+            $criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]);
3406
+
3407
+            if ($updateCounter || $criticalChange) {
3408
+                // meeting request is out of date, set properties to indicate this
3409
+                mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
3410
+                mapi_savechanges($this->message);
3411
+
3412
+                $result = true;
3413
+            }
3414
+        }
3415
+
3416
+        return $result;
3417
+    }
3418
+
3419
+    /**
3420
+     * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar.
3421
+     *
3422
+     * @param Number $basedate basedate of the exception if we want to compare with exception
3423
+     *
3424
+     * @return bool true if meeting request is updated later
3425
+     */
3426
+    public function isMeetingUpdated($basedate = false) {
3427
+        $result = false;
3428
+
3429
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]);
3430
+
3431
+        if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) {
3432
+            return $result;
3433
+        }
3434
+
3435
+        $calendarItem = $this->getCorrespondentCalendarItem(true);
3436
+
3437
+        if ($calendarItem !== false) {
3438
+            // basedate is provided so open exception
3439
+            if ($basedate !== false) {
3440
+                $exception = $this->getExceptionItem($calendarItem, $basedate);
3441
+
3442
+                if ($exception !== false) {
3443
+                    // we are able to find the exception compare with it
3444
+                    $calendarItem = $exception;
3445
+                }
3446
+                // we are not able to find exception, could mean that a significant change has occurred on series
3447
+                    // and it deleted all exceptions, so compare with series
3448
+                    // $calendarItem already contains reference to series
3449
+            }
3450
+
3451
+            if ($calendarItem !== false) {
3452
+                $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]);
3453
+
3454
+                /*
3455 3455
 				 * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated)
3456 3456
 				 * if(message_counter >= appointment_counter) meeting is not updated, do normal processing
3457 3457
 				 */
3458
-				if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) {
3459
-					if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3460
-						$result = true;
3461
-					}
3462
-				}
3463
-			}
3464
-		}
3465
-
3466
-		return $result;
3467
-	}
3468
-
3469
-	/**
3470
-	 * Checks if there has been any significant changes on appointment/meeting item.
3471
-	 * Significant changes be:
3472
-	 * 1) startdate has been changed
3473
-	 * 2) duedate has been changed OR
3474
-	 * 3) recurrence pattern has been created, modified or removed.
3475
-	 *
3476
-	 * @param array oldProps old props before an update
3477
-	 * @param Number basedate basedate
3478
-	 * @param bool isRecurrenceChanged for change in recurrence pattern.
3479
-	 * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3480
-	 * @param mixed $oldProps
3481
-	 * @param mixed $basedate
3482
-	 * @param mixed $isRecurrenceChanged
3483
-	 */
3484
-	public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3485
-		$message = null;
3486
-		$attach = null;
3487
-
3488
-		// If basedate is specified then we need to open exception message to clear recipient responses
3489
-		if ($basedate) {
3490
-			$recurrence = new Recurrence($this->store, $this->message);
3491
-			if ($recurrence->isException($basedate)) {
3492
-				$attach = $recurrence->getExceptionAttachment($basedate);
3493
-				if ($attach) {
3494
-					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3495
-				}
3496
-			}
3497
-		}
3498
-		else {
3499
-			// use normal message or recurring series message
3500
-			$message = $this->message;
3501
-		}
3502
-
3503
-		if (!$message) {
3504
-			return;
3505
-		}
3506
-
3507
-		$newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3508
-
3509
-		// Check whether message is updated or not.
3510
-		if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3511
-			return;
3512
-		}
3513
-
3514
-		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3515
-			($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3516
-			$isRecurrenceChanged) {
3517
-			$this->clearRecipientResponse($message);
3518
-
3519
-			mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3520
-
3521
-			mapi_savechanges($message);
3522
-			if ($attach) { // Also save attachment Object.
3523
-				mapi_savechanges($attach);
3524
-			}
3525
-		}
3526
-	}
3527
-
3528
-	/**
3529
-	 * Clear responses of all attendees who have replied in past.
3530
-	 *
3531
-	 * @param MAPI_MESSAGE $message on which responses should be cleared
3532
-	 */
3533
-	public function clearRecipientResponse($message) {
3534
-		$recipTable = mapi_message_getrecipienttable($message);
3535
-		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3536
-
3537
-		foreach ($recipsRows as $recipient) {
3538
-			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3539
-				// Recipient is attendee, set the trackstatus to 'Not Responded'
3540
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3541
-			}
3542
-			else {
3543
-				// Recipient is organizer, this is not possible, but for safety
3544
-				// it is best to clear the trackstatus for him as well by setting
3545
-				// the trackstatus to 'Organized'.
3546
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3547
-			}
3548
-			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3549
-		}
3550
-	}
3551
-
3552
-	/**
3553
-	 * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3554
-	 * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3555
-	 * for exception then this function will return recurring series for that meeting request
3556
-	 * after that you need to use getExceptionItem function to get exception item that will be
3557
-	 * fetched from the attachment table of recurring series MAPIMessage.
3558
-	 *
3559
-	 * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3560
-	 *
3561
-	 * @return entryid or MAPIMessage resource of calendar item
3562
-	 */
3563
-	public function getCorrespondentCalendarItem($open = true) {
3564
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
3565
-
3566
-		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3567
-			// can work only with meeting requests/responses/cancellations
3568
-			return false;
3569
-		}
3570
-
3571
-		$globalId = $props[$this->proptags['goid']];
3572
-		$cleanGlobalId = $props[$this->proptags['goid2']];
3573
-
3574
-		// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3575
-		if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3576
-			$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3577
-
3578
-			$store = $delegatorStore['store'];
3579
-			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3580
-		}
3581
-		else {
3582
-			$store = $this->store;
3583
-			$calFolder = $this->openDefaultCalendar();
3584
-		}
3585
-
3586
-		$basedate = $this->getBasedateFromGlobalID($globalId);
3587
-
3588
-		/**
3589
-		 * First search for any appointments which correspond to the $globalId,
3590
-		 * this can be the entire series (if the Meeting Request refers to the
3591
-		 * entire series), or an particular Occurrence (if the meeting Request
3592
-		 * contains a basedate).
3593
-		 *
3594
-		 * If we cannot find a corresponding item, and the $globalId contains
3595
-		 * a $basedate, it might imply that a new exception will have to be
3596
-		 * created for a series which is present in the calendar, we can look
3597
-		 * that one up by searching for the $cleanGlobalId.
3598
-		 */
3599
-		$entryids = $this->findCalendarItems($globalId, $calFolder);
3600
-		if ($basedate && empty($entryids)) {
3601
-			$entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3602
-		}
3603
-
3604
-		// there should be only one item returned
3605
-		if (!empty($entryids) && count($entryids) === 1) {
3606
-			// return only entryid
3607
-			if ($open === false) {
3608
-				return $entryids[0];
3609
-			}
3610
-
3611
-			// open calendar item and return it
3612
-			return mapi_msgstore_openentry($store, $entryids[0]);
3613
-		}
3614
-
3615
-		// no items found in calendar
3616
-		return false;
3617
-	}
3618
-
3619
-	/**
3620
-	 * Function returns exception item based on the basedate passed.
3621
-	 *
3622
-	 * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar
3623
-	 * @param Unixtime    $basedate         basedate of exception that needs to be returned
3624
-	 * @param MAPIStore   $store            store that contains the recurring calendar item
3625
-	 *
3626
-	 * @return entryid or MAPIMessage resource of exception item
3627
-	 */
3628
-	public function getExceptionItem($recurringMessage, $basedate, $store = false) {
3629
-		$occurItem = false;
3630
-
3631
-		$props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
3632
-
3633
-		// check if the passed item is recurring series
3634
-		if ($props[$this->proptags['recurring']] !== false) {
3635
-			return false;
3636
-		}
3637
-
3638
-		if ($store === false) {
3639
-			// If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3640
-			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3641
-				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3642
-				$store = $delegatorStore['store'];
3643
-			}
3644
-			else {
3645
-				$store = $this->store;
3646
-			}
3647
-		}
3648
-
3649
-		$recurr = new Recurrence($store, $recurringMessage);
3650
-		$attach = $recurr->getExceptionAttachment($basedate);
3651
-		if ($attach) {
3652
-			$occurItem = mapi_attach_openobj($attach);
3653
-		}
3654
-
3655
-		return $occurItem;
3656
-	}
3657
-
3658
-	/**
3659
-	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3660
-	 *
3661
-	 * @param MAPIMessage $message   meeting request item that should be checked for conflicts in calendar
3662
-	 * @param MAPIStore   $userStore store containing calendar folder that will be used for confilict checking
3663
-	 * @param MAPIFolder  $calFolder calendar folder for conflict checking
3664
-	 *
3665
-	 * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3666
-	 * conflict of recurring meeting and false if meeting is not conflicting
3667
-	 * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message
3668
-	 */
3669
-	public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) {
3670
-		$returnValue = false;
3671
-		$noOfInstances = 0;
3672
-
3673
-		if ($message === false) {
3674
-			$message = $this->message;
3675
-		}
3676
-
3677
-		$messageProps = mapi_getprops(
3678
-			$message,
3679
-			[
3680
-				PR_MESSAGE_CLASS,
3681
-				$this->proptags['goid'],
3682
-				$this->proptags['goid2'],
3683
-				$this->proptags['startdate'],
3684
-				$this->proptags['duedate'],
3685
-				$this->proptags['recurring'],
3686
-				$this->proptags['clipstart'],
3687
-				$this->proptags['clipend'],
3688
-				PR_RCVD_REPRESENTING_ENTRYID,
3689
-				$this->proptags['basedate'],
3690
-				PR_RCVD_REPRESENTING_NAME,
3691
-			]
3692
-		);
3693
-
3694
-		if ($userStore === false) {
3695
-			$userStore = $this->store;
3696
-
3697
-			// check if delegate is processing the response
3698
-			if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3699
-				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3700
-
3701
-				$userStore = $delegatorStore['store'];
3702
-				$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3703
-			}
3704
-		}
3705
-
3706
-		if ($calFolder === false) {
3707
-			$calFolder = $this->openDefaultCalendar($userStore);
3708
-		}
3709
-
3710
-		if ($calFolder) {
3711
-			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3712
-			if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3713
-				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3714
-				$recurr = new Recurrence($userStore, $message);
3715
-				$items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3716
-
3717
-				foreach ($items as $item) {
3718
-					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3719
-					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3720
-
3721
-					foreach ($calendarItems as $calendarItem) {
3722
-						if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3723
-							/*
3458
+                if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) {
3459
+                    if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) {
3460
+                        $result = true;
3461
+                    }
3462
+                }
3463
+            }
3464
+        }
3465
+
3466
+        return $result;
3467
+    }
3468
+
3469
+    /**
3470
+     * Checks if there has been any significant changes on appointment/meeting item.
3471
+     * Significant changes be:
3472
+     * 1) startdate has been changed
3473
+     * 2) duedate has been changed OR
3474
+     * 3) recurrence pattern has been created, modified or removed.
3475
+     *
3476
+     * @param array oldProps old props before an update
3477
+     * @param Number basedate basedate
3478
+     * @param bool isRecurrenceChanged for change in recurrence pattern.
3479
+     * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3480
+     * @param mixed $oldProps
3481
+     * @param mixed $basedate
3482
+     * @param mixed $isRecurrenceChanged
3483
+     */
3484
+    public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3485
+        $message = null;
3486
+        $attach = null;
3487
+
3488
+        // If basedate is specified then we need to open exception message to clear recipient responses
3489
+        if ($basedate) {
3490
+            $recurrence = new Recurrence($this->store, $this->message);
3491
+            if ($recurrence->isException($basedate)) {
3492
+                $attach = $recurrence->getExceptionAttachment($basedate);
3493
+                if ($attach) {
3494
+                    $message = mapi_attach_openobj($attach, MAPI_MODIFY);
3495
+                }
3496
+            }
3497
+        }
3498
+        else {
3499
+            // use normal message or recurring series message
3500
+            $message = $this->message;
3501
+        }
3502
+
3503
+        if (!$message) {
3504
+            return;
3505
+        }
3506
+
3507
+        $newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3508
+
3509
+        // Check whether message is updated or not.
3510
+        if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3511
+            return;
3512
+        }
3513
+
3514
+        if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3515
+            ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3516
+            $isRecurrenceChanged) {
3517
+            $this->clearRecipientResponse($message);
3518
+
3519
+            mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3520
+
3521
+            mapi_savechanges($message);
3522
+            if ($attach) { // Also save attachment Object.
3523
+                mapi_savechanges($attach);
3524
+            }
3525
+        }
3526
+    }
3527
+
3528
+    /**
3529
+     * Clear responses of all attendees who have replied in past.
3530
+     *
3531
+     * @param MAPI_MESSAGE $message on which responses should be cleared
3532
+     */
3533
+    public function clearRecipientResponse($message) {
3534
+        $recipTable = mapi_message_getrecipienttable($message);
3535
+        $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3536
+
3537
+        foreach ($recipsRows as $recipient) {
3538
+            if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3539
+                // Recipient is attendee, set the trackstatus to 'Not Responded'
3540
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3541
+            }
3542
+            else {
3543
+                // Recipient is organizer, this is not possible, but for safety
3544
+                // it is best to clear the trackstatus for him as well by setting
3545
+                // the trackstatus to 'Organized'.
3546
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3547
+            }
3548
+            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3549
+        }
3550
+    }
3551
+
3552
+    /**
3553
+     * Function returns correspondent calendar item attached with the meeting request/response/cancellation.
3554
+     * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is
3555
+     * for exception then this function will return recurring series for that meeting request
3556
+     * after that you need to use getExceptionItem function to get exception item that will be
3557
+     * fetched from the attachment table of recurring series MAPIMessage.
3558
+     *
3559
+     * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true.
3560
+     *
3561
+     * @return entryid or MAPIMessage resource of calendar item
3562
+     */
3563
+    public function getCorrespondentCalendarItem($open = true) {
3564
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]);
3565
+
3566
+        if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
3567
+            // can work only with meeting requests/responses/cancellations
3568
+            return false;
3569
+        }
3570
+
3571
+        $globalId = $props[$this->proptags['goid']];
3572
+        $cleanGlobalId = $props[$this->proptags['goid2']];
3573
+
3574
+        // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3575
+        if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3576
+            $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3577
+
3578
+            $store = $delegatorStore['store'];
3579
+            $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3580
+        }
3581
+        else {
3582
+            $store = $this->store;
3583
+            $calFolder = $this->openDefaultCalendar();
3584
+        }
3585
+
3586
+        $basedate = $this->getBasedateFromGlobalID($globalId);
3587
+
3588
+        /**
3589
+         * First search for any appointments which correspond to the $globalId,
3590
+         * this can be the entire series (if the Meeting Request refers to the
3591
+         * entire series), or an particular Occurrence (if the meeting Request
3592
+         * contains a basedate).
3593
+         *
3594
+         * If we cannot find a corresponding item, and the $globalId contains
3595
+         * a $basedate, it might imply that a new exception will have to be
3596
+         * created for a series which is present in the calendar, we can look
3597
+         * that one up by searching for the $cleanGlobalId.
3598
+         */
3599
+        $entryids = $this->findCalendarItems($globalId, $calFolder);
3600
+        if ($basedate && empty($entryids)) {
3601
+            $entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true);
3602
+        }
3603
+
3604
+        // there should be only one item returned
3605
+        if (!empty($entryids) && count($entryids) === 1) {
3606
+            // return only entryid
3607
+            if ($open === false) {
3608
+                return $entryids[0];
3609
+            }
3610
+
3611
+            // open calendar item and return it
3612
+            return mapi_msgstore_openentry($store, $entryids[0]);
3613
+        }
3614
+
3615
+        // no items found in calendar
3616
+        return false;
3617
+    }
3618
+
3619
+    /**
3620
+     * Function returns exception item based on the basedate passed.
3621
+     *
3622
+     * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar
3623
+     * @param Unixtime    $basedate         basedate of exception that needs to be returned
3624
+     * @param MAPIStore   $store            store that contains the recurring calendar item
3625
+     *
3626
+     * @return entryid or MAPIMessage resource of exception item
3627
+     */
3628
+    public function getExceptionItem($recurringMessage, $basedate, $store = false) {
3629
+        $occurItem = false;
3630
+
3631
+        $props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]);
3632
+
3633
+        // check if the passed item is recurring series
3634
+        if ($props[$this->proptags['recurring']] !== false) {
3635
+            return false;
3636
+        }
3637
+
3638
+        if ($store === false) {
3639
+            // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar.
3640
+            if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3641
+                $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3642
+                $store = $delegatorStore['store'];
3643
+            }
3644
+            else {
3645
+                $store = $this->store;
3646
+            }
3647
+        }
3648
+
3649
+        $recurr = new Recurrence($store, $recurringMessage);
3650
+        $attach = $recurr->getExceptionAttachment($basedate);
3651
+        if ($attach) {
3652
+            $occurItem = mapi_attach_openobj($attach);
3653
+        }
3654
+
3655
+        return $occurItem;
3656
+    }
3657
+
3658
+    /**
3659
+     * Function which checks whether received meeting request is either conflicting with other appointments or not.
3660
+     *
3661
+     * @param MAPIMessage $message   meeting request item that should be checked for conflicts in calendar
3662
+     * @param MAPIStore   $userStore store containing calendar folder that will be used for confilict checking
3663
+     * @param MAPIFolder  $calFolder calendar folder for conflict checking
3664
+     *
3665
+     * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3666
+     * conflict of recurring meeting and false if meeting is not conflicting
3667
+     * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message
3668
+     */
3669
+    public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) {
3670
+        $returnValue = false;
3671
+        $noOfInstances = 0;
3672
+
3673
+        if ($message === false) {
3674
+            $message = $this->message;
3675
+        }
3676
+
3677
+        $messageProps = mapi_getprops(
3678
+            $message,
3679
+            [
3680
+                PR_MESSAGE_CLASS,
3681
+                $this->proptags['goid'],
3682
+                $this->proptags['goid2'],
3683
+                $this->proptags['startdate'],
3684
+                $this->proptags['duedate'],
3685
+                $this->proptags['recurring'],
3686
+                $this->proptags['clipstart'],
3687
+                $this->proptags['clipend'],
3688
+                PR_RCVD_REPRESENTING_ENTRYID,
3689
+                $this->proptags['basedate'],
3690
+                PR_RCVD_REPRESENTING_NAME,
3691
+            ]
3692
+        );
3693
+
3694
+        if ($userStore === false) {
3695
+            $userStore = $this->store;
3696
+
3697
+            // check if delegate is processing the response
3698
+            if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) {
3699
+                $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]);
3700
+
3701
+                $userStore = $delegatorStore['store'];
3702
+                $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3703
+            }
3704
+        }
3705
+
3706
+        if ($calFolder === false) {
3707
+            $calFolder = $this->openDefaultCalendar($userStore);
3708
+        }
3709
+
3710
+        if ($calFolder) {
3711
+            // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3712
+            if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) {
3713
+                // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3714
+                $recurr = new Recurrence($userStore, $message);
3715
+                $items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3716
+
3717
+                foreach ($items as $item) {
3718
+                    // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3719
+                    $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3720
+
3721
+                    foreach ($calendarItems as $calendarItem) {
3722
+                        if ($calendarItem[$this->proptags['busystatus']] !== fbFree) {
3723
+                            /*
3724 3724
 							 * Only meeting requests have globalID, normal appointments do not have globalID
3725 3725
 							 * so if any normal appointment if found then it is assumed to be conflict.
3726 3726
 							 */
3727
-							if (isset($calendarItem[$this->proptags['goid']])) {
3728
-								if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3729
-									++$noOfInstances;
3730
-
3731
-									break;
3732
-								}
3733
-							}
3734
-							else {
3735
-								++$noOfInstances;
3736
-
3737
-								break;
3738
-							}
3739
-						}
3740
-					}
3741
-				}
3742
-
3743
-				if ($noOfInstances > 0) {
3744
-					$returnValue = $noOfInstances;
3745
-				}
3746
-			}
3747
-			else {
3748
-				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3749
-				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3750
-
3751
-				if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3752
-					$basedate = $messageProps[$this->proptags['basedate']];
3753
-					// Get the goid2 from recurring MR which further used to
3754
-					// check the resource conflicts item.
3755
-					$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]);
3756
-					$messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3757
-					$messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3758
-				}
3759
-
3760
-				foreach ($items as $item) {
3761
-					if ($item[$this->proptags['busystatus']] !== fbFree) {
3762
-						if (isset($item[$this->proptags['goid']])) {
3763
-							if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) &&
3764
-								($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3765
-								$returnValue = true;
3766
-
3767
-								break;
3768
-							}
3769
-						}
3770
-						else {
3771
-							$returnValue = true;
3772
-
3773
-							break;
3774
-						}
3775
-					}
3776
-				}
3777
-			}
3778
-		}
3779
-
3780
-		return $returnValue;
3781
-	}
3782
-
3783
-	/**
3784
-	 *  Function which adds organizer to recipient list which is passed.
3785
-	 *  This function also checks if it has organizer.
3786
-	 *
3787
-	 * @param array $messageProps message properties
3788
-	 * @param array $recipients   recipients list of message
3789
-	 * @param bool  $isException  true if we are processing recipient of exception
3790
-	 */
3791
-	public function addDelegator($messageProps, &$recipients) {
3792
-		$hasDelegator = false;
3793
-		// Check if meeting already has an organizer.
3794
-		foreach ($recipients as $key => $recipient) {
3795
-			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
3796
-				$hasDelegator = true;
3797
-			}
3798
-		}
3799
-
3800
-		if (!$hasDelegator) {
3801
-			// Create delegator.
3802
-			$delegator = [];
3803
-			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3804
-			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3805
-			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3806
-			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3807
-			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3808
-			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3809
-			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3810
-			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3811
-			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3812
-
3813
-			// Add organizer to recipients list.
3814
-			array_unshift($recipients, $delegator);
3815
-		}
3816
-	}
3817
-
3818
-	/**
3819
-	 * Function will return delegator's store and calendar folder for processing meetings.
3820
-	 *
3821
-	 * @param string $receivedRepresentingEnryid  entryid of the delegator user
3822
-	 * @param array  $foldersToOpen               contains list of folder types that should be returned in result
3823
-	 * @param mixed  $receivedRepresentingEntryId
3824
-	 *
3825
-	 * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty
3826
-	 */
3827
-	public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) {
3828
-		$returnData = [];
3829
-
3830
-		$delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3831
-		$returnData['store'] = $delegatorStore;
3832
-
3833
-		if (!empty($foldersToOpen)) {
3834
-			for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) {
3835
-				$folderType = $foldersToOpen[$index];
3836
-
3837
-				// first try with default folders
3838
-				$folder = $this->openDefaultFolder($folderType, $delegatorStore);
3839
-
3840
-				// if folder not found then try with base folders
3841
-				if ($folder === false) {
3842
-					$folder = $this->openBaseFolder($folderType, $delegatorStore);
3843
-				}
3844
-
3845
-				if ($folder === false) {
3846
-					// we are still not able to get the folder so give up
3847
-					continue;
3848
-				}
3849
-
3850
-				$returnData[$folderType] = $folder;
3851
-			}
3852
-		}
3853
-
3854
-		return $returnData;
3855
-	}
3856
-
3857
-	/**
3858
-	 * Function returns extra info about meeting timing along with message body
3859
-	 * which will be included in body while sending meeting request/response.
3860
-	 *
3861
-	 * @return string $meetingTimeInfo info about meeting timing along with message body
3862
-	 */
3863
-	public function getMeetingTimeInfo() {
3864
-		return $this->meetingTimeInfo;
3865
-	}
3866
-
3867
-	/**
3868
-	 * Function sets extra info about meeting timing along with message body
3869
-	 * which will be included in body while sending meeting request/response.
3870
-	 *
3871
-	 * @param string $meetingTimeInfo info about meeting timing along with message body
3872
-	 */
3873
-	public function setMeetingTimeInfo($meetingTimeInfo) {
3874
-		$this->meetingTimeInfo = $meetingTimeInfo;
3875
-	}
3876
-
3877
-	/**
3878
-	 * Helper function which is use to get local categories of all occurrence.
3879
-	 *
3880
-	 * @param MAPIMessage $calendarItem meeting request item
3881
-	 * @param MAPIStore   $store        store containing calendar folder
3882
-	 * @param MAPIFolder  $calFolder    calendar folder
3883
-	 *
3884
-	 * @return array $localCategories which contain array of basedate along with categories
3885
-	 */
3886
-	public function getLocalCategories($calendarItem, $store, $calFolder) {
3887
-		$calendarItemProps = mapi_getprops($calendarItem);
3888
-		$recurrence = new Recurrence($store, $calendarItem);
3889
-
3890
-		// Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3891
-		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3892
-		$localCategories = [];
3893
-
3894
-		foreach ($items as $item) {
3895
-			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]);
3896
-			foreach ($recurrenceItems as $recurrenceItem) {
3897
-				// Check if occurrence is exception then get the local categories of that occurrence.
3898
-				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3899
-					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3900
-
3901
-					if ($exceptionAttach) {
3902
-						$exception = mapi_attach_openobj($exceptionAttach, 0);
3903
-						$exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]);
3904
-						if (isset($exceptionProps[$this->proptags['categories']])) {
3905
-							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3906
-						}
3907
-					}
3908
-				}
3909
-			}
3910
-		}
3911
-
3912
-		return $localCategories;
3913
-	}
3914
-
3915
-	/**
3916
-	 * Helper function which is use to apply local categories on respective occurrences.
3917
-	 *
3918
-	 * @param MAPIMessage $calendarItem    meeting request item
3919
-	 * @param MAPIStore   $store           store containing calendar folder
3920
-	 * @param array       $localCategories array contains basedate and array of categories
3921
-	 */
3922
-	public function applyLocalCategories($calendarItem, $store, $localCategories) {
3923
-		$calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
3924
-		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3925
-		$recurrence = new Recurrence($store, $message);
3926
-
3927
-		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3928
-		// Otherwise create new exception with categories.
3929
-		foreach ($localCategories as $key => $value) {
3930
-			if ($recurrence->isException($key)) {
3931
-				$recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3932
-			}
3933
-			else {
3934
-				$recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3935
-			}
3936
-			mapi_savechanges($message);
3937
-		}
3938
-	}
3727
+                            if (isset($calendarItem[$this->proptags['goid']])) {
3728
+                                if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) {
3729
+                                    ++$noOfInstances;
3730
+
3731
+                                    break;
3732
+                                }
3733
+                            }
3734
+                            else {
3735
+                                ++$noOfInstances;
3736
+
3737
+                                break;
3738
+                            }
3739
+                        }
3740
+                    }
3741
+                }
3742
+
3743
+                if ($noOfInstances > 0) {
3744
+                    $returnValue = $noOfInstances;
3745
+                }
3746
+            }
3747
+            else {
3748
+                // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3749
+                $items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3750
+
3751
+                if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) {
3752
+                    $basedate = $messageProps[$this->proptags['basedate']];
3753
+                    // Get the goid2 from recurring MR which further used to
3754
+                    // check the resource conflicts item.
3755
+                    $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]);
3756
+                    $messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate);
3757
+                    $messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
3758
+                }
3759
+
3760
+                foreach ($items as $item) {
3761
+                    if ($item[$this->proptags['busystatus']] !== fbFree) {
3762
+                        if (isset($item[$this->proptags['goid']])) {
3763
+                            if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) &&
3764
+                                ($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) {
3765
+                                $returnValue = true;
3766
+
3767
+                                break;
3768
+                            }
3769
+                        }
3770
+                        else {
3771
+                            $returnValue = true;
3772
+
3773
+                            break;
3774
+                        }
3775
+                    }
3776
+                }
3777
+            }
3778
+        }
3779
+
3780
+        return $returnValue;
3781
+    }
3782
+
3783
+    /**
3784
+     *  Function which adds organizer to recipient list which is passed.
3785
+     *  This function also checks if it has organizer.
3786
+     *
3787
+     * @param array $messageProps message properties
3788
+     * @param array $recipients   recipients list of message
3789
+     * @param bool  $isException  true if we are processing recipient of exception
3790
+     */
3791
+    public function addDelegator($messageProps, &$recipients) {
3792
+        $hasDelegator = false;
3793
+        // Check if meeting already has an organizer.
3794
+        foreach ($recipients as $key => $recipient) {
3795
+            if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
3796
+                $hasDelegator = true;
3797
+            }
3798
+        }
3799
+
3800
+        if (!$hasDelegator) {
3801
+            // Create delegator.
3802
+            $delegator = [];
3803
+            $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3804
+            $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3805
+            $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3806
+            $delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3807
+            $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3808
+            $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3809
+            $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3810
+            $delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3811
+            $delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3812
+
3813
+            // Add organizer to recipients list.
3814
+            array_unshift($recipients, $delegator);
3815
+        }
3816
+    }
3817
+
3818
+    /**
3819
+     * Function will return delegator's store and calendar folder for processing meetings.
3820
+     *
3821
+     * @param string $receivedRepresentingEnryid  entryid of the delegator user
3822
+     * @param array  $foldersToOpen               contains list of folder types that should be returned in result
3823
+     * @param mixed  $receivedRepresentingEntryId
3824
+     *
3825
+     * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty
3826
+     */
3827
+    public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) {
3828
+        $returnData = [];
3829
+
3830
+        $delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId);
3831
+        $returnData['store'] = $delegatorStore;
3832
+
3833
+        if (!empty($foldersToOpen)) {
3834
+            for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) {
3835
+                $folderType = $foldersToOpen[$index];
3836
+
3837
+                // first try with default folders
3838
+                $folder = $this->openDefaultFolder($folderType, $delegatorStore);
3839
+
3840
+                // if folder not found then try with base folders
3841
+                if ($folder === false) {
3842
+                    $folder = $this->openBaseFolder($folderType, $delegatorStore);
3843
+                }
3844
+
3845
+                if ($folder === false) {
3846
+                    // we are still not able to get the folder so give up
3847
+                    continue;
3848
+                }
3849
+
3850
+                $returnData[$folderType] = $folder;
3851
+            }
3852
+        }
3853
+
3854
+        return $returnData;
3855
+    }
3856
+
3857
+    /**
3858
+     * Function returns extra info about meeting timing along with message body
3859
+     * which will be included in body while sending meeting request/response.
3860
+     *
3861
+     * @return string $meetingTimeInfo info about meeting timing along with message body
3862
+     */
3863
+    public function getMeetingTimeInfo() {
3864
+        return $this->meetingTimeInfo;
3865
+    }
3866
+
3867
+    /**
3868
+     * Function sets extra info about meeting timing along with message body
3869
+     * which will be included in body while sending meeting request/response.
3870
+     *
3871
+     * @param string $meetingTimeInfo info about meeting timing along with message body
3872
+     */
3873
+    public function setMeetingTimeInfo($meetingTimeInfo) {
3874
+        $this->meetingTimeInfo = $meetingTimeInfo;
3875
+    }
3876
+
3877
+    /**
3878
+     * Helper function which is use to get local categories of all occurrence.
3879
+     *
3880
+     * @param MAPIMessage $calendarItem meeting request item
3881
+     * @param MAPIStore   $store        store containing calendar folder
3882
+     * @param MAPIFolder  $calFolder    calendar folder
3883
+     *
3884
+     * @return array $localCategories which contain array of basedate along with categories
3885
+     */
3886
+    public function getLocalCategories($calendarItem, $store, $calFolder) {
3887
+        $calendarItemProps = mapi_getprops($calendarItem);
3888
+        $recurrence = new Recurrence($store, $calendarItem);
3889
+
3890
+        // Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3891
+        $items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3892
+        $localCategories = [];
3893
+
3894
+        foreach ($items as $item) {
3895
+            $recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]);
3896
+            foreach ($recurrenceItems as $recurrenceItem) {
3897
+                // Check if occurrence is exception then get the local categories of that occurrence.
3898
+                if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3899
+                    $exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3900
+
3901
+                    if ($exceptionAttach) {
3902
+                        $exception = mapi_attach_openobj($exceptionAttach, 0);
3903
+                        $exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]);
3904
+                        if (isset($exceptionProps[$this->proptags['categories']])) {
3905
+                            $localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']];
3906
+                        }
3907
+                    }
3908
+                }
3909
+            }
3910
+        }
3911
+
3912
+        return $localCategories;
3913
+    }
3914
+
3915
+    /**
3916
+     * Helper function which is use to apply local categories on respective occurrences.
3917
+     *
3918
+     * @param MAPIMessage $calendarItem    meeting request item
3919
+     * @param MAPIStore   $store           store containing calendar folder
3920
+     * @param array       $localCategories array contains basedate and array of categories
3921
+     */
3922
+    public function applyLocalCategories($calendarItem, $store, $localCategories) {
3923
+        $calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
3924
+        $message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3925
+        $recurrence = new Recurrence($store, $message);
3926
+
3927
+        // Check for all occurrence if it is exception then modify the exception by setting up categories,
3928
+        // Otherwise create new exception with categories.
3929
+        foreach ($localCategories as $key => $value) {
3930
+            if ($recurrence->isException($key)) {
3931
+                $recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3932
+            }
3933
+            else {
3934
+                $recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3935
+            }
3936
+            mapi_savechanges($message);
3937
+        }
3938
+    }
3939 3939
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -152,15 +152,15 @@  discard block
 block discarded – undo
152 152
 		$properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501';
153 153
 		$properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503';
154 154
 		$properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200';
155
-		$properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201';                    // AppointmentSequenceNumber
156
-		$properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203';            // AppointmentLastSequence
155
+		$properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201'; // AppointmentSequenceNumber
156
+		$properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203'; // AppointmentLastSequence
157 157
 		$properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202';
158 158
 		$properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205';
159 159
 		$properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224';
160 160
 		$properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
161 161
 		$properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2';
162 162
 		$properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208';
163
-		$properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229';        // PidLidFInvited, MeetingRequestWasSent
163
+		$properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229'; // PidLidFInvited, MeetingRequestWasSent
164 164
 		$properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d';
165 165
 		$properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e';
166 166
 		$properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560';
@@ -169,11 +169,11 @@  discard block
 block discarded – undo
169 169
 		$properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223';
170 170
 		$properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235';
171 171
 		$properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236';
172
-		$properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD';                // StartRecurTime
173
-		$properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE';                // StartRecurTime
174
-		$properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF';                // EndRecurDate
175
-		$properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10';                // EndRecurTime
176
-		$properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA';                // LID_IS_EXCEPTION
172
+		$properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD'; // StartRecurTime
173
+		$properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE'; // StartRecurTime
174
+		$properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF'; // EndRecurDate
175
+		$properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10'; // EndRecurTime
176
+		$properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA'; // LID_IS_EXCEPTION
177 177
 		$properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230';
178 178
 		// Propose new time properties
179 179
 		$properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250';
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
 		$listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY;
546 546
 		$messageProps = mapi_getprops($this->message, $listProperties);
547 547
 
548
-		$goid = $messageProps[$this->proptags['goid']];    // GlobalID (0x3)
548
+		$goid = $messageProps[$this->proptags['goid']]; // GlobalID (0x3)
549 549
 		if (!isset($goid)) {
550 550
 			return;
551 551
 		}
@@ -1436,7 +1436,7 @@  discard block
 block discarded – undo
1436 1436
 		$props[$this->proptags['goid2']] = $goid;
1437 1437
 
1438 1438
 		if (!isset($props[$this->proptags['updatecounter']])) {
1439
-			$props[$this->proptags['updatecounter']] = 0;            // OL also starts sequence no with zero.
1439
+			$props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero.
1440 1440
 			$props[$this->proptags['last_updatecounter']] = 0;
1441 1441
 		}
1442 1442
 
@@ -2074,9 +2074,9 @@  discard block
 block discarded – undo
2074 2074
 			$subjectprefix = _('New Time Proposed');
2075 2075
 		}
2076 2076
 
2077
-		$props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT];
2077
+		$props[PR_SUBJECT] = $subjectprefix.': '.$messageprops[PR_SUBJECT];
2078 2078
 
2079
-		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix;
2079
+		$props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.'.$classpostfix;
2080 2080
 		if (isset($messageprops[PR_OWNER_APPT_ID])) {
2081 2081
 			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2082 2082
 		}
@@ -2285,12 +2285,12 @@  discard block
 block discarded – undo
2285 2285
 		$hasOrganizer = false;
2286 2286
 		// Check if meeting already has an organizer.
2287 2287
 		foreach ($recipients as $key => $recipient) {
2288
-			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2288
+			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable|recipOrganizer)) {
2289 2289
 				$hasOrganizer = true;
2290 2290
 			}
2291 2291
 			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2292 2292
 				// Recipients for an occurrence
2293
-				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2293
+				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalResponse;
2294 2294
 			}
2295 2295
 		}
2296 2296
 
@@ -2304,7 +2304,7 @@  discard block
 block discarded – undo
2304 2304
 			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2305 2305
 			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2306 2306
 			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2307
-			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2307
+			$organizer[PR_RECIPIENT_FLAGS] = recipSendable|recipOrganizer;
2308 2308
 			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2309 2309
 
2310 2310
 			// Add organizer to recipients list.
@@ -2362,7 +2362,7 @@  discard block
 block discarded – undo
2362 2362
 		$month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00';
2363 2363
 		$day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00';
2364 2364
 
2365
-		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2365
+		return hex2bin(strtoupper(substr($hexguid, 0, 32).$year.$month.$day.substr($hexguid, 40)));
2366 2366
 	}
2367 2367
 
2368 2368
 	/**
@@ -2400,7 +2400,7 @@  discard block
 block discarded – undo
2400 2400
 					continue;
2401 2401
 				}
2402 2402
 
2403
-				$attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]);
2403
+				$attachOld = mapi_message_openattach($copyFrom, (int)$attachProps[PR_ATTACH_NUM]);
2404 2404
 				$attachNewResourceMsg = mapi_message_createattach($copyTo);
2405 2405
 				mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0);
2406 2406
 				mapi_savechanges($attachNewResourceMsg);
@@ -2491,7 +2491,7 @@  discard block
 block discarded – undo
2491 2491
 				[
2492 2492
 					RES_PROPERTY,
2493 2493
 					[
2494
-						RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2494
+						RELOP => RELOP_EQ, // Equals recipient type 3: Resource
2495 2495
 						ULPROPTAG => PR_RECIPIENT_TYPE,
2496 2496
 						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2497 2497
 					],
@@ -2623,7 +2623,7 @@  discard block
 block discarded – undo
2623 2623
 
2624 2624
 				// Prefix the subject if needed
2625 2625
 				if ($prefix && isset($messageprops[PR_SUBJECT])) {
2626
-					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2626
+					$messageprops[PR_SUBJECT] = $prefix.$messageprops[PR_SUBJECT];
2627 2627
 				}
2628 2628
 
2629 2629
 				// Set status to cancelled if needed
@@ -2773,7 +2773,7 @@  discard block
 block discarded – undo
2773 2773
 				[
2774 2774
 					RES_PROPERTY,
2775 2775
 					[
2776
-						RELOP => RELOP_EQ,    // Equals recipient type 3: Resource
2776
+						RELOP => RELOP_EQ, // Equals recipient type 3: Resource
2777 2777
 						ULPROPTAG => PR_RECIPIENT_TYPE,
2778 2778
 						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2779 2779
 					],
@@ -3005,7 +3005,7 @@  discard block
 block discarded – undo
3005 3005
 				$restriction[1][] = [
3006 3006
 					RES_PROPERTY,
3007 3007
 					[
3008
-						RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3008
+						RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource)
3009 3009
 						ULPROPTAG => PR_RECIPIENT_TYPE,
3010 3010
 						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3011 3011
 					],
@@ -3061,7 +3061,7 @@  discard block
 block discarded – undo
3061 3061
 
3062 3062
 		// Prefix the subject if needed
3063 3063
 		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
3064
-			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
3064
+			$newmessageprops[PR_SUBJECT] = $prefix.$newmessageprops[PR_SUBJECT];
3065 3065
 		}
3066 3066
 
3067 3067
 		if (isset($newmessageprops[$this->proptags['categories']]) &&
@@ -3102,7 +3102,7 @@  discard block
 block discarded – undo
3102 3102
 									[
3103 3103
 										RES_PROPERTY,
3104 3104
 										[
3105
-											RELOP => RELOP_NE,    // Does not equal recipient type: MAPI_BCC (Resource)
3105
+											RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource)
3106 3106
 											ULPROPTAG => PR_RECIPIENT_TYPE,
3107 3107
 											VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
3108 3108
 										],
@@ -3209,9 +3209,9 @@  discard block
 block discarded – undo
3209 3209
 			$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3210 3210
 			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3211 3211
 			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3212
-			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;    // HIGH Importance
3212
+			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance
3213 3213
 			if (isset($newmessageprops[PR_SUBJECT])) {
3214
-				$newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT];
3214
+				$newmessageprops[PR_SUBJECT] = _('Canceled: ').$newmessageprops[PR_SUBJECT];
3215 3215
 			}
3216 3216
 
3217 3217
 			mapi_setprops($new, $newmessageprops);
@@ -3269,12 +3269,12 @@  discard block
 block discarded – undo
3269 3269
 
3270 3270
 			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
3271 3271
 			// RecurStartDate = year * 512 + month_number * 32 + day_number
3272
-			$newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
3272
+			$newmessageprops[$this->proptags['start_recur_date']] = (((int)$startDate[0]) * 512) + (((int)$startDate[1]) * 32) + ((int)$startDate[2]);
3273 3273
 			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
3274
-			$newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
3274
+			$newmessageprops[$this->proptags['start_recur_time']] = (((int)$startDate[3]) * 4096) + (((int)$startDate[4]) * 64) + ((int)$startDate[5]);
3275 3275
 
3276
-			$newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
3277
-			$newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
3276
+			$newmessageprops[$this->proptags['end_recur_date']] = (((int)$endDate[0]) * 512) + (((int)$endDate[1]) * 32) + ((int)$endDate[2]);
3277
+			$newmessageprops[$this->proptags['end_recur_time']] = (((int)$endDate[3]) * 4096) + (((int)$endDate[4]) * 64) + ((int)$endDate[5]);
3278 3278
 		}
3279 3279
 	}
3280 3280
 
Please login to merge, or discard this patch.
Braces   +69 added lines, -138 removed lines patch added patch discarded remove patch
@@ -326,8 +326,7 @@  discard block
 block discarded – undo
326 326
 
327 327
 			$userStore = $delegatorStore['store'];
328 328
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
329
-		}
330
-		else {
329
+		} else {
331 330
 			$userStore = $this->store;
332 331
 			$calFolder = $this->openDefaultCalendar();
333 332
 		}
@@ -396,8 +395,7 @@  discard block
 block discarded – undo
396 395
 			// Create/modify exception
397 396
 			if ($recurr->isException($basedate)) {
398 397
 				$recurr->modifyException($exception_props, $basedate);
399
-			}
400
-			else {
398
+			} else {
401 399
 				// When we are creating an exception we need copy recipients from main recurring item
402 400
 				$recipTable = mapi_message_getrecipienttable($calendarItem);
403 401
 				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
@@ -415,8 +413,7 @@  discard block
 block discarded – undo
415 413
 			if ($attach) {
416 414
 				$recurringItem = $calendarItem;
417 415
 				$calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY);
418
-			}
419
-			else {
416
+			} else {
420 417
 				return false;
421 418
 			}
422 419
 		}
@@ -499,8 +496,7 @@  discard block
 block discarded – undo
499 496
 			$props = [];
500 497
 			if ($messageprops[$this->proptags['counter_proposal']]) {
501 498
 				$props[$this->proptags['counter_proposal']] = true;
502
-			}
503
-			else {
499
+			} else {
504 500
 				$props[$this->proptags['counter_proposal']] = false;
505 501
 			}
506 502
 
@@ -556,8 +552,7 @@  discard block
 block discarded – undo
556 552
 
557 553
 			$store = $delegatorStore['store'];
558 554
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
559
-		}
560
-		else {
555
+		} else {
561 556
 			$store = $this->store;
562 557
 			$calFolder = $this->openDefaultCalendar();
563 558
 		}
@@ -585,13 +580,11 @@  discard block
 block discarded – undo
585 580
 
586 581
 					if ($recurr->isException($basedate)) {
587 582
 						$recurr->modifyException($messageProps, $basedate);
588
-					}
589
-					else {
583
+					} else {
590 584
 						$recurr->createException($messageProps, $basedate);
591 585
 					}
592 586
 				}
593
-			}
594
-			else {
587
+			} else {
595 588
 				// set the properties of the cancellation object
596 589
 				mapi_setprops($calendarItem, $messageProps);
597 590
 			}
@@ -645,8 +638,7 @@  discard block
 block discarded – undo
645 638
 
646 639
 			$store = $delegatorStore['store'];
647 640
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
648
-		}
649
-		else {
641
+		} else {
650 642
 			$calFolder = $this->openDefaultCalendar();
651 643
 			$store = $this->store;
652 644
 		}
@@ -707,8 +699,7 @@  discard block
 block discarded – undo
707 699
 		$senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID];
708 700
 		if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) {
709 701
 			$entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate);
710
-		}
711
-		else {
702
+		} else {
712 703
 			$entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate);
713 704
 		}
714 705
 
@@ -762,8 +753,7 @@  discard block
 block discarded – undo
762 753
 				if (!$calendarItem) {
763 754
 					// Recurring item not found, so create new meeting in Calendar
764 755
 					$calendarItem = mapi_folder_createmessage($calFolder);
765
-				}
766
-				else {
756
+				} else {
767 757
 					// we have found the main recurring item, check if this meeting request is already processed
768 758
 					if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
769 759
 						// only set required properties, other properties are already copied when processing this meeting request
@@ -784,8 +774,7 @@  discard block
 block discarded – undo
784 774
 					if (isset($props[$this->proptags['reminderminutes']])) {
785 775
 						$props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60);
786 776
 					}
787
-				}
788
-				else {
777
+				} else {
789 778
 					// only get required properties so we will not overwrite existing updated properties from calendar
790 779
 					$props = mapi_getprops($this->message, [PR_ENTRYID]);
791 780
 				}
@@ -806,13 +795,11 @@  discard block
 block discarded – undo
806 795
 				if (isset($props[$this->proptags['intendedbusystatus']])) {
807 796
 					if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
808 797
 						$props[$this->proptags['busystatus']] = fbTentative;
809
-					}
810
-					else {
798
+					} else {
811 799
 						$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
812 800
 					}
813 801
 					// we already have intendedbusystatus value in $props so no need to copy it
814
-				}
815
-				else {
802
+				} else {
816 803
 					$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
817 804
 				}
818 805
 
@@ -875,8 +862,7 @@  discard block
 block discarded – undo
875 862
 				}
876 863
 
877 864
 				$entryid = $props[PR_ENTRYID];
878
-			}
879
-			else {
865
+			} else {
880 866
 				/**
881 867
 				 * This meeting request is not recurring, so can be an exception or normal meeting.
882 868
 				 * If exception then find main recurring item and update exception
@@ -943,13 +929,11 @@  discard block
 block discarded – undo
943 929
 						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
944 930
 							if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
945 931
 								$calItemProps[$this->proptags['busystatus']] = fbTentative;
946
-							}
947
-							else {
932
+							} else {
948 933
 								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
949 934
 							}
950 935
 							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
951
-						}
952
-						else {
936
+						} else {
953 937
 							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
954 938
 						}
955 939
 
@@ -982,8 +966,7 @@  discard block
 block discarded – undo
982 966
 
983 967
 						$messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
984 968
 						$entryid = $messageprops[PR_ENTRYID];
985
-					}
986
-					else {
969
+					} else {
987 970
 						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
988 971
 						$new = mapi_folder_createmessage($calFolder);
989 972
 						$props = mapi_getprops($this->message);
@@ -1018,13 +1001,11 @@  discard block
 block discarded – undo
1018 1001
 						if (isset($props[$this->proptags['intendedbusystatus']])) {
1019 1002
 							if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
1020 1003
 								$props[$this->proptags['busystatus']] = fbTentative;
1021
-							}
1022
-							else {
1004
+							} else {
1023 1005
 								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
1024 1006
 							}
1025 1007
 							// we already have intendedbusystatus value in $props so no need to copy it
1026
-						}
1027
-						else {
1008
+						} else {
1028 1009
 							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1029 1010
 						}
1030 1011
 
@@ -1056,8 +1037,7 @@  discard block
 block discarded – undo
1056 1037
 							],
1057 1038
 							];
1058 1039
 							$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
1059
-						}
1060
-						else {
1040
+						} else {
1061 1041
 							$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
1062 1042
 						}
1063 1043
 						$this->addOrganizer($props, $recipients);
@@ -1069,8 +1049,7 @@  discard block
 block discarded – undo
1069 1049
 					}
1070 1050
 				}
1071 1051
 			}
1072
-		}
1073
-		else {
1052
+		} else {
1074 1053
 			// Here only properties are set on calendaritem, because user is responding from calendar.
1075 1054
 			$props = [];
1076 1055
 			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
@@ -1078,13 +1057,11 @@  discard block
 block discarded – undo
1078 1057
 			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1079 1058
 				if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1080 1059
 					$props[$this->proptags['busystatus']] = fbTentative;
1081
-				}
1082
-				else {
1060
+				} else {
1083 1061
 					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1084 1062
 				}
1085 1063
 				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1086
-			}
1087
-			else {
1064
+			} else {
1088 1065
 				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1089 1066
 			}
1090 1067
 
@@ -1107,8 +1084,7 @@  discard block
 block discarded – undo
1107 1084
 
1108 1085
 				if ($recurr->isException($basedate)) {
1109 1086
 					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1110
-				}
1111
-				else {
1087
+				} else {
1112 1088
 					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1113 1089
 					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1114 1090
 
@@ -1120,8 +1096,7 @@  discard block
 block discarded – undo
1120 1096
 
1121 1097
 					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1122 1098
 				}
1123
-			}
1124
-			else {
1099
+			} else {
1125 1100
 				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1126 1101
 			}
1127 1102
 			mapi_savechanges($this->message);
@@ -1162,8 +1137,7 @@  discard block
 block discarded – undo
1162 1137
 
1163 1138
 			$store = $delegatorStore['store'];
1164 1139
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1165
-		}
1166
-		else {
1140
+		} else {
1167 1141
 			$calFolder = $this->openDefaultCalendar();
1168 1142
 			$store = $this->store;
1169 1143
 		}
@@ -1255,8 +1229,7 @@  discard block
 block discarded – undo
1255 1229
 
1256 1230
 			$store = $delegatorStore['store'];
1257 1231
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
1258
-		}
1259
-		else {
1232
+		} else {
1260 1233
 			$store = $this->store;
1261 1234
 			$calFolder = $this->openDefaultCalendar();
1262 1235
 		}
@@ -1288,8 +1261,7 @@  discard block
 block discarded – undo
1288 1261
 						// exception found, remove it from calendar
1289 1262
 						$this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store);
1290 1263
 					}
1291
-				}
1292
-				else {
1264
+				} else {
1293 1265
 					// remove normal / recurring series from calendar
1294 1266
 					$entryids = mapi_getprops($calendarItem, [PR_ENTRYID]);
1295 1267
 
@@ -1304,14 +1276,12 @@  discard block
 block discarded – undo
1304 1276
 
1305 1277
 			// Move the cancellation mail to wastebasket
1306 1278
 			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1307
-		}
1308
-		else {
1279
+		} else {
1309 1280
 			// Here only properties are set on calendaritem, because user is responding from calendar.
1310 1281
 			if ($basedate) {
1311 1282
 				// remove the occurrence
1312 1283
 				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1313
-			}
1314
-			else {
1284
+			} else {
1315 1285
 				// remove normal/recurring meeting item.
1316 1286
 				// Move the message to the waste basket
1317 1287
 				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
@@ -1358,8 +1328,7 @@  discard block
 block discarded – undo
1358 1328
 
1359 1329
 			// save changes in the message
1360 1330
 			mapi_savechanges($this->message);
1361
-		}
1362
-		else {
1331
+		} else {
1363 1332
 			// cancellation of normal meeting request
1364 1333
 			// Send the cancellation
1365 1334
 			$this->updateMeetingRequest();
@@ -1499,8 +1468,7 @@  discard block
 block discarded – undo
1499 1468
 					}
1500 1469
 				}
1501 1470
 			}
1502
-		}
1503
-		else {
1471
+		} else {
1504 1472
 			// Basedate found, an exception is to be send
1505 1473
 			if ($basedate) {
1506 1474
 				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
@@ -1508,8 +1476,7 @@  discard block
 block discarded – undo
1508 1476
 				if ($cancel) {
1509 1477
 					// @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series
1510 1478
 					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1511
-				}
1512
-				else {
1479
+				} else {
1513 1480
 					$attach = $recurr->getExceptionAttachment($basedate);
1514 1481
 
1515 1482
 					if ($attach) {
@@ -1528,8 +1495,7 @@  discard block
 block discarded – undo
1528 1495
 						}
1529 1496
 					}
1530 1497
 				}
1531
-			}
1532
-			else {
1498
+			} else {
1533 1499
 				// This is normal meeting
1534 1500
 				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1535 1501
 
@@ -1606,8 +1572,7 @@  discard block
 block discarded – undo
1606 1572
 
1607 1573
 		if (!isset($messageprops[$this->proptags['goid']])) {
1608 1574
 			$this->setMeetingRequest($basedate);
1609
-		}
1610
-		else {
1575
+		} else {
1611 1576
 			$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1612 1577
 
1613 1578
 			// increment value of last_updatecounter, last_updatecounter will be common for recurring series
@@ -1626,8 +1591,7 @@  discard block
 block discarded – undo
1626 1591
 		if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) {
1627 1592
 			// we are checking with calendar item
1628 1593
 			$calendarItem = $this->message;
1629
-		}
1630
-		else {
1594
+		} else {
1631 1595
 			// we are checking with meeting request / response / cancellation mail
1632 1596
 			// get calendar items
1633 1597
 			$calendarItem = $this->getCorrespondentCalendarItem(true);
@@ -1762,8 +1726,7 @@  discard block
 block discarded – undo
1762 1726
 			if (isset($inboxprops[$prop])) {
1763 1727
 				return $inboxprops[$prop];
1764 1728
 			}
1765
-		}
1766
-		catch (MAPIException $e) {
1729
+		} catch (MAPIException $e) {
1767 1730
 			// public store doesn't support this method
1768 1731
 			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1769 1732
 				// don't propagate this error to parent handlers, if store doesn't support it
@@ -1853,8 +1816,7 @@  discard block
 block discarded – undo
1853 1816
 				if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) {
1854 1817
 					$accessToFolder = true;
1855 1818
 				}
1856
-			}
1857
-			catch (MAPIException $e) {
1819
+			} catch (MAPIException $e) {
1858 1820
 				// we don't have rights to open folder, so return false
1859 1821
 				if ($e->getCode() == MAPI_E_NO_ACCESS) {
1860 1822
 					return $accessToFolder;
@@ -1884,8 +1846,7 @@  discard block
 block discarded – undo
1884 1846
 				$delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]);
1885 1847
 
1886 1848
 				$store = $delegatorStore['store'];
1887
-			}
1888
-			else {
1849
+			} else {
1889 1850
 				$store = $this->store;
1890 1851
 			}
1891 1852
 		}
@@ -1895,8 +1856,7 @@  discard block
 block discarded – undo
1895 1856
 		if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
1896 1857
 			$entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1897 1858
 			$entryid = $entryid[PR_PARENT_ENTRYID];
1898
-		}
1899
-		else {
1859
+		} else {
1900 1860
 			$entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
1901 1861
 			if ($entryid === false) {
1902 1862
 				$entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store);
@@ -1922,8 +1882,7 @@  discard block
 block discarded – undo
1922 1882
 
1923 1883
 		try {
1924 1884
 			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
1925
-		}
1926
-		catch (MAPIException $e) {
1885
+		} catch (MAPIException $e) {
1927 1886
 			return;
1928 1887
 		}
1929 1888
 
@@ -1991,8 +1950,7 @@  discard block
 block discarded – undo
1991 1950
 				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1992 1951
 				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1993 1952
 				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1994
-			}
1995
-			else {
1953
+			} else {
1996 1954
 				// Exceptions is deleted.
1997 1955
 				// Update $messageprops with timings of occurrence
1998 1956
 				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
@@ -2004,8 +1962,7 @@  discard block
 block discarded – undo
2004 1962
 
2005 1963
 			$props[$this->proptags['recurring']] = false;
2006 1964
 			$props[$this->proptags['is_exception']] = true;
2007
-		}
2008
-		else {
1965
+		} else {
2009 1966
 			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
2010 1967
 			// Send all recurrence info in response, if this is a recurrence meeting.
2011 1968
 			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
@@ -2287,8 +2244,7 @@  discard block
 block discarded – undo
2287 2244
 		foreach ($recipients as $key => $recipient) {
2288 2245
 			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2289 2246
 				$hasOrganizer = true;
2290
-			}
2291
-			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2247
+			} elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2292 2248
 				// Recipients for an occurrence
2293 2249
 				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2294 2250
 			}
@@ -2429,8 +2385,7 @@  discard block
 block discarded – undo
2429 2385
 			],
2430 2386
 			];
2431 2387
 			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res);
2432
-		}
2433
-		else {
2388
+		} else {
2434 2389
 			$recipients = mapi_table_queryallrows($recipientTable, $this->recipprops);
2435 2390
 		}
2436 2391
 
@@ -2526,8 +2481,7 @@  discard block
 block discarded – undo
2526 2481
 				if ($accessToFolder) {
2527 2482
 					$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2528 2483
 				}
2529
-			}
2530
-			catch (MAPIException $e) {
2484
+			} catch (MAPIException $e) {
2531 2485
 				$e->setHandled();
2532 2486
 				$this->errorSetResource = 1; // No access
2533 2487
 			}
@@ -2554,8 +2508,7 @@  discard block
 block discarded – undo
2554 2508
 						 */
2555 2509
 						// $errorSetResource = 2;
2556 2510
 						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2557
-					}
2558
-					else {
2511
+					} else {
2559 2512
 						if ($declineRecurringMeetingRequests && !$cancel) {
2560 2513
 							// Check if appointment is recurring
2561 2514
 							if ($messageprops[$this->proptags['recurring']]) {
@@ -2616,8 +2569,7 @@  discard block
 block discarded – undo
2616 2569
 					if (!$newResourceMsg) {
2617 2570
 						$newResourceMsg = mapi_folder_createmessage($calFolder);
2618 2571
 					}
2619
-				}
2620
-				else {
2572
+				} else {
2621 2573
 					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2622 2574
 				}
2623 2575
 
@@ -2631,8 +2583,7 @@  discard block
 block discarded – undo
2631 2583
 				if ($cancel) {
2632 2584
 					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2633 2585
 					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2634
-				}
2635
-				else {
2586
+				} else {
2636 2587
 					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2637 2588
 				}
2638 2589
 				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
@@ -2676,8 +2627,7 @@  discard block
 block discarded – undo
2676 2627
 						$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2677 2628
 						$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2678 2629
 					}
2679
-				}
2680
-				else {
2630
+				} else {
2681 2631
 					// get organizer information
2682 2632
 					$addrinfo = $this->getOwnerAddress($this->store);
2683 2633
 
@@ -2713,12 +2663,10 @@  discard block
 block discarded – undo
2713 2663
 					// Update occurrence
2714 2664
 					if ($recurr->isException($basedate)) {
2715 2665
 						$recurr->modifyException($messageprops, $basedate, $recips);
2716
-					}
2717
-					else {
2666
+					} else {
2718 2667
 						$recurr->createException($messageprops, $basedate, false, $recips);
2719 2668
 					}
2720
-				}
2721
-				else {
2669
+				} else {
2722 2670
 					mapi_setprops($newResourceMsg, $messageprops);
2723 2671
 
2724 2672
 					// Copy attachments
@@ -2742,8 +2690,7 @@  discard block
 block discarded – undo
2742 2690
 					'msg' => $newResourceMsg,
2743 2691
 				];
2744 2692
 				$this->includesResources = true;
2745
-			}
2746
-			else {
2693
+			} else {
2747 2694
 				/*
2748 2695
 				 * If no other errors occurred and you have no access to the
2749 2696
 				 * folder of the resource, throw an error=1.
@@ -2838,8 +2785,7 @@  discard block
 block discarded – undo
2838 2785
 			],
2839 2786
 			];
2840 2787
 			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2841
-		}
2842
-		else {
2788
+		} else {
2843 2789
 			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2844 2790
 		}
2845 2791
 
@@ -2857,13 +2803,11 @@  discard block
 block discarded – undo
2857 2803
 		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2858 2804
 			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2859 2805
 				$exception_props[$this->proptags['busystatus']] = fbTentative;
2860
-			}
2861
-			else {
2806
+			} else {
2862 2807
 				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2863 2808
 			}
2864 2809
 			// we already have intendedbusystatus value in $exception_props so no need to copy it
2865
-		}
2866
-		else {
2810
+		} else {
2867 2811
 			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2868 2812
 		}
2869 2813
 
@@ -2879,8 +2823,7 @@  discard block
 block discarded – undo
2879 2823
 
2880 2824
 		if ($recurr->isException($basedate)) {
2881 2825
 			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2882
-		}
2883
-		else {
2826
+		} else {
2884 2827
 			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2885 2828
 		}
2886 2829
 
@@ -2917,8 +2860,7 @@  discard block
 block discarded – undo
2917 2860
 
2918 2861
 		if ($recurr->isException($basedate)) {
2919 2862
 			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2920
-		}
2921
-		else {
2863
+		} else {
2922 2864
 			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2923 2865
 		}
2924 2866
 
@@ -3017,8 +2959,7 @@  discard block
 block discarded – undo
3017 2959
 
3018 2960
 			if (!$deletedRecips) {
3019 2961
 				$deletedRecips = array_merge([], $recipients);
3020
-			}
3021
-			else {
2962
+			} else {
3022 2963
 				$deletedRecips = array_merge($deletedRecips, $recipients);
3023 2964
 			}
3024 2965
 		}
@@ -3157,8 +3098,7 @@  discard block
 block discarded – undo
3157 3098
 				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled';
3158 3099
 				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
3159 3100
 				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
3160
-			}
3161
-			else {
3101
+			} else {
3162 3102
 				$newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request';
3163 3103
 				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
3164 3104
 			}
@@ -3327,8 +3267,7 @@  discard block
 block discarded – undo
3327 3267
 					$sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey;
3328 3268
 				}
3329 3269
 			}
3330
-		}
3331
-		else {
3270
+		} else {
3332 3271
 			// normal user is sending mail, so both set of properties will be same
3333 3272
 			$userDetails = $this->getOwnerAddress($userStore);
3334 3273
 
@@ -3494,8 +3433,7 @@  discard block
 block discarded – undo
3494 3433
 					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3495 3434
 				}
3496 3435
 			}
3497
-		}
3498
-		else {
3436
+		} else {
3499 3437
 			// use normal message or recurring series message
3500 3438
 			$message = $this->message;
3501 3439
 		}
@@ -3538,8 +3476,7 @@  discard block
 block discarded – undo
3538 3476
 			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3539 3477
 				// Recipient is attendee, set the trackstatus to 'Not Responded'
3540 3478
 				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3541
-			}
3542
-			else {
3479
+			} else {
3543 3480
 				// Recipient is organizer, this is not possible, but for safety
3544 3481
 				// it is best to clear the trackstatus for him as well by setting
3545 3482
 				// the trackstatus to 'Organized'.
@@ -3577,8 +3514,7 @@  discard block
 block discarded – undo
3577 3514
 
3578 3515
 			$store = $delegatorStore['store'];
3579 3516
 			$calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID];
3580
-		}
3581
-		else {
3517
+		} else {
3582 3518
 			$store = $this->store;
3583 3519
 			$calFolder = $this->openDefaultCalendar();
3584 3520
 		}
@@ -3640,8 +3576,7 @@  discard block
 block discarded – undo
3640 3576
 			if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) {
3641 3577
 				$delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]);
3642 3578
 				$store = $delegatorStore['store'];
3643
-			}
3644
-			else {
3579
+			} else {
3645 3580
 				$store = $this->store;
3646 3581
 			}
3647 3582
 		}
@@ -3730,8 +3665,7 @@  discard block
 block discarded – undo
3730 3665
 
3731 3666
 									break;
3732 3667
 								}
3733
-							}
3734
-							else {
3668
+							} else {
3735 3669
 								++$noOfInstances;
3736 3670
 
3737 3671
 								break;
@@ -3743,8 +3677,7 @@  discard block
 block discarded – undo
3743 3677
 				if ($noOfInstances > 0) {
3744 3678
 					$returnValue = $noOfInstances;
3745 3679
 				}
3746
-			}
3747
-			else {
3680
+			} else {
3748 3681
 				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3749 3682
 				$items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]);
3750 3683
 
@@ -3766,8 +3699,7 @@  discard block
 block discarded – undo
3766 3699
 
3767 3700
 								break;
3768 3701
 							}
3769
-						}
3770
-						else {
3702
+						} else {
3771 3703
 							$returnValue = true;
3772 3704
 
3773 3705
 							break;
@@ -3929,8 +3861,7 @@  discard block
 block discarded – undo
3929 3861
 		foreach ($localCategories as $key => $value) {
3930 3862
 			if ($recurrence->isException($key)) {
3931 3863
 				$recurrence->modifyException([$this->proptags['categories'] => $value], $key);
3932
-			}
3933
-			else {
3864
+			} else {
3934 3865
 				$recurrence->createException([$this->proptags['categories'] => $value], $key, false);
3935 3866
 			}
3936 3867
 			mapi_savechanges($message);
Please login to merge, or discard this patch.
mapi/class.taskrecurrence.php 3 patches
Indentation   +405 added lines, -405 removed lines patch added patch discarded remove patch
@@ -5,411 +5,411 @@
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6 6
  */
7 7
 
8
-	class TaskRecurrence extends BaseRecurrence {
9
-		/**
10
-		 * Timezone info which is always false for task.
11
-		 */
12
-		public $tz = false;
13
-
14
-		private $action;
15
-
16
-		public function __construct($store, $message) {
17
-			$this->store = $store;
18
-			$this->message = $message;
19
-
20
-			$properties = [];
21
-			$properties["entryid"] = PR_ENTRYID;
22
-			$properties["parent_entryid"] = PR_PARENT_ENTRYID;
23
-			$properties["icon_index"] = PR_ICON_INDEX;
24
-			$properties["message_class"] = PR_MESSAGE_CLASS;
25
-			$properties["message_flags"] = PR_MESSAGE_FLAGS;
26
-			$properties["subject"] = PR_SUBJECT;
27
-			$properties["importance"] = PR_IMPORTANCE;
28
-			$properties["sensitivity"] = PR_SENSITIVITY;
29
-			$properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
30
-			$properties["status"] = "PT_LONG:PSETID_Task:0x8101";
31
-			$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
32
-			$properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
33
-			$properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
34
-			$properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
35
-			$properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
36
-			$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
37
-			$properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
38
-			$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
39
-			$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
40
-			$properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
41
-			$properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
42
-			$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
43
-			$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
44
-
45
-			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
46
-			$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
47
-			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
48
-
49
-			$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
50
-			$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
51
-			$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
52
-			$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
53
-
54
-			$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
55
-			$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
56
-			$properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
57
-			$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
58
-			$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
59
-			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
60
-			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
61
-
62
-			$this->proptags = getPropIdsFromStrings($store, $properties);
63
-
64
-			parent::__construct($store, $message, $properties);
65
-		}
66
-
67
-		/**
68
-		 * Function which saves recurrence and also regenerates task if necessary.
69
-		 *
70
-		 *@param array $recur new recurrence properties
71
-		 *
72
-		 *@return array of properties of regenerated task else false
73
-		 */
74
-		public function setRecurrence(&$recur) {
75
-			$this->recur = $recur;
76
-			$this->action = &$recur;
77
-
78
-			if (!isset($this->recur["changed_occurrences"])) {
79
-				$this->recur["changed_occurrences"] = [];
80
-			}
81
-
82
-			if (!isset($this->recur["deleted_occurrences"])) {
83
-				$this->recur["deleted_occurrences"] = [];
84
-			}
85
-
86
-			if (!isset($this->recur['startocc'])) {
87
-				$this->recur['startocc'] = 0;
88
-			}
89
-			if (!isset($this->recur['endocc'])) {
90
-				$this->recur['endocc'] = 0;
91
-			}
92
-
93
-			// Save recurrence because we need proper startrecurrdate and endrecurrdate
94
-			$this->saveRecurrence();
95
-
96
-			// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
97
-			$msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
98
-			$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
99
-			foreach ($recurring_data as $key => $value) {
100
-				$this->recur[$key] = $value;
101
-			}
102
-
103
-			$this->setFirstOccurrence();
104
-
105
-			// Let's see if next occurrence has to be generated
106
-			return $this->moveToNextOccurrence();
107
-		}
108
-
109
-		/**
110
-		 * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
111
-		 */
112
-		public function setFirstOccurrence() {
113
-			// Check if it is already the first occurrence
114
-			if ($this->action['start'] == $this->recur["start"]) {
115
-				return;
116
-			}
117
-			$items = $this->getNextOccurrence();
118
-
119
-			$props = [];
120
-			$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
121
-			$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
122
-
123
-			$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
124
-			$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
125
-
126
-			mapi_setprops($this->message, $props);
127
-		}
128
-
129
-		/**
130
-		 * Function which creates new task as current occurrence and moves the
131
-		 * existing task to next occurrence.
132
-		 *
133
-		 *@param array $recur $action from client
134
-		 *
135
-		 *@return bool if moving to next occurrence succeed then it returns
136
-		 *        properties of either newly created task or existing task ELSE
137
-		 *        false because that was last occurrence
138
-		 */
139
-		public function moveToNextOccurrence() {
140
-			$result = false;
141
-			/*
8
+    class TaskRecurrence extends BaseRecurrence {
9
+        /**
10
+         * Timezone info which is always false for task.
11
+         */
12
+        public $tz = false;
13
+
14
+        private $action;
15
+
16
+        public function __construct($store, $message) {
17
+            $this->store = $store;
18
+            $this->message = $message;
19
+
20
+            $properties = [];
21
+            $properties["entryid"] = PR_ENTRYID;
22
+            $properties["parent_entryid"] = PR_PARENT_ENTRYID;
23
+            $properties["icon_index"] = PR_ICON_INDEX;
24
+            $properties["message_class"] = PR_MESSAGE_CLASS;
25
+            $properties["message_flags"] = PR_MESSAGE_FLAGS;
26
+            $properties["subject"] = PR_SUBJECT;
27
+            $properties["importance"] = PR_IMPORTANCE;
28
+            $properties["sensitivity"] = PR_SENSITIVITY;
29
+            $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
30
+            $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
31
+            $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
32
+            $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
33
+            $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
34
+            $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
35
+            $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
36
+            $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
37
+            $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
38
+            $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
39
+            $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
40
+            $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
41
+            $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
42
+            $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
43
+            $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
44
+
45
+            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
46
+            $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
47
+            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
48
+
49
+            $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
50
+            $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
51
+            $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
52
+            $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
53
+
54
+            $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
55
+            $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
56
+            $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
57
+            $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
58
+            $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
59
+            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
60
+            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
61
+
62
+            $this->proptags = getPropIdsFromStrings($store, $properties);
63
+
64
+            parent::__construct($store, $message, $properties);
65
+        }
66
+
67
+        /**
68
+         * Function which saves recurrence and also regenerates task if necessary.
69
+         *
70
+         *@param array $recur new recurrence properties
71
+         *
72
+         *@return array of properties of regenerated task else false
73
+         */
74
+        public function setRecurrence(&$recur) {
75
+            $this->recur = $recur;
76
+            $this->action = &$recur;
77
+
78
+            if (!isset($this->recur["changed_occurrences"])) {
79
+                $this->recur["changed_occurrences"] = [];
80
+            }
81
+
82
+            if (!isset($this->recur["deleted_occurrences"])) {
83
+                $this->recur["deleted_occurrences"] = [];
84
+            }
85
+
86
+            if (!isset($this->recur['startocc'])) {
87
+                $this->recur['startocc'] = 0;
88
+            }
89
+            if (!isset($this->recur['endocc'])) {
90
+                $this->recur['endocc'] = 0;
91
+            }
92
+
93
+            // Save recurrence because we need proper startrecurrdate and endrecurrdate
94
+            $this->saveRecurrence();
95
+
96
+            // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
97
+            $msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
98
+            $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
99
+            foreach ($recurring_data as $key => $value) {
100
+                $this->recur[$key] = $value;
101
+            }
102
+
103
+            $this->setFirstOccurrence();
104
+
105
+            // Let's see if next occurrence has to be generated
106
+            return $this->moveToNextOccurrence();
107
+        }
108
+
109
+        /**
110
+         * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
111
+         */
112
+        public function setFirstOccurrence() {
113
+            // Check if it is already the first occurrence
114
+            if ($this->action['start'] == $this->recur["start"]) {
115
+                return;
116
+            }
117
+            $items = $this->getNextOccurrence();
118
+
119
+            $props = [];
120
+            $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
121
+            $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
122
+
123
+            $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
124
+            $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
125
+
126
+            mapi_setprops($this->message, $props);
127
+        }
128
+
129
+        /**
130
+         * Function which creates new task as current occurrence and moves the
131
+         * existing task to next occurrence.
132
+         *
133
+         *@param array $recur $action from client
134
+         *
135
+         *@return bool if moving to next occurrence succeed then it returns
136
+         *        properties of either newly created task or existing task ELSE
137
+         *        false because that was last occurrence
138
+         */
139
+        public function moveToNextOccurrence() {
140
+            $result = false;
141
+            /*
142 142
 			 * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
143 143
 			 * then we create first two occurrence separately and for first occurrence recurrence has ended.
144 144
 			 */
145
-			if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
146
-				($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
147
-				$nextOccurrence = $this->getNextOccurrence();
148
-				$result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
149
-
150
-				$props = [];
151
-				if ($nextOccurrence) {
152
-					if (!isset($this->action['deleteOccurrence'])) {
153
-						// Create current occurrence as separate task
154
-						$result = $this->regenerateTask($this->action['complete']);
155
-					}
156
-
157
-					// Set reminder for next occurrence
158
-					$this->setReminder($nextOccurrence);
159
-
160
-					// Update properties for next occurrence
161
-					$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
162
-					$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
163
-
164
-					$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
165
-					$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
166
-
167
-					// If current task as been mark as 'Complete' then next occurrence should be incomplete.
168
-					if (isset($this->action['complete']) && $this->action['complete'] == 1) {
169
-						$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
170
-						$this->action['complete'] = $props[$this->proptags["complete"]] = false;
171
-						$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
172
-					}
173
-
174
-					$props[$this->proptags["dead_occurrence"]] = false;
175
-				}
176
-				else {
177
-					if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
178
-						return false;
179
-					}
180
-
181
-					// Didn't get next occurrence, probably this is the last one, so recurrence ends here
182
-					$props[$this->proptags["dead_occurrence"]] = true;
183
-					$props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
184
-					$props[$this->proptags["task_f_creator"]] = true;
185
-
186
-					// OL props
187
-					$props[$this->proptags["side_effects"]] = 1296;
188
-					$props[$this->proptags["icon_index"]] = 1280;
189
-				}
190
-
191
-				mapi_setprops($this->message, $props);
192
-			}
193
-
194
-			return $result;
195
-		}
196
-
197
-		/**
198
-		 * Function which return properties of next occurrence.
199
-		 *
200
-		 *@return array startdate/enddate of next occurrence
201
-		 */
202
-		public function getNextOccurrence() {
203
-			if ($this->recur) {
204
-				$items = [];
205
-
206
-				// @TODO: fix start of range
207
-				$start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
208
-				$dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
209
-
210
-				// Fix recur object
211
-				$this->recur['startocc'] = 0;
212
-				$this->recur['endocc'] = 0;
213
-
214
-				// Retrieve next occurrence
215
-				$items = $this->getItems($start, $dayend, 1);
216
-
217
-				return !empty($items) ? $items[0] : false;
218
-			}
219
-		}
220
-
221
-		/**
222
-		 * Function which clones current occurrence and sets appropriate properties.
223
-		 * The original recurring item is moved to next occurrence.
224
-		 *
225
-		 *@param bool $markComplete true if existing occurrence has to be mark complete else false
226
-		 */
227
-		public function regenerateTask($markComplete) {
228
-			// Get all properties
229
-			$taskItemProps = mapi_getprops($this->message);
230
-
231
-			if (isset($this->action["subject"])) {
232
-				$taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
233
-			}
234
-			if (isset($this->action["importance"])) {
235
-				$taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
236
-			}
237
-			if (isset($this->action["startdate"])) {
238
-				$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
239
-				$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
240
-			}
241
-			if (isset($this->action["duedate"])) {
242
-				$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
243
-				$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
244
-			}
245
-
246
-			$folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
247
-			$newMessage = mapi_folder_createmessage($folder);
248
-
249
-			$taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
250
-			$taskItemProps[$this->proptags["complete"]] = $markComplete;
251
-			$taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
252
-
253
-			// This occurrence has been marked as 'Complete' so disable reminder
254
-			if ($markComplete) {
255
-				$taskItemProps[$this->proptags["reset_reminder"]] = false;
256
-				$taskItemProps[$this->proptags["reminder"]] = false;
257
-				$taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
258
-
259
-				unset($this->action[$this->proptags['datecompleted']]);
260
-			}
261
-
262
-			// Recurrence ends for this item
263
-			$taskItemProps[$this->proptags["dead_occurrence"]] = true;
264
-			$taskItemProps[$this->proptags["task_f_creator"]] = true;
265
-
266
-			// OL props
267
-			$taskItemProps[$this->proptags["side_effects"]] = 1296;
268
-			$taskItemProps[$this->proptags["icon_index"]] = 1280;
269
-
270
-			// Copy recipients
271
-			$recipienttable = mapi_message_getrecipienttable($this->message);
272
-			$recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]);
273
-
274
-			$copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
275
-			$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
276
-			foreach ($copy_to_recipientRows as $recipient) {
277
-				mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]);
278
-			}
279
-			mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
280
-
281
-			// Copy attachments
282
-			$attachmentTable = mapi_message_getattachmenttable($this->message);
283
-			if ($attachmentTable) {
284
-				$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
285
-
286
-				foreach ($attachments as $attach_props) {
287
-					$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
288
-					$attach_newResourceMsg = mapi_message_createattach($newMessage);
289
-
290
-					mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
291
-					mapi_savechanges($attach_newResourceMsg);
292
-				}
293
-			}
294
-
295
-			mapi_setprops($newMessage, $taskItemProps);
296
-			mapi_savechanges($newMessage);
297
-
298
-			// Update body of original message
299
-			$msgbody = mapi_openproperty($this->message, PR_BODY);
300
-			$msgbody = trim($msgbody, "\0");
301
-			$separator = "------------\r\n";
302
-
303
-			if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
304
-				$msgbody = $separator . $msgbody;
305
-				$stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0);
306
-				mapi_stream_setsize($stream, strlen($msgbody));
307
-				mapi_stream_write($stream, $msgbody);
308
-				mapi_stream_commit($stream);
309
-			}
310
-
311
-			// We need these properties to notify client
312
-			return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
313
-		}
314
-
315
-		/**
316
-		 * processOccurrenceItem, adds an item to a list of occurrences, but only if the
317
-		 * resulting occurrence starts or ends in the interval <$start, $end>.
318
-		 *
319
-		 * @param array $items    reference to the array to be added to
320
-		 * @param date  $start    start of timeframe in GMT TIME
321
-		 * @param date  $end      end of timeframe in GMT TIME
322
-		 * @param date  $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
323
-		 * @param mixed $now
324
-		 */
325
-		public function processOccurrenceItem(&$items, $start, $end, $now) {
326
-			if ($now > $start) {
327
-				$newItem = [];
328
-				$newItem[$this->proptags['startdate']] = $now;
329
-
330
-				// If startdate and enddate are set on task, then slide enddate according to duration
331
-				if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
332
-					$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
333
-				}
334
-				else {
335
-					$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
336
-				}
337
-
338
-				$items[] = $newItem;
339
-			}
340
-		}
341
-
342
-		/**
343
-		 * Function which marks existing occurrence to 'Complete'.
344
-		 *
345
-		 *@param array $recur array action from client
346
-		 *
347
-		 *@return array of properties of regenerated task else false
348
-		 */
349
-		public function markOccurrenceComplete(&$recur) {
350
-			// Fix timezone object
351
-			$this->tz = false;
352
-			$this->action = &$recur;
353
-			$dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
354
-
355
-			if (!$dead_occurrence) {
356
-				return $this->moveToNextOccurrence();
357
-			}
358
-
359
-			return false;
360
-		}
361
-
362
-		/**
363
-		 * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
364
-		 *
365
-		 *@param array $nextOccurrence properties of next occurrence
366
-		 */
367
-		public function setReminder($nextOccurrence) {
368
-			$props = [];
369
-			if ($nextOccurrence) {
370
-				// Check if reminder is reset. Default is 'false'
371
-				$reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
372
-				$reminder = $this->messageprops[$this->proptags['reminder']];
373
-
374
-				// Either reminder was already set OR reminder was set but was dismissed bty user
375
-				if ($reminder || $reset_reminder) {
376
-					// Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
377
-					$reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
378
-					$reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
379
-					$reminder_difference = $reminder_difference - $reminder_time;
380
-
381
-					// Apply duration to next calculated duedate
382
-					$next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
383
-
384
-					$props[$this->proptags['reminder_time']] = $next_reminder_time;
385
-					$props[$this->proptags['flagdueby']] = $next_reminder_time;
386
-					$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
387
-				}
388
-			}
389
-			else {
390
-				// Didn't get next occurrence, probably this is the last occurrence
391
-				$props[$this->proptags['reminder']] = false;
392
-				$props[$this->proptags['reset_reminder']] = false;
393
-			}
394
-
395
-			if (!empty($props)) {
396
-				mapi_setprops($this->message, $props);
397
-			}
398
-		}
399
-
400
-		/**
401
-		 * Function which recurring task to next occurrence.
402
-		 * It simply doesn't regenerate task.
403
-		 *
404
-		 * @param array $action
405
-		 */
406
-		public function deleteOccurrence($action) {
407
-			$this->tz = false;
408
-			$this->action = $action;
409
-			$result = $this->moveToNextOccurrence();
410
-
411
-			mapi_savechanges($this->message);
412
-
413
-			return $result;
414
-		}
415
-	}
145
+            if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
146
+                ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
147
+                $nextOccurrence = $this->getNextOccurrence();
148
+                $result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
149
+
150
+                $props = [];
151
+                if ($nextOccurrence) {
152
+                    if (!isset($this->action['deleteOccurrence'])) {
153
+                        // Create current occurrence as separate task
154
+                        $result = $this->regenerateTask($this->action['complete']);
155
+                    }
156
+
157
+                    // Set reminder for next occurrence
158
+                    $this->setReminder($nextOccurrence);
159
+
160
+                    // Update properties for next occurrence
161
+                    $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
162
+                    $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
163
+
164
+                    $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
165
+                    $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
166
+
167
+                    // If current task as been mark as 'Complete' then next occurrence should be incomplete.
168
+                    if (isset($this->action['complete']) && $this->action['complete'] == 1) {
169
+                        $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
170
+                        $this->action['complete'] = $props[$this->proptags["complete"]] = false;
171
+                        $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
172
+                    }
173
+
174
+                    $props[$this->proptags["dead_occurrence"]] = false;
175
+                }
176
+                else {
177
+                    if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
178
+                        return false;
179
+                    }
180
+
181
+                    // Didn't get next occurrence, probably this is the last one, so recurrence ends here
182
+                    $props[$this->proptags["dead_occurrence"]] = true;
183
+                    $props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
184
+                    $props[$this->proptags["task_f_creator"]] = true;
185
+
186
+                    // OL props
187
+                    $props[$this->proptags["side_effects"]] = 1296;
188
+                    $props[$this->proptags["icon_index"]] = 1280;
189
+                }
190
+
191
+                mapi_setprops($this->message, $props);
192
+            }
193
+
194
+            return $result;
195
+        }
196
+
197
+        /**
198
+         * Function which return properties of next occurrence.
199
+         *
200
+         *@return array startdate/enddate of next occurrence
201
+         */
202
+        public function getNextOccurrence() {
203
+            if ($this->recur) {
204
+                $items = [];
205
+
206
+                // @TODO: fix start of range
207
+                $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
208
+                $dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
209
+
210
+                // Fix recur object
211
+                $this->recur['startocc'] = 0;
212
+                $this->recur['endocc'] = 0;
213
+
214
+                // Retrieve next occurrence
215
+                $items = $this->getItems($start, $dayend, 1);
216
+
217
+                return !empty($items) ? $items[0] : false;
218
+            }
219
+        }
220
+
221
+        /**
222
+         * Function which clones current occurrence and sets appropriate properties.
223
+         * The original recurring item is moved to next occurrence.
224
+         *
225
+         *@param bool $markComplete true if existing occurrence has to be mark complete else false
226
+         */
227
+        public function regenerateTask($markComplete) {
228
+            // Get all properties
229
+            $taskItemProps = mapi_getprops($this->message);
230
+
231
+            if (isset($this->action["subject"])) {
232
+                $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
233
+            }
234
+            if (isset($this->action["importance"])) {
235
+                $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
236
+            }
237
+            if (isset($this->action["startdate"])) {
238
+                $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
239
+                $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
240
+            }
241
+            if (isset($this->action["duedate"])) {
242
+                $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
243
+                $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
244
+            }
245
+
246
+            $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
247
+            $newMessage = mapi_folder_createmessage($folder);
248
+
249
+            $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
250
+            $taskItemProps[$this->proptags["complete"]] = $markComplete;
251
+            $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
252
+
253
+            // This occurrence has been marked as 'Complete' so disable reminder
254
+            if ($markComplete) {
255
+                $taskItemProps[$this->proptags["reset_reminder"]] = false;
256
+                $taskItemProps[$this->proptags["reminder"]] = false;
257
+                $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
258
+
259
+                unset($this->action[$this->proptags['datecompleted']]);
260
+            }
261
+
262
+            // Recurrence ends for this item
263
+            $taskItemProps[$this->proptags["dead_occurrence"]] = true;
264
+            $taskItemProps[$this->proptags["task_f_creator"]] = true;
265
+
266
+            // OL props
267
+            $taskItemProps[$this->proptags["side_effects"]] = 1296;
268
+            $taskItemProps[$this->proptags["icon_index"]] = 1280;
269
+
270
+            // Copy recipients
271
+            $recipienttable = mapi_message_getrecipienttable($this->message);
272
+            $recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]);
273
+
274
+            $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
275
+            $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
276
+            foreach ($copy_to_recipientRows as $recipient) {
277
+                mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]);
278
+            }
279
+            mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
280
+
281
+            // Copy attachments
282
+            $attachmentTable = mapi_message_getattachmenttable($this->message);
283
+            if ($attachmentTable) {
284
+                $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
285
+
286
+                foreach ($attachments as $attach_props) {
287
+                    $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
288
+                    $attach_newResourceMsg = mapi_message_createattach($newMessage);
289
+
290
+                    mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
291
+                    mapi_savechanges($attach_newResourceMsg);
292
+                }
293
+            }
294
+
295
+            mapi_setprops($newMessage, $taskItemProps);
296
+            mapi_savechanges($newMessage);
297
+
298
+            // Update body of original message
299
+            $msgbody = mapi_openproperty($this->message, PR_BODY);
300
+            $msgbody = trim($msgbody, "\0");
301
+            $separator = "------------\r\n";
302
+
303
+            if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
304
+                $msgbody = $separator . $msgbody;
305
+                $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0);
306
+                mapi_stream_setsize($stream, strlen($msgbody));
307
+                mapi_stream_write($stream, $msgbody);
308
+                mapi_stream_commit($stream);
309
+            }
310
+
311
+            // We need these properties to notify client
312
+            return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
313
+        }
314
+
315
+        /**
316
+         * processOccurrenceItem, adds an item to a list of occurrences, but only if the
317
+         * resulting occurrence starts or ends in the interval <$start, $end>.
318
+         *
319
+         * @param array $items    reference to the array to be added to
320
+         * @param date  $start    start of timeframe in GMT TIME
321
+         * @param date  $end      end of timeframe in GMT TIME
322
+         * @param date  $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
323
+         * @param mixed $now
324
+         */
325
+        public function processOccurrenceItem(&$items, $start, $end, $now) {
326
+            if ($now > $start) {
327
+                $newItem = [];
328
+                $newItem[$this->proptags['startdate']] = $now;
329
+
330
+                // If startdate and enddate are set on task, then slide enddate according to duration
331
+                if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
332
+                    $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
333
+                }
334
+                else {
335
+                    $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
336
+                }
337
+
338
+                $items[] = $newItem;
339
+            }
340
+        }
341
+
342
+        /**
343
+         * Function which marks existing occurrence to 'Complete'.
344
+         *
345
+         *@param array $recur array action from client
346
+         *
347
+         *@return array of properties of regenerated task else false
348
+         */
349
+        public function markOccurrenceComplete(&$recur) {
350
+            // Fix timezone object
351
+            $this->tz = false;
352
+            $this->action = &$recur;
353
+            $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
354
+
355
+            if (!$dead_occurrence) {
356
+                return $this->moveToNextOccurrence();
357
+            }
358
+
359
+            return false;
360
+        }
361
+
362
+        /**
363
+         * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
364
+         *
365
+         *@param array $nextOccurrence properties of next occurrence
366
+         */
367
+        public function setReminder($nextOccurrence) {
368
+            $props = [];
369
+            if ($nextOccurrence) {
370
+                // Check if reminder is reset. Default is 'false'
371
+                $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
372
+                $reminder = $this->messageprops[$this->proptags['reminder']];
373
+
374
+                // Either reminder was already set OR reminder was set but was dismissed bty user
375
+                if ($reminder || $reset_reminder) {
376
+                    // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
377
+                    $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
378
+                    $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
379
+                    $reminder_difference = $reminder_difference - $reminder_time;
380
+
381
+                    // Apply duration to next calculated duedate
382
+                    $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
383
+
384
+                    $props[$this->proptags['reminder_time']] = $next_reminder_time;
385
+                    $props[$this->proptags['flagdueby']] = $next_reminder_time;
386
+                    $this->action['reminder'] = $props[$this->proptags['reminder']] = true;
387
+                }
388
+            }
389
+            else {
390
+                // Didn't get next occurrence, probably this is the last occurrence
391
+                $props[$this->proptags['reminder']] = false;
392
+                $props[$this->proptags['reset_reminder']] = false;
393
+            }
394
+
395
+            if (!empty($props)) {
396
+                mapi_setprops($this->message, $props);
397
+            }
398
+        }
399
+
400
+        /**
401
+         * Function which recurring task to next occurrence.
402
+         * It simply doesn't regenerate task.
403
+         *
404
+         * @param array $action
405
+         */
406
+        public function deleteOccurrence($action) {
407
+            $this->tz = false;
408
+            $this->action = $action;
409
+            $result = $this->moveToNextOccurrence();
410
+
411
+            mapi_savechanges($this->message);
412
+
413
+            return $result;
414
+        }
415
+    }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
 				$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
285 285
 
286 286
 				foreach ($attachments as $attach_props) {
287
-					$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
287
+					$attach_old = mapi_message_openattach($this->message, (int)$attach_props[PR_ATTACH_NUM]);
288 288
 					$attach_newResourceMsg = mapi_message_createattach($newMessage);
289 289
 
290 290
 					mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
@@ -301,7 +301,7 @@  discard block
 block discarded – undo
301 301
 			$separator = "------------\r\n";
302 302
 
303 303
 			if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
304
-				$msgbody = $separator . $msgbody;
304
+				$msgbody = $separator.$msgbody;
305 305
 				$stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0);
306 306
 				mapi_stream_setsize($stream, strlen($msgbody));
307 307
 				mapi_stream_write($stream, $msgbody);
Please login to merge, or discard this patch.
Braces   +3 added lines, -6 removed lines patch added patch discarded remove patch
@@ -172,8 +172,7 @@  discard block
 block discarded – undo
172 172
 					}
173 173
 
174 174
 					$props[$this->proptags["dead_occurrence"]] = false;
175
-				}
176
-				else {
175
+				} else {
177 176
 					if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
178 177
 						return false;
179 178
 					}
@@ -330,8 +329,7 @@  discard block
 block discarded – undo
330 329
 				// If startdate and enddate are set on task, then slide enddate according to duration
331 330
 				if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
332 331
 					$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
333
-				}
334
-				else {
332
+				} else {
335 333
 					$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
336 334
 				}
337 335
 
@@ -385,8 +383,7 @@  discard block
 block discarded – undo
385 383
 					$props[$this->proptags['flagdueby']] = $next_reminder_time;
386 384
 					$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
387 385
 				}
388
-			}
389
-			else {
386
+			} else {
390 387
 				// Didn't get next occurrence, probably this is the last occurrence
391 388
 				$props[$this->proptags['reminder']] = false;
392 389
 				$props[$this->proptags['reset_reminder']] = false;
Please login to merge, or discard this patch.
mapi/mapicode.php 2 patches
Indentation   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -27,7 +27,7 @@  discard block
 block discarded – undo
27 27
  * @param mixed $code
28 28
  */
29 29
 function make_mapi_e($code) {
30
-	return (int) mapi_make_scode(1, $code);
30
+    return (int) mapi_make_scode(1, $code);
31 31
 }
32 32
 
33 33
 /**
@@ -36,7 +36,7 @@  discard block
 block discarded – undo
36 36
  * @param mixed $code
37 37
  */
38 38
 function make_mapi_s($code) {
39
-	return (int) mapi_make_scode(0, $code);
39
+    return (int) mapi_make_scode(0, $code);
40 40
 }
41 41
 
42 42
 /* From mapicode.h */
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -27,7 +27,7 @@  discard block
 block discarded – undo
27 27
  * @param mixed $code
28 28
  */
29 29
 function make_mapi_e($code) {
30
-	return (int) mapi_make_scode(1, $code);
30
+	return (int)mapi_make_scode(1, $code);
31 31
 }
32 32
 
33 33
 /**
@@ -36,7 +36,7 @@  discard block
 block discarded – undo
36 36
  * @param mixed $code
37 37
  */
38 38
 function make_mapi_s($code) {
39
-	return (int) mapi_make_scode(0, $code);
39
+	return (int)mapi_make_scode(0, $code);
40 40
 }
41 41
 
42 42
 /* From mapicode.h */
Please login to merge, or discard this patch.
mapi/class.recurrence.php 3 patches
Indentation   +1267 added lines, -1267 removed lines patch added patch discarded remove patch
@@ -5,11 +5,11 @@  discard block
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6 6
  */
7 7
 
8
-	/**
9
-	 * Recurrence.
10
-	 */
11
-	class Recurrence extends BaseRecurrence {
12
-		/*
8
+    /**
9
+     * Recurrence.
10
+     */
11
+    class Recurrence extends BaseRecurrence {
12
+        /*
13 13
 		 * ABOUT TIMEZONES
14 14
 		 *
15 15
 		 * Timezones are rather complicated here so here are some rules to think about:
@@ -21,1279 +21,1279 @@  discard block
 block discarded – undo
21 21
 		 *   always in LOCAL time.
22 22
 		 */
23 23
 
24
-		// All properties for a recipient that are interesting
25
-		public $recipprops = [
26
-			PR_ENTRYID,
27
-			PR_SEARCH_KEY,
28
-			PR_DISPLAY_NAME,
29
-			PR_EMAIL_ADDRESS,
30
-			PR_RECIPIENT_ENTRYID,
31
-			PR_RECIPIENT_TYPE,
32
-			PR_SEND_INTERNET_ENCODING,
33
-			PR_SEND_RICH_INFO,
34
-			PR_RECIPIENT_DISPLAY_NAME,
35
-			PR_ADDRTYPE,
36
-			PR_DISPLAY_TYPE,
37
-			PR_DISPLAY_TYPE_EX,
38
-			PR_RECIPIENT_TRACKSTATUS,
39
-			PR_RECIPIENT_TRACKSTATUS_TIME,
40
-			PR_RECIPIENT_FLAGS,
41
-			PR_ROWID,
42
-		];
43
-
44
-		/**
45
-		 * Constructor.
46
-		 *
47
-		 * @param resource $store    MAPI Message Store Object
48
-		 * @param resource $message  the MAPI (appointment) message
49
-		 * @param array    $proptags an associative array of protags and their values
50
-		 */
51
-		public function __construct($store, $message, $proptags = []) {
52
-			if ($proptags) {
53
-				$this->proptags = $proptags;
54
-			}
55
-			else {
56
-				$properties = [];
57
-				$properties["entryid"] = PR_ENTRYID;
58
-				$properties["parent_entryid"] = PR_PARENT_ENTRYID;
59
-				$properties["message_class"] = PR_MESSAGE_CLASS;
60
-				$properties["icon_index"] = PR_ICON_INDEX;
61
-				$properties["subject"] = PR_SUBJECT;
62
-				$properties["display_to"] = PR_DISPLAY_TO;
63
-				$properties["importance"] = PR_IMPORTANCE;
64
-				$properties["sensitivity"] = PR_SENSITIVITY;
65
-				$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
66
-				$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
67
-				$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
68
-				$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
69
-				$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
70
-				$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
71
-				$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
72
-				$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
73
-				$properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
74
-				$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
75
-				$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
76
-				$properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
77
-				$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
78
-				$properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
79
-				$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
80
-				$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
81
-				$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
82
-				$properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
83
-				$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
84
-				$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
85
-				$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
86
-				$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
87
-				$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
88
-				$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
89
-				$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
90
-				$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
91
-				$properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
92
-				$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
93
-				$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
94
-				$properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514";
95
-
96
-				$this->proptags = getPropIdsFromStrings($store, $properties);
97
-			}
98
-
99
-			parent::__construct($store, $message);
100
-		}
101
-
102
-		/**
103
-		 * Create an exception.
104
-		 *
105
-		 * @param array        $exception_props  the exception properties (same properties as normal recurring items)
106
-		 * @param date         $base_date        the base date of the exception (LOCAL time of non-exception occurrence)
107
-		 * @param bool         $delete           true - delete occurrence, false - create new exception or modify existing
108
-		 * @param array        $exception_recips true - delete occurrence, false - create new exception or modify existing
109
-		 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
110
-		 */
111
-		public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) {
112
-			$baseday = $this->dayStartOf($base_date);
113
-			$basetime = $baseday + $this->recur["startocc"] * 60;
114
-
115
-			// Remove any pre-existing exception on this base date
116
-			if ($this->isException($baseday)) {
117
-				$this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
118
-			}
119
-
120
-			if (!$delete) {
121
-				if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
122
-					return false;
123
-				}
124
-				// Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
125
-				foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) {
126
-					if (isset($this->messageprops[$this->proptags[$propname]])) {
127
-						$props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
128
-					}
129
-				}
130
-
131
-				$props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
132
-				unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]);
133
-
134
-				$props = $exception_props + $props;
135
-
136
-				// Basedate in the exception attachment is the GMT time at which the original occurrence would have been
137
-				$props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
138
-
139
-				if (!isset($exception_props[$this->proptags["startdate"]])) {
140
-					$props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
141
-				}
142
-
143
-				if (!isset($exception_props[$this->proptags["duedate"]])) {
144
-					$props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
145
-				}
146
-
147
-				// synchronize commonstart/commonend with startdate/duedate
148
-				if (isset($props[$this->proptags["startdate"]])) {
149
-					$props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
150
-				}
151
-
152
-				if (isset($props[$this->proptags["duedate"]])) {
153
-					$props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
154
-				}
155
-
156
-				// Save the data into an attachment
157
-				$this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
158
-
159
-				$changed_item = [];
160
-
161
-				$changed_item["basedate"] = $baseday;
162
-				$changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
163
-				$changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
164
-
165
-				if (array_key_exists($this->proptags["subject"], $exception_props)) {
166
-					$changed_item["subject"] = $exception_props[$this->proptags["subject"]];
167
-				}
168
-
169
-				if (array_key_exists($this->proptags["location"], $exception_props)) {
170
-					$changed_item["location"] = $exception_props[$this->proptags["location"]];
171
-				}
172
-
173
-				if (array_key_exists($this->proptags["label"], $exception_props)) {
174
-					$changed_item["label"] = $exception_props[$this->proptags["label"]];
175
-				}
176
-
177
-				if (array_key_exists($this->proptags["reminder"], $exception_props)) {
178
-					$changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
179
-				}
180
-
181
-				if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
182
-					$changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
183
-				}
184
-
185
-				if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
186
-					$changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
187
-				}
188
-
189
-				if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
190
-					$changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
191
-				}
192
-
193
-				// Add the changed occurrence to the list
194
-				array_push($this->recur["changed_occurrences"], $changed_item);
195
-			}
196
-			else {
197
-				// Delete the occurrence by placing it in the deleted occurrences list
198
-				array_push($this->recur["deleted_occurrences"], $baseday);
199
-			}
200
-
201
-			// Turn on hideattachments, because the attachments in this item are the exceptions
202
-			mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
203
-
204
-			// Save recurrence data to message
205
-			$this->saveRecurrence();
206
-
207
-			return true;
208
-		}
209
-
210
-		/**
211
-		 * Modifies an existing exception, but only updates the given properties
212
-		 * NOTE: You can't remove properties from an exception, only add new ones.
213
-		 *
214
-		 * @param mixed $exception_props
215
-		 * @param mixed $base_date
216
-		 * @param mixed $exception_recips
217
-		 * @param mixed $copy_attach_from
218
-		 */
219
-		public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) {
220
-			if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
221
-				return false;
222
-			}
223
-
224
-			$baseday = $this->dayStartOf($base_date);
225
-			$extomodify = false;
226
-
227
-			for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) {
228
-				if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) {
229
-					$extomodify = &$this->recur["changed_occurrences"][$i];
230
-				}
231
-			}
232
-
233
-			if (!$extomodify) {
234
-				return false;
235
-			}
236
-
237
-			// remove basedate property as we want to preserve the old value
238
-			// client will send basedate with time part as zero, so discard that value
239
-			unset($exception_props[$this->proptags["basedate"]]);
240
-
241
-			if (array_key_exists($this->proptags["startdate"], $exception_props)) {
242
-				$extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
243
-			}
244
-
245
-			if (array_key_exists($this->proptags["duedate"], $exception_props)) {
246
-				$extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
247
-			}
248
-
249
-			if (array_key_exists($this->proptags["subject"], $exception_props)) {
250
-				$extomodify["subject"] = $exception_props[$this->proptags["subject"]];
251
-			}
252
-
253
-			if (array_key_exists($this->proptags["location"], $exception_props)) {
254
-				$extomodify["location"] = $exception_props[$this->proptags["location"]];
255
-			}
256
-
257
-			if (array_key_exists($this->proptags["label"], $exception_props)) {
258
-				$extomodify["label"] = $exception_props[$this->proptags["label"]];
259
-			}
260
-
261
-			if (array_key_exists($this->proptags["reminder"], $exception_props)) {
262
-				$extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
263
-			}
264
-
265
-			if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
266
-				$extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
267
-			}
268
-
269
-			if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
270
-				$extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
271
-			}
272
-
273
-			if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
274
-				$extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
275
-			}
276
-
277
-			$exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
278
-
279
-			// synchronize commonstart/commonend with startdate/duedate
280
-			if (isset($exception_props[$this->proptags["startdate"]])) {
281
-				$exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
282
-			}
283
-
284
-			if (isset($exception_props[$this->proptags["duedate"]])) {
285
-				$exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
286
-			}
287
-
288
-			$attach = $this->getExceptionAttachment($baseday);
289
-			if (!$attach) {
290
-				if ($copy_attach_from) {
291
-					$this->deleteExceptionAttachment($base_date);
292
-					$this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
293
-				}
294
-				else {
295
-					$this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
296
-				}
297
-			}
298
-			else {
299
-				$message = mapi_attach_openobj($attach, MAPI_MODIFY);
300
-
301
-				// Set exception properties on embedded message and save
302
-				mapi_setprops($message, $exception_props);
303
-				$this->setExceptionRecipients($message, $exception_recips, false);
304
-				mapi_savechanges($message);
305
-
306
-				// If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
307
-				// on the attachment which holds the embedded msg and save everything.
308
-				$props = [];
309
-				if (isset($exception_props[$this->proptags["startdate"]])) {
310
-					$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
311
-				}
312
-				if (isset($exception_props[$this->proptags["duedate"]])) {
313
-					$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
314
-				}
315
-				if (!empty($props)) {
316
-					mapi_setprops($attach, $props);
317
-				}
318
-
319
-				mapi_savechanges($attach);
320
-			}
321
-
322
-			// Save recurrence data to message
323
-			$this->saveRecurrence();
324
-
325
-			return true;
326
-		}
327
-
328
-		// Checks to see if the following is true:
329
-		// 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
330
-		// 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
331
-		//
332
-		// Both $basedate and $start are in LOCAL time
333
-		public function isValidExceptionDate($basedate, $start) {
334
-			// The way we do this is to look at the days that we're 'moving' the item in the exception. Each
335
-			// of these days may only contain the item that we're modifying. Any other item violates the rules.
336
-
337
-			if ($this->isException($basedate)) {
338
-				// If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
339
-				// the exception used to be.
340
-				$oldexception = $this->getChangeException($basedate);
341
-				$prevday = $this->dayStartOf($oldexception["start"]);
342
-			}
343
-			else {
344
-				// If its a new exception, we want to look at the original placement of this item.
345
-				$prevday = $basedate;
346
-			}
347
-
348
-			$startday = $this->dayStartOf($start);
349
-
350
-			// Get all the occurrences on the days between the basedate (may be reversed)
351
-			if ($prevday < $startday) {
352
-				$items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
353
-			}
354
-			else {
355
-				$items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
356
-			}
357
-
358
-			// There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
359
-			// then we abort the change, since one of the rules has been violated.
360
-			return count($items) == 1;
361
-		}
362
-
363
-		/**
364
-		 * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:.
365
-		 *
366
-		 * Both must be true:
367
-		 * - reminder time of this item is not before the starttime of the previous recurring item
368
-		 * - reminder time of the next item is not before the starttime of this item
369
-		 *
370
-		 * @param date   $basedate        the base date of the exception (LOCAL time of non-exception occurrence)
371
-		 * @param string $reminderminutes reminder minutes which is set of the item
372
-		 * @param date   $startdate       the startdate of the selected item
373
-		 * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
374
-		 */
375
-		public function isValidReminderTime($basedate, $reminderminutes, $startdate) {
376
-			// get all occurrence items before the seleceted items occurrence starttime
377
-			$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
378
-
379
-			if (!empty($occitems)) {
380
-				// as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
381
-				$previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
382
-
383
-				// if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
384
-				if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
385
-					return false;
386
-				}
387
-			}
388
-
389
-			// Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
390
-			$currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
391
-
392
-			// If there are another two occurrences, then the first is the current occurrence, and the one after that
393
-			// is the next occurrence.
394
-			if (count($currentOcc) > 1) {
395
-				$next = $currentOcc[1];
396
-				// Get reminder time of the next occurrence.
397
-				$nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
398
-				// If the reminder time of the next item is before the start of this item, then that's not allowed
399
-				if ($nextOccReminderTime <= $startdate) {
400
-					return false;
401
-				}
402
-			}
403
-
404
-			// All was ok
405
-			return true;
406
-		}
407
-
408
-		public function setRecurrence($tz, $recur) {
409
-			// only reset timezone if specified
410
-			if ($tz) {
411
-				$this->tz = $tz;
412
-			}
413
-
414
-			$this->recur = $recur;
415
-
416
-			if (!isset($this->recur["changed_occurrences"])) {
417
-				$this->recur["changed_occurrences"] = [];
418
-			}
419
-
420
-			if (!isset($this->recur["deleted_occurrences"])) {
421
-				$this->recur["deleted_occurrences"] = [];
422
-			}
423
-
424
-			$this->deleteAttachments();
425
-			$this->saveRecurrence();
426
-
427
-			// if client has not set the recurring_pattern then we should generate it and save it
428
-			$messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
429
-			if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
430
-				$this->saveRecurrencePattern();
431
-			}
432
-		}
433
-
434
-		// Returns the start or end time of the occurrence on the given base date.
435
-		// This assumes that the basedate you supply is in LOCAL time
436
-		public function getOccurrenceStart($basedate) {
437
-			$daystart = $this->dayStartOf($basedate);
438
-
439
-			return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
440
-		}
441
-
442
-		public function getOccurrenceEnd($basedate) {
443
-			$daystart = $this->dayStartOf($basedate);
444
-
445
-			return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
446
-		}
447
-
448
-		// Backwards compatible code
449
-		public function getOccurrenceStart($basedate) {
450
-			return $this->getOccurrenceStart($basedate);
451
-		}
452
-
453
-		public function getOccurrenceEnd($basedate) {
454
-			return $this->getOccurrenceEnd($basedate);
455
-		}
456
-
457
-		/**
458
-		 * This function returns the next remindertime starting from $timestamp
459
-		 * When no next reminder exists, false is returned.
460
-		 *
461
-		 * Note: Before saving this new reminder time (when snoozing), you must check for
462
-		 *       yourself if this reminder time is earlier than your snooze time, else
463
-		 *       use your snooze time and not this reminder time.
464
-		 *
465
-		 * @param mixed $timestamp
466
-		 */
467
-		public function getNextReminderTime($timestamp) {
468
-			/**
469
-			 * Get next item from now until forever, but max 1 item with reminder set
470
-			 * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
471
-			 * Here for getting next 10 occurrences assuming that next here we will be able to find
472
-			 * nextreminder occurrence in 10 occureneces.
473
-			 */
474
-			$items = $this->getItems($timestamp, 0x7FF00000, 10, true);
475
-
476
-			// Initially setting nextreminder to false so when no next reminder exists, false is returned.
477
-			$nextreminder = false;
478
-			/*
24
+        // All properties for a recipient that are interesting
25
+        public $recipprops = [
26
+            PR_ENTRYID,
27
+            PR_SEARCH_KEY,
28
+            PR_DISPLAY_NAME,
29
+            PR_EMAIL_ADDRESS,
30
+            PR_RECIPIENT_ENTRYID,
31
+            PR_RECIPIENT_TYPE,
32
+            PR_SEND_INTERNET_ENCODING,
33
+            PR_SEND_RICH_INFO,
34
+            PR_RECIPIENT_DISPLAY_NAME,
35
+            PR_ADDRTYPE,
36
+            PR_DISPLAY_TYPE,
37
+            PR_DISPLAY_TYPE_EX,
38
+            PR_RECIPIENT_TRACKSTATUS,
39
+            PR_RECIPIENT_TRACKSTATUS_TIME,
40
+            PR_RECIPIENT_FLAGS,
41
+            PR_ROWID,
42
+        ];
43
+
44
+        /**
45
+         * Constructor.
46
+         *
47
+         * @param resource $store    MAPI Message Store Object
48
+         * @param resource $message  the MAPI (appointment) message
49
+         * @param array    $proptags an associative array of protags and their values
50
+         */
51
+        public function __construct($store, $message, $proptags = []) {
52
+            if ($proptags) {
53
+                $this->proptags = $proptags;
54
+            }
55
+            else {
56
+                $properties = [];
57
+                $properties["entryid"] = PR_ENTRYID;
58
+                $properties["parent_entryid"] = PR_PARENT_ENTRYID;
59
+                $properties["message_class"] = PR_MESSAGE_CLASS;
60
+                $properties["icon_index"] = PR_ICON_INDEX;
61
+                $properties["subject"] = PR_SUBJECT;
62
+                $properties["display_to"] = PR_DISPLAY_TO;
63
+                $properties["importance"] = PR_IMPORTANCE;
64
+                $properties["sensitivity"] = PR_SENSITIVITY;
65
+                $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
66
+                $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
67
+                $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
68
+                $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
69
+                $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
70
+                $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
71
+                $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
72
+                $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
73
+                $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
74
+                $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
75
+                $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
76
+                $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
77
+                $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
78
+                $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
79
+                $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
80
+                $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
81
+                $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
82
+                $properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
83
+                $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
84
+                $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
85
+                $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
86
+                $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
87
+                $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
88
+                $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
89
+                $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
90
+                $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
91
+                $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
92
+                $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
93
+                $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
94
+                $properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514";
95
+
96
+                $this->proptags = getPropIdsFromStrings($store, $properties);
97
+            }
98
+
99
+            parent::__construct($store, $message);
100
+        }
101
+
102
+        /**
103
+         * Create an exception.
104
+         *
105
+         * @param array        $exception_props  the exception properties (same properties as normal recurring items)
106
+         * @param date         $base_date        the base date of the exception (LOCAL time of non-exception occurrence)
107
+         * @param bool         $delete           true - delete occurrence, false - create new exception or modify existing
108
+         * @param array        $exception_recips true - delete occurrence, false - create new exception or modify existing
109
+         * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
110
+         */
111
+        public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) {
112
+            $baseday = $this->dayStartOf($base_date);
113
+            $basetime = $baseday + $this->recur["startocc"] * 60;
114
+
115
+            // Remove any pre-existing exception on this base date
116
+            if ($this->isException($baseday)) {
117
+                $this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
118
+            }
119
+
120
+            if (!$delete) {
121
+                if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
122
+                    return false;
123
+                }
124
+                // Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
125
+                foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) {
126
+                    if (isset($this->messageprops[$this->proptags[$propname]])) {
127
+                        $props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
128
+                    }
129
+                }
130
+
131
+                $props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
132
+                unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]);
133
+
134
+                $props = $exception_props + $props;
135
+
136
+                // Basedate in the exception attachment is the GMT time at which the original occurrence would have been
137
+                $props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
138
+
139
+                if (!isset($exception_props[$this->proptags["startdate"]])) {
140
+                    $props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
141
+                }
142
+
143
+                if (!isset($exception_props[$this->proptags["duedate"]])) {
144
+                    $props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
145
+                }
146
+
147
+                // synchronize commonstart/commonend with startdate/duedate
148
+                if (isset($props[$this->proptags["startdate"]])) {
149
+                    $props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
150
+                }
151
+
152
+                if (isset($props[$this->proptags["duedate"]])) {
153
+                    $props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
154
+                }
155
+
156
+                // Save the data into an attachment
157
+                $this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
158
+
159
+                $changed_item = [];
160
+
161
+                $changed_item["basedate"] = $baseday;
162
+                $changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
163
+                $changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
164
+
165
+                if (array_key_exists($this->proptags["subject"], $exception_props)) {
166
+                    $changed_item["subject"] = $exception_props[$this->proptags["subject"]];
167
+                }
168
+
169
+                if (array_key_exists($this->proptags["location"], $exception_props)) {
170
+                    $changed_item["location"] = $exception_props[$this->proptags["location"]];
171
+                }
172
+
173
+                if (array_key_exists($this->proptags["label"], $exception_props)) {
174
+                    $changed_item["label"] = $exception_props[$this->proptags["label"]];
175
+                }
176
+
177
+                if (array_key_exists($this->proptags["reminder"], $exception_props)) {
178
+                    $changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
179
+                }
180
+
181
+                if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
182
+                    $changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
183
+                }
184
+
185
+                if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
186
+                    $changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
187
+                }
188
+
189
+                if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
190
+                    $changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
191
+                }
192
+
193
+                // Add the changed occurrence to the list
194
+                array_push($this->recur["changed_occurrences"], $changed_item);
195
+            }
196
+            else {
197
+                // Delete the occurrence by placing it in the deleted occurrences list
198
+                array_push($this->recur["deleted_occurrences"], $baseday);
199
+            }
200
+
201
+            // Turn on hideattachments, because the attachments in this item are the exceptions
202
+            mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
203
+
204
+            // Save recurrence data to message
205
+            $this->saveRecurrence();
206
+
207
+            return true;
208
+        }
209
+
210
+        /**
211
+         * Modifies an existing exception, but only updates the given properties
212
+         * NOTE: You can't remove properties from an exception, only add new ones.
213
+         *
214
+         * @param mixed $exception_props
215
+         * @param mixed $base_date
216
+         * @param mixed $exception_recips
217
+         * @param mixed $copy_attach_from
218
+         */
219
+        public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) {
220
+            if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
221
+                return false;
222
+            }
223
+
224
+            $baseday = $this->dayStartOf($base_date);
225
+            $extomodify = false;
226
+
227
+            for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) {
228
+                if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) {
229
+                    $extomodify = &$this->recur["changed_occurrences"][$i];
230
+                }
231
+            }
232
+
233
+            if (!$extomodify) {
234
+                return false;
235
+            }
236
+
237
+            // remove basedate property as we want to preserve the old value
238
+            // client will send basedate with time part as zero, so discard that value
239
+            unset($exception_props[$this->proptags["basedate"]]);
240
+
241
+            if (array_key_exists($this->proptags["startdate"], $exception_props)) {
242
+                $extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
243
+            }
244
+
245
+            if (array_key_exists($this->proptags["duedate"], $exception_props)) {
246
+                $extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
247
+            }
248
+
249
+            if (array_key_exists($this->proptags["subject"], $exception_props)) {
250
+                $extomodify["subject"] = $exception_props[$this->proptags["subject"]];
251
+            }
252
+
253
+            if (array_key_exists($this->proptags["location"], $exception_props)) {
254
+                $extomodify["location"] = $exception_props[$this->proptags["location"]];
255
+            }
256
+
257
+            if (array_key_exists($this->proptags["label"], $exception_props)) {
258
+                $extomodify["label"] = $exception_props[$this->proptags["label"]];
259
+            }
260
+
261
+            if (array_key_exists($this->proptags["reminder"], $exception_props)) {
262
+                $extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
263
+            }
264
+
265
+            if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
266
+                $extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
267
+            }
268
+
269
+            if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
270
+                $extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
271
+            }
272
+
273
+            if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
274
+                $extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
275
+            }
276
+
277
+            $exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
278
+
279
+            // synchronize commonstart/commonend with startdate/duedate
280
+            if (isset($exception_props[$this->proptags["startdate"]])) {
281
+                $exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
282
+            }
283
+
284
+            if (isset($exception_props[$this->proptags["duedate"]])) {
285
+                $exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
286
+            }
287
+
288
+            $attach = $this->getExceptionAttachment($baseday);
289
+            if (!$attach) {
290
+                if ($copy_attach_from) {
291
+                    $this->deleteExceptionAttachment($base_date);
292
+                    $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
293
+                }
294
+                else {
295
+                    $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
296
+                }
297
+            }
298
+            else {
299
+                $message = mapi_attach_openobj($attach, MAPI_MODIFY);
300
+
301
+                // Set exception properties on embedded message and save
302
+                mapi_setprops($message, $exception_props);
303
+                $this->setExceptionRecipients($message, $exception_recips, false);
304
+                mapi_savechanges($message);
305
+
306
+                // If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
307
+                // on the attachment which holds the embedded msg and save everything.
308
+                $props = [];
309
+                if (isset($exception_props[$this->proptags["startdate"]])) {
310
+                    $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
311
+                }
312
+                if (isset($exception_props[$this->proptags["duedate"]])) {
313
+                    $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
314
+                }
315
+                if (!empty($props)) {
316
+                    mapi_setprops($attach, $props);
317
+                }
318
+
319
+                mapi_savechanges($attach);
320
+            }
321
+
322
+            // Save recurrence data to message
323
+            $this->saveRecurrence();
324
+
325
+            return true;
326
+        }
327
+
328
+        // Checks to see if the following is true:
329
+        // 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
330
+        // 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
331
+        //
332
+        // Both $basedate and $start are in LOCAL time
333
+        public function isValidExceptionDate($basedate, $start) {
334
+            // The way we do this is to look at the days that we're 'moving' the item in the exception. Each
335
+            // of these days may only contain the item that we're modifying. Any other item violates the rules.
336
+
337
+            if ($this->isException($basedate)) {
338
+                // If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
339
+                // the exception used to be.
340
+                $oldexception = $this->getChangeException($basedate);
341
+                $prevday = $this->dayStartOf($oldexception["start"]);
342
+            }
343
+            else {
344
+                // If its a new exception, we want to look at the original placement of this item.
345
+                $prevday = $basedate;
346
+            }
347
+
348
+            $startday = $this->dayStartOf($start);
349
+
350
+            // Get all the occurrences on the days between the basedate (may be reversed)
351
+            if ($prevday < $startday) {
352
+                $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
353
+            }
354
+            else {
355
+                $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
356
+            }
357
+
358
+            // There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
359
+            // then we abort the change, since one of the rules has been violated.
360
+            return count($items) == 1;
361
+        }
362
+
363
+        /**
364
+         * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:.
365
+         *
366
+         * Both must be true:
367
+         * - reminder time of this item is not before the starttime of the previous recurring item
368
+         * - reminder time of the next item is not before the starttime of this item
369
+         *
370
+         * @param date   $basedate        the base date of the exception (LOCAL time of non-exception occurrence)
371
+         * @param string $reminderminutes reminder minutes which is set of the item
372
+         * @param date   $startdate       the startdate of the selected item
373
+         * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
374
+         */
375
+        public function isValidReminderTime($basedate, $reminderminutes, $startdate) {
376
+            // get all occurrence items before the seleceted items occurrence starttime
377
+            $occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
378
+
379
+            if (!empty($occitems)) {
380
+                // as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
381
+                $previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
382
+
383
+                // if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
384
+                if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
385
+                    return false;
386
+                }
387
+            }
388
+
389
+            // Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
390
+            $currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
391
+
392
+            // If there are another two occurrences, then the first is the current occurrence, and the one after that
393
+            // is the next occurrence.
394
+            if (count($currentOcc) > 1) {
395
+                $next = $currentOcc[1];
396
+                // Get reminder time of the next occurrence.
397
+                $nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
398
+                // If the reminder time of the next item is before the start of this item, then that's not allowed
399
+                if ($nextOccReminderTime <= $startdate) {
400
+                    return false;
401
+                }
402
+            }
403
+
404
+            // All was ok
405
+            return true;
406
+        }
407
+
408
+        public function setRecurrence($tz, $recur) {
409
+            // only reset timezone if specified
410
+            if ($tz) {
411
+                $this->tz = $tz;
412
+            }
413
+
414
+            $this->recur = $recur;
415
+
416
+            if (!isset($this->recur["changed_occurrences"])) {
417
+                $this->recur["changed_occurrences"] = [];
418
+            }
419
+
420
+            if (!isset($this->recur["deleted_occurrences"])) {
421
+                $this->recur["deleted_occurrences"] = [];
422
+            }
423
+
424
+            $this->deleteAttachments();
425
+            $this->saveRecurrence();
426
+
427
+            // if client has not set the recurring_pattern then we should generate it and save it
428
+            $messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
429
+            if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
430
+                $this->saveRecurrencePattern();
431
+            }
432
+        }
433
+
434
+        // Returns the start or end time of the occurrence on the given base date.
435
+        // This assumes that the basedate you supply is in LOCAL time
436
+        public function getOccurrenceStart($basedate) {
437
+            $daystart = $this->dayStartOf($basedate);
438
+
439
+            return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
440
+        }
441
+
442
+        public function getOccurrenceEnd($basedate) {
443
+            $daystart = $this->dayStartOf($basedate);
444
+
445
+            return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
446
+        }
447
+
448
+        // Backwards compatible code
449
+        public function getOccurrenceStart($basedate) {
450
+            return $this->getOccurrenceStart($basedate);
451
+        }
452
+
453
+        public function getOccurrenceEnd($basedate) {
454
+            return $this->getOccurrenceEnd($basedate);
455
+        }
456
+
457
+        /**
458
+         * This function returns the next remindertime starting from $timestamp
459
+         * When no next reminder exists, false is returned.
460
+         *
461
+         * Note: Before saving this new reminder time (when snoozing), you must check for
462
+         *       yourself if this reminder time is earlier than your snooze time, else
463
+         *       use your snooze time and not this reminder time.
464
+         *
465
+         * @param mixed $timestamp
466
+         */
467
+        public function getNextReminderTime($timestamp) {
468
+            /**
469
+             * Get next item from now until forever, but max 1 item with reminder set
470
+             * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
471
+             * Here for getting next 10 occurrences assuming that next here we will be able to find
472
+             * nextreminder occurrence in 10 occureneces.
473
+             */
474
+            $items = $this->getItems($timestamp, 0x7FF00000, 10, true);
475
+
476
+            // Initially setting nextreminder to false so when no next reminder exists, false is returned.
477
+            $nextreminder = false;
478
+            /*
479 479
 			 * Loop through all reminder which we get in items variable
480 480
 			 * and check whether the remindertime is greater than timestamp.
481 481
 			 * On the first occurrence of greater nextreminder break the loop
482 482
 			 * and return the value to calling function.
483 483
 			 */
484
-			for ($i = 0, $len = count($items); $i < $len; ++$i) {
485
-				$item = $items[$i];
486
-				$tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
487
-
488
-				// If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
489
-				if ($tempnextreminder > $timestamp) {
490
-					$nextreminder = $tempnextreminder;
491
-
492
-					break;
493
-				}
494
-			}
495
-
496
-			return $nextreminder;
497
-		}
498
-
499
-		/**
500
-		 * Note: Static function, more like a utility function.
501
-		 *
502
-		 * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
503
-		 * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
504
-		 * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
505
-		 * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
506
-		 *
507
-		 * @param $store resource The store in which the calendar resides
508
-		 * @param $calendar resource The calendar to get the items from
509
-		 * @param $viewstart int Timestamp of beginning of view window
510
-		 * @param $viewend int Timestamp of end of view window
511
-		 * @param $propsrequested array Array of properties to return
512
-		 * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
513
-		 *                    expanded so that it seems that there are only many single appointments in the table.
514
-		 */
515
-		public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
516
-			return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
517
-		}
518
-
519
-		/*
484
+            for ($i = 0, $len = count($items); $i < $len; ++$i) {
485
+                $item = $items[$i];
486
+                $tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
487
+
488
+                // If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
489
+                if ($tempnextreminder > $timestamp) {
490
+                    $nextreminder = $tempnextreminder;
491
+
492
+                    break;
493
+                }
494
+            }
495
+
496
+            return $nextreminder;
497
+        }
498
+
499
+        /**
500
+         * Note: Static function, more like a utility function.
501
+         *
502
+         * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
503
+         * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
504
+         * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
505
+         * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
506
+         *
507
+         * @param $store resource The store in which the calendar resides
508
+         * @param $calendar resource The calendar to get the items from
509
+         * @param $viewstart int Timestamp of beginning of view window
510
+         * @param $viewend int Timestamp of end of view window
511
+         * @param $propsrequested array Array of properties to return
512
+         * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
513
+         *                    expanded so that it seems that there are only many single appointments in the table.
514
+         */
515
+        public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
516
+            return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
517
+        }
518
+
519
+        /*
520 520
 		 * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY
521 521
 		 *****************************************************************************************************************
522 522
 		 */
523 523
 
524
-		/**
525
-		 * Generates and stores recurrence pattern string to recurring_pattern property.
526
-		 */
527
-		public function saveRecurrencePattern() {
528
-			// Start formatting the properties in such a way we can apply
529
-			// them directly into the recurrence pattern.
530
-			$type = $this->recur['type'];
531
-			$everyn = $this->recur['everyn'];
532
-			$start = $this->recur['start'];
533
-			$end = $this->recur['end'];
534
-			$term = $this->recur['term'];
535
-			$numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false;
536
-			$startocc = $this->recur['startocc'];
537
-			$endocc = $this->recur['endocc'];
538
-			$pattern = '';
539
-			$occSingleDayRank = false;
540
-			$occTimeRange = ($startocc != 0 && $endocc != 0);
541
-
542
-			switch ($type) {
543
-				// Daily
544
-				case 0x0A:
545
-					if ($everyn == 1) {
546
-						$type = _('workday');
547
-						$occSingleDayRank = true;
548
-					}
549
-					elseif ($everyn == (24 * 60)) {
550
-						$type = _('day');
551
-						$occSingleDayRank = true;
552
-					}
553
-					else {
554
-						$everyn /= (24 * 60);
555
-						$type = _('days');
556
-						$occSingleDayRank = false;
557
-					}
558
-
559
-					break;
560
-				// Weekly
561
-				case 0x0B:
562
-					if ($everyn == 1) {
563
-						$type = _('week');
564
-						$occSingleDayRank = true;
565
-					}
566
-					else {
567
-						$type = _('weeks');
568
-						$occSingleDayRank = false;
569
-					}
570
-
571
-					break;
572
-				// Monthly
573
-				case 0x0C:
574
-					if ($everyn == 1) {
575
-						$type = _('month');
576
-						$occSingleDayRank = true;
577
-					}
578
-					else {
579
-						$type = _('months');
580
-						$occSingleDayRank = false;
581
-					}
582
-
583
-					break;
584
-				// Yearly
585
-				case 0x0D:
586
-					if ($everyn <= 12) {
587
-						$everyn = 1;
588
-						$type = _('year');
589
-						$occSingleDayRank = true;
590
-					}
591
-					else {
592
-						$everyn = $everyn / 12;
593
-						$type = _('years');
594
-						$occSingleDayRank = false;
595
-					}
596
-
597
-					break;
598
-			}
599
-
600
-			// get timings of the first occurrence
601
-			$firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
602
-			$firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
603
-
604
-			$start = gmdate(_('d-m-Y'), $firstoccstartdate);
605
-			$end = gmdate(_('d-m-Y'), $firstoccenddate);
606
-			$startocc = gmdate(_('G:i'), $firstoccstartdate);
607
-			$endocc = gmdate(_('G:i'), $firstoccenddate);
608
-
609
-			// Based on the properties, we need to generate the recurrence pattern string.
610
-			// This is obviously very easy since we can simply concatenate a bunch of strings,
611
-			// however this messes up translations for languages which order their words
612
-			// differently.
613
-			// To improve translation quality we create a series of default strings, in which
614
-			// we only have to fill in the correct variables. The base string is thus selected
615
-			// based on the available properties.
616
-			if ($term == 0x23) {
617
-				// Never ends
618
-				if ($occTimeRange) {
619
-					if ($occSingleDayRank) {
620
-						$pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
621
-					}
622
-					else {
623
-						$pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
624
-					}
625
-				}
626
-				else {
627
-					if ($occSingleDayRank) {
628
-						$pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start);
629
-					}
630
-					else {
631
-						$pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start);
632
-					}
633
-				}
634
-			}
635
-			elseif ($term == 0x22) {
636
-				// After a number of times
637
-				if ($occTimeRange) {
638
-					if ($occSingleDayRank) {
639
-						$pattern = sprintf(ngettext(
640
-							'Occurs every %s effective %s for %s occurrence from %s to %s.',
641
-							'Occurs every %s effective %s for %s occurrences from %s to %s.',
642
-							$numocc
643
-						), $type, $start, $numocc, $startocc, $endocc);
644
-					}
645
-					else {
646
-						$pattern = sprintf(ngettext(
647
-							'Occurs every %s %s effective %s for %s occurrence from %s to %s.',
648
-							'Occurs every %s %s effective %s for %s occurrences %s to %s.',
649
-							$numocc
650
-						), $everyn, $type, $start, $numocc, $startocc, $endocc);
651
-					}
652
-				}
653
-				else {
654
-					if ($occSingleDayRank) {
655
-						$pattern = sprintf(ngettext(
656
-							'Occurs every %s effective %s for %s occurrence.',
657
-							'Occurs every %s effective %s for %s occurrences.',
658
-							$numocc
659
-						), $type, $start, $numocc);
660
-					}
661
-					else {
662
-						$pattern = sprintf(ngettext(
663
-							'Occurs every %s %s effective %s for %s occurrence.',
664
-							'Occurs every %s %s effective %s for %s occurrences.',
665
-							$numocc
666
-						), $everyn, $type, $start, $numocc);
667
-					}
668
-				}
669
-			}
670
-			elseif ($term == 0x21) {
671
-				// After the given enddate
672
-				if ($occTimeRange) {
673
-					if ($occSingleDayRank) {
674
-						$pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
675
-					}
676
-					else {
677
-						$pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
678
-					}
679
-				}
680
-				else {
681
-					if ($occSingleDayRank) {
682
-						$pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end);
683
-					}
684
-					else {
685
-						$pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
686
-					}
687
-				}
688
-			}
689
-
690
-			if (!empty($pattern)) {
691
-				mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
692
-			}
693
-		}
694
-
695
-		/*
524
+        /**
525
+         * Generates and stores recurrence pattern string to recurring_pattern property.
526
+         */
527
+        public function saveRecurrencePattern() {
528
+            // Start formatting the properties in such a way we can apply
529
+            // them directly into the recurrence pattern.
530
+            $type = $this->recur['type'];
531
+            $everyn = $this->recur['everyn'];
532
+            $start = $this->recur['start'];
533
+            $end = $this->recur['end'];
534
+            $term = $this->recur['term'];
535
+            $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false;
536
+            $startocc = $this->recur['startocc'];
537
+            $endocc = $this->recur['endocc'];
538
+            $pattern = '';
539
+            $occSingleDayRank = false;
540
+            $occTimeRange = ($startocc != 0 && $endocc != 0);
541
+
542
+            switch ($type) {
543
+                // Daily
544
+                case 0x0A:
545
+                    if ($everyn == 1) {
546
+                        $type = _('workday');
547
+                        $occSingleDayRank = true;
548
+                    }
549
+                    elseif ($everyn == (24 * 60)) {
550
+                        $type = _('day');
551
+                        $occSingleDayRank = true;
552
+                    }
553
+                    else {
554
+                        $everyn /= (24 * 60);
555
+                        $type = _('days');
556
+                        $occSingleDayRank = false;
557
+                    }
558
+
559
+                    break;
560
+                // Weekly
561
+                case 0x0B:
562
+                    if ($everyn == 1) {
563
+                        $type = _('week');
564
+                        $occSingleDayRank = true;
565
+                    }
566
+                    else {
567
+                        $type = _('weeks');
568
+                        $occSingleDayRank = false;
569
+                    }
570
+
571
+                    break;
572
+                // Monthly
573
+                case 0x0C:
574
+                    if ($everyn == 1) {
575
+                        $type = _('month');
576
+                        $occSingleDayRank = true;
577
+                    }
578
+                    else {
579
+                        $type = _('months');
580
+                        $occSingleDayRank = false;
581
+                    }
582
+
583
+                    break;
584
+                // Yearly
585
+                case 0x0D:
586
+                    if ($everyn <= 12) {
587
+                        $everyn = 1;
588
+                        $type = _('year');
589
+                        $occSingleDayRank = true;
590
+                    }
591
+                    else {
592
+                        $everyn = $everyn / 12;
593
+                        $type = _('years');
594
+                        $occSingleDayRank = false;
595
+                    }
596
+
597
+                    break;
598
+            }
599
+
600
+            // get timings of the first occurrence
601
+            $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
602
+            $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
603
+
604
+            $start = gmdate(_('d-m-Y'), $firstoccstartdate);
605
+            $end = gmdate(_('d-m-Y'), $firstoccenddate);
606
+            $startocc = gmdate(_('G:i'), $firstoccstartdate);
607
+            $endocc = gmdate(_('G:i'), $firstoccenddate);
608
+
609
+            // Based on the properties, we need to generate the recurrence pattern string.
610
+            // This is obviously very easy since we can simply concatenate a bunch of strings,
611
+            // however this messes up translations for languages which order their words
612
+            // differently.
613
+            // To improve translation quality we create a series of default strings, in which
614
+            // we only have to fill in the correct variables. The base string is thus selected
615
+            // based on the available properties.
616
+            if ($term == 0x23) {
617
+                // Never ends
618
+                if ($occTimeRange) {
619
+                    if ($occSingleDayRank) {
620
+                        $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
621
+                    }
622
+                    else {
623
+                        $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
624
+                    }
625
+                }
626
+                else {
627
+                    if ($occSingleDayRank) {
628
+                        $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start);
629
+                    }
630
+                    else {
631
+                        $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start);
632
+                    }
633
+                }
634
+            }
635
+            elseif ($term == 0x22) {
636
+                // After a number of times
637
+                if ($occTimeRange) {
638
+                    if ($occSingleDayRank) {
639
+                        $pattern = sprintf(ngettext(
640
+                            'Occurs every %s effective %s for %s occurrence from %s to %s.',
641
+                            'Occurs every %s effective %s for %s occurrences from %s to %s.',
642
+                            $numocc
643
+                        ), $type, $start, $numocc, $startocc, $endocc);
644
+                    }
645
+                    else {
646
+                        $pattern = sprintf(ngettext(
647
+                            'Occurs every %s %s effective %s for %s occurrence from %s to %s.',
648
+                            'Occurs every %s %s effective %s for %s occurrences %s to %s.',
649
+                            $numocc
650
+                        ), $everyn, $type, $start, $numocc, $startocc, $endocc);
651
+                    }
652
+                }
653
+                else {
654
+                    if ($occSingleDayRank) {
655
+                        $pattern = sprintf(ngettext(
656
+                            'Occurs every %s effective %s for %s occurrence.',
657
+                            'Occurs every %s effective %s for %s occurrences.',
658
+                            $numocc
659
+                        ), $type, $start, $numocc);
660
+                    }
661
+                    else {
662
+                        $pattern = sprintf(ngettext(
663
+                            'Occurs every %s %s effective %s for %s occurrence.',
664
+                            'Occurs every %s %s effective %s for %s occurrences.',
665
+                            $numocc
666
+                        ), $everyn, $type, $start, $numocc);
667
+                    }
668
+                }
669
+            }
670
+            elseif ($term == 0x21) {
671
+                // After the given enddate
672
+                if ($occTimeRange) {
673
+                    if ($occSingleDayRank) {
674
+                        $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
675
+                    }
676
+                    else {
677
+                        $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
678
+                    }
679
+                }
680
+                else {
681
+                    if ($occSingleDayRank) {
682
+                        $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end);
683
+                    }
684
+                    else {
685
+                        $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
686
+                    }
687
+                }
688
+            }
689
+
690
+            if (!empty($pattern)) {
691
+                mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
692
+            }
693
+        }
694
+
695
+        /*
696 696
 		 * Remove an exception by base_date. This is the base date in local daystart time
697 697
 		 */
698
-		public function deleteException($base_date) {
699
-			// Remove all exceptions on $base_date from the deleted and changed occurrences lists
700
-
701
-			// Remove all items in $todelete from deleted_occurrences
702
-			$new = [];
703
-
704
-			foreach ($this->recur["deleted_occurrences"] as $entry) {
705
-				if ($entry != $base_date) {
706
-					$new[] = $entry;
707
-				}
708
-			}
709
-			$this->recur["deleted_occurrences"] = $new;
710
-
711
-			$new = [];
712
-
713
-			foreach ($this->recur["changed_occurrences"] as $entry) {
714
-				if (!$this->isSameDay($entry["basedate"], $base_date)) {
715
-					$new[] = $entry;
716
-				}
717
-				else {
718
-					$this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
719
-				}
720
-			}
721
-
722
-			$this->recur["changed_occurrences"] = $new;
723
-		}
724
-
725
-		/**
726
-		 * Function which saves the exception data in an attachment.
727
-		 *
728
-		 * @param array        $exception_props  the exception data (like any other MAPI appointment)
729
-		 * @param array        $exception_recips list of recipients
730
-		 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
731
-		 */
732
-		public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) {
733
-			// Create new attachment.
734
-			$attachment = mapi_message_createattach($this->message);
735
-			$props = [];
736
-			$props[PR_ATTACHMENT_FLAGS] = 2;
737
-			$props[PR_ATTACHMENT_HIDDEN] = true;
738
-			$props[PR_ATTACHMENT_LINKID] = 0;
739
-			$props[PR_ATTACH_FLAGS] = 0;
740
-			$props[PR_ATTACH_METHOD] = 5;
741
-			$props[PR_DISPLAY_NAME] = "Exception";
742
-			$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
743
-			$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
744
-			mapi_setprops($attachment, $props);
745
-
746
-			$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
747
-
748
-			if ($copy_attach_from) {
749
-				$attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
750
-				if ($attachmentTable) {
751
-					$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
752
-
753
-					foreach ($attachments as $attach_props) {
754
-						$attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
755
-						$attach_newResourceMsg = mapi_message_createattach($imessage);
756
-						mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
757
-						mapi_savechanges($attach_newResourceMsg);
758
-					}
759
-				}
760
-			}
761
-
762
-			$props = $props + $exception_props;
763
-
764
-			// FIXME: the following piece of code is written to fix the creation
765
-			// of an exception. This is only a quickfix as it is not yet possible
766
-			// to change an existing exception.
767
-			// remove mv properties when needed
768
-			foreach ($props as $propTag => $propVal) {
769
-				if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) {
770
-					unset($props[$propTag]);
771
-				}
772
-			}
773
-
774
-			mapi_setprops($imessage, $props);
775
-
776
-			$this->setExceptionRecipients($imessage, $exception_recips, true);
777
-
778
-			mapi_savechanges($imessage);
779
-			mapi_savechanges($attachment);
780
-		}
781
-
782
-		/**
783
-		 * Function which deletes the attachment of an exception.
784
-		 *
785
-		 * @param date $base_date base date of the attachment. Should be in GMT. The attachment
786
-		 *                        actually saves the real time of the original date, so we have
787
-		 *                        to check whether it's on the same day.
788
-		 */
789
-		public function deleteExceptionAttachment($base_date) {
790
-			$attachments = mapi_message_getattachmenttable($this->message);
791
-			$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]);
792
-
793
-			foreach ($attachTable as $attachRow) {
794
-				$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
795
-				$exception = mapi_attach_openobj($tempattach);
796
-
797
-				$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
798
-
799
-				if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
800
-					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
801
-				}
802
-			}
803
-		}
804
-
805
-		/**
806
-		 * Function which deletes all attachments of a message.
807
-		 */
808
-		public function deleteAttachments() {
809
-			$attachments = mapi_message_getattachmenttable($this->message);
810
-			$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
811
-
812
-			foreach ($attachTable as $attachRow) {
813
-				if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
814
-					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
815
-				}
816
-			}
817
-		}
818
-
819
-		/**
820
-		 * Get an exception attachment based on its basedate.
821
-		 *
822
-		 * @param mixed $base_date
823
-		 */
824
-		public function getExceptionAttachment($base_date) {
825
-			// Retrieve only exceptions which are stored as embedded messages
826
-			$attach_res = [
827
-				RES_AND,
828
-				[
829
-					[
830
-						RES_PROPERTY,
831
-						[
832
-							RELOP => RELOP_EQ,
833
-							ULPROPTAG => PR_ATTACH_METHOD,
834
-							VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
835
-						],
836
-					],
837
-				],
838
-			];
839
-			$attachments = mapi_message_getattachmenttable($this->message);
840
-			$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
841
-
842
-			if (is_array($attachRows)) {
843
-				foreach ($attachRows as $attachRow) {
844
-					$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
845
-					$exception = mapi_attach_openobj($tempattach);
846
-
847
-					$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
848
-
849
-					if (!isset($data[$this->proptags["basedate"]])) {
850
-						// if no basedate found then it could be embedded message so ignore it
851
-						// we need proper restriction to exclude embedded messages as well
852
-						continue;
853
-					}
854
-
855
-					if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
856
-						return $tempattach;
857
-					}
858
-				}
859
-			}
860
-
861
-			return false;
862
-		}
863
-
864
-		/**
865
-		 * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
866
-		 * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
867
-		 * - The occurrence isn't specified as a deleted occurrence.
868
-		 *
869
-		 * @param array $items        reference to the array to be added to
870
-		 * @param date  $start        start of timeframe in GMT TIME
871
-		 * @param date  $end          end of timeframe in GMT TIME
872
-		 * @param date  $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
873
-		 * @param int   $startocc     start of occurrence since beginning of day in minutes
874
-		 * @param int   $endocc       end of occurrence since beginning of day in minutes
875
-		 * @param int   $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
876
-		 * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
877
-		 */
878
-		public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
879
-			$exception = $this->isException($basedate);
880
-			if ($exception) {
881
-				return false;
882
-			}
883
-			$occstart = $basedate + $startocc * 60;
884
-			$occend = $basedate + $endocc * 60;
885
-
886
-			// Convert to GMT
887
-			$occstart = $this->toGMT($tz, $occstart);
888
-			$occend = $this->toGMT($tz, $occend);
889
-
890
-			/**
891
-			 * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
892
-			 * see any part of the appointment. Partial overlaps DO match.
893
-			 *
894
-			 * SECOND PART : check if occurrence is not a zero duration occurrence which
895
-			 * starts at 00:00 and ends on 00:00. if it is so, then process
896
-			 * the occurrence and send it in response.
897
-			 */
898
-			if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
899
-				return;
900
-			}
901
-
902
-			// Properties for this occurrence are the same as the main object,
903
-			// With these properties overridden
904
-			$newitem = $this->messageprops;
905
-			$newitem[$this->proptags["startdate"]] = $occstart;
906
-			$newitem[$this->proptags["duedate"]] = $occend;
907
-			$newitem[$this->proptags["commonstart"]] = $occstart;
908
-			$newitem[$this->proptags["commonend"]] = $occend;
909
-			$newitem["basedate"] = $basedate;
910
-
911
-			// If reminderonly is set, only add reminders
912
-			if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
913
-				return;
914
-			}
915
-
916
-			$items[] = $newitem;
917
-		}
918
-
919
-		/**
920
-		 * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
921
-		 *
922
-		 * @param array $items reference to the array to be added to
923
-		 * @param date  $start start of timeframe in GMT TIME
924
-		 * @param date  $end   end of timeframe in GMT TIME
925
-		 */
926
-		public function processExceptionItems(&$items, $start, $end) {
927
-			$limit = 0;
928
-			foreach ($this->recur["changed_occurrences"] as $exception) {
929
-				// Convert to GMT
930
-				$occstart = $this->toGMT($this->tz, $exception["start"]);
931
-				$occend = $this->toGMT($this->tz, $exception["end"]);
932
-
933
-				// Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
934
-				// see any part of the appointment. Partial overlaps DO match.
935
-				if ($occstart >= $end || $occend <= $start) {
936
-					continue;
937
-				}
938
-
939
-				array_push($items, $this->getExceptionProperties($exception));
940
-				if ((count($items) == $limit)) {
941
-					break;
942
-				}
943
-			}
944
-		}
945
-
946
-		/**
947
-		 * Function which verifies if on the given date an exception, delete or change, occurs.
948
-		 *
949
-		 * @param date  $date     the date
950
-		 * @param mixed $basedate
951
-		 *
952
-		 * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date
953
-		 */
954
-		public function isException($basedate) {
955
-			if ($this->isDeleteException($basedate)) {
956
-				return true;
957
-			}
958
-
959
-			if ($this->getChangeException($basedate) != false) {
960
-				return true;
961
-			}
962
-
963
-			return false;
964
-		}
965
-
966
-		/**
967
-		 * Returns TRUE if there is a DELETE exception on the given base date.
968
-		 *
969
-		 * @param mixed $basedate
970
-		 */
971
-		public function isDeleteException($basedate) {
972
-			// Check if the occurrence is deleted on the specified date
973
-			foreach ($this->recur["deleted_occurrences"] as $deleted) {
974
-				if ($this->isSameDay($deleted, $basedate)) {
975
-					return true;
976
-				}
977
-			}
978
-
979
-			return false;
980
-		}
981
-
982
-		/**
983
-		 * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
984
-		 *
985
-		 * @param mixed $basedate
986
-		 */
987
-		public function getChangeException($basedate) {
988
-			// Check if the occurrence is modified on the specified date
989
-			foreach ($this->recur["changed_occurrences"] as $changed) {
990
-				if ($this->isSameDay($changed["basedate"], $basedate)) {
991
-					return $changed;
992
-				}
993
-			}
994
-
995
-			return false;
996
-		}
997
-
998
-		/**
999
-		 * Function to see if two dates are on the same day.
1000
-		 *
1001
-		 * @param date  $time1 date 1
1002
-		 * @param date  $time2 date 2
1003
-		 * @param mixed $date1
1004
-		 * @param mixed $date2
1005
-		 *
1006
-		 * @return bool Returns TRUE when both dates are on the same day
1007
-		 */
1008
-		public function isSameDay($date1, $date2) {
1009
-			$time1 = $this->gmtime($date1);
1010
-			$time2 = $this->gmtime($date2);
1011
-
1012
-			return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
1013
-		}
1014
-
1015
-		/**
1016
-		 * Function to get all properties of a single changed exception.
1017
-		 *
1018
-		 * @param date  $date      base date of exception
1019
-		 * @param mixed $exception
1020
-		 *
1021
-		 * @return array associative array of properties for the exception, compatible with
1022
-		 */
1023
-		public function getExceptionProperties($exception) {
1024
-			// Exception has same properties as main object, with some properties overridden:
1025
-			$item = $this->messageprops;
1026
-
1027
-			// Special properties
1028
-			$item["exception"] = true;
1029
-			$item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time !
1030
-
1031
-			// MAPI-compatible properties (you can handle an exception as a normal calendar item like this)
1032
-			$item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]);
1033
-			$item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]);
1034
-			$item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]];
1035
-			$item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]];
1036
-
1037
-			if (isset($exception["subject"])) {
1038
-				$item[$this->proptags["subject"]] = $exception["subject"];
1039
-			}
1040
-
1041
-			if (isset($exception["label"])) {
1042
-				$item[$this->proptags["label"]] = $exception["label"];
1043
-			}
1044
-
1045
-			if (isset($exception["alldayevent"])) {
1046
-				$item[$this->proptags["alldayevent"]] = $exception["alldayevent"];
1047
-			}
1048
-
1049
-			if (isset($exception["location"])) {
1050
-				$item[$this->proptags["location"]] = $exception["location"];
1051
-			}
1052
-
1053
-			if (isset($exception["remind_before"])) {
1054
-				$item[$this->proptags["reminder_minutes"]] = $exception["remind_before"];
1055
-			}
1056
-
1057
-			if (isset($exception["reminder_set"])) {
1058
-				$item[$this->proptags["reminder"]] = $exception["reminder_set"];
1059
-			}
1060
-
1061
-			if (isset($exception["busystatus"])) {
1062
-				$item[$this->proptags["busystatus"]] = $exception["busystatus"];
1063
-			}
1064
-
1065
-			return $item;
1066
-		}
1067
-
1068
-		/**
1069
-		 * Function which sets recipients for an exception.
1070
-		 *
1071
-		 * The $exception_recips can be provided in 2 ways:
1072
-		 *  - A delta which indicates which recipients must be added, removed or deleted.
1073
-		 *  - A complete array of the recipients which should be applied to the message.
1074
-		 *
1075
-		 * The first option is preferred as it will require less work to be executed.
1076
-		 *
1077
-		 * @param resource $message          exception attachment of recurring item
1078
-		 * @param array    $exception_recips list of recipients
1079
-		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1080
-		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1081
-		 *                                   be applied.
1082
-		 */
1083
-		public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1084
-			if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1085
-				$this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1086
-			}
1087
-			else {
1088
-				$this->setAllExceptionRecipients($message, $exception_recips);
1089
-			}
1090
-		}
1091
-
1092
-		/**
1093
-		 * Function which applies the provided delta for recipients changes to the exception.
1094
-		 *
1095
-		 * The $exception_recips should be an array containing the following keys:
1096
-		 *  - "add": this contains an array of recipients which must be added
1097
-		 *  - "remove": This contains an array of recipients which must be removed
1098
-		 *  - "modify": This contains an array of recipients which must be modified
1099
-		 *
1100
-		 * @param resource $message          exception attachment of recurring item
1101
-		 * @param array    $exception_recips list of recipients
1102
-		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1103
-		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1104
-		 *                                   be applied.
1105
-		 * @param mixed    $exception
1106
-		 */
1107
-		public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) {
1108
-			// Check if the recipients from the original message should be copied,
1109
-			// if so, open the recipient table of the parent message and apply all
1110
-			// rows on the target recipient.
1111
-			if ($copy_orig_recips === true) {
1112
-				$origTable = mapi_message_getrecipienttable($this->message);
1113
-				$recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
1114
-				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
1115
-			}
1116
-
1117
-			// Add organizer to meeting only if it is not organized.
1118
-			$msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1119
-			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1120
-				$this->addOrganizer($msgprops, $exception_recips['add']);
1121
-			}
1122
-
1123
-			// Remove all deleted recipients
1124
-			if (isset($exception_recips['remove'])) {
1125
-				foreach ($exception_recips['remove'] as &$recip) {
1126
-					if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1127
-						$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1128
-					}
1129
-					else {
1130
-						$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1131
-					}
1132
-					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;        // No Response required
1133
-				}
1134
-				unset($recip);
1135
-				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1136
-			}
1137
-
1138
-			// Add all new recipients
1139
-			if (isset($exception_recips['add'])) {
1140
-				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1141
-			}
1142
-
1143
-			// Modify the existing recipients
1144
-			if (isset($exception_recips['modify'])) {
1145
-				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1146
-			}
1147
-		}
1148
-
1149
-		/**
1150
-		 * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1151
-		 *
1152
-		 * The $exception_recips should be an array containing all recipients which must be applied
1153
-		 * to the exception. This will copy all recipients from the original message and then start filter
1154
-		 * out all recipients which are not provided by the $exception_recips list.
1155
-		 *
1156
-		 * @param resource $message          exception attachment of recurring item
1157
-		 * @param array    $exception_recips list of recipients
1158
-		 */
1159
-		public function setAllExceptionRecipients($message, $exception_recips) {
1160
-			$deletedRecipients = [];
1161
-			$useMessageRecipients = false;
1162
-
1163
-			$recipientTable = mapi_message_getrecipienttable($message);
1164
-			$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1165
-
1166
-			if (empty($recipientRows)) {
1167
-				$useMessageRecipients = true;
1168
-				$recipientTable = mapi_message_getrecipienttable($this->message);
1169
-				$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1170
-			}
1171
-
1172
-			// Add organizer to meeting only if it is not organized.
1173
-			$msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1174
-			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1175
-				$this->addOrganizer($msgprops, $exception_recips);
1176
-			}
1177
-
1178
-			if (!empty($exception_recips)) {
1179
-				foreach ($recipientRows as $key => $recipient) {
1180
-					$found = false;
1181
-					foreach ($exception_recips as $excep_recip) {
1182
-						if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
1183
-							$found = true;
1184
-						}
1185
-					}
1186
-
1187
-					if (!$found) {
1188
-						$foundInDeletedRecipients = false;
1189
-						// Look if the $recipient is in the list of deleted recipients
1190
-						if (!empty($deletedRecipients)) {
1191
-							foreach ($deletedRecipients as $recip) {
1192
-								if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1193
-									$foundInDeletedRecipients = true;
1194
-
1195
-									break;
1196
-								}
1197
-							}
1198
-						}
1199
-
1200
-						// If recipient is not in list of deleted recipient, add him
1201
-						if (!$foundInDeletedRecipients) {
1202
-							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1203
-								$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1204
-							}
1205
-							else {
1206
-								$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1207
-							}
1208
-							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;    // No Response required
1209
-							$deletedRecipients[] = $recipient;
1210
-						}
1211
-					}
1212
-
1213
-					// When $message contains a non-empty recipienttable, we must delete the recipients
1214
-					// before re-adding them. However, when $message is doesn't contain any recipients,
1215
-					// we are using the recipient table of the original message ($this->message)
1216
-					// rather then $message. In that case, we don't need to remove the recipients
1217
-					// from the $message, as the recipient table is already empty, and
1218
-					// mapi_message_modifyrecipients() will throw an error.
1219
-					if ($useMessageRecipients === false) {
1220
-						mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1221
-					}
1222
-				}
1223
-				$exception_recips = array_merge($exception_recips, $deletedRecipients);
1224
-			}
1225
-			else {
1226
-				$exception_recips = $recipientRows;
1227
-			}
1228
-
1229
-			if (!empty($exception_recips)) {
1230
-				// Set the new list of recipients on the exception message, this also removes the existing recipients
1231
-				mapi_message_modifyrecipients($message, 0, $exception_recips);
1232
-			}
1233
-		}
1234
-
1235
-		/**
1236
-		 * Function returns basedates of all changed occurrences.
1237
-		 *
1238
-		 *@return array array(
1239
-		 * 0 => 123459321
1240
-		 * )
1241
-		 */
1242
-		public function getAllExceptions() {
1243
-			$result = false;
1244
-			if (!empty($this->recur["changed_occurrences"])) {
1245
-				$result = [];
1246
-				foreach ($this->recur["changed_occurrences"] as $exception) {
1247
-					$result[] = $exception["basedate"];
1248
-				}
1249
-
1250
-				return $result;
1251
-			}
1252
-
1253
-			return $result;
1254
-		}
1255
-
1256
-		/**
1257
-		 *  Function which adds organizer to recipient list which is passed.
1258
-		 *  This function also checks if it has organizer.
1259
-		 *
1260
-		 * @param array $messageProps message properties
1261
-		 * @param array $recipients   recipients list of message
1262
-		 * @param bool  $isException  true if we are processing recipient of exception
1263
-		 */
1264
-		public function addOrganizer($messageProps, &$recipients, $isException = false) {
1265
-			$hasOrganizer = false;
1266
-			// Check if meeting already has an organizer.
1267
-			foreach ($recipients as $key => $recipient) {
1268
-				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1269
-					$hasOrganizer = true;
1270
-				}
1271
-				elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1272
-					// Recipients for an occurrence
1273
-					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1274
-				}
1275
-			}
1276
-
1277
-			if (!$hasOrganizer) {
1278
-				// Create organizer.
1279
-				$organizer = [];
1280
-				$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1281
-				$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1282
-				$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1283
-				$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
1284
-				$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1285
-				$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1286
-				$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1287
-				$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1288
-				$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
1289
-
1290
-				// Add organizer to recipients list.
1291
-				array_unshift($recipients, $organizer);
1292
-			}
1293
-		}
1294
-	}
1295
-
1296
-	/*
698
+        public function deleteException($base_date) {
699
+            // Remove all exceptions on $base_date from the deleted and changed occurrences lists
700
+
701
+            // Remove all items in $todelete from deleted_occurrences
702
+            $new = [];
703
+
704
+            foreach ($this->recur["deleted_occurrences"] as $entry) {
705
+                if ($entry != $base_date) {
706
+                    $new[] = $entry;
707
+                }
708
+            }
709
+            $this->recur["deleted_occurrences"] = $new;
710
+
711
+            $new = [];
712
+
713
+            foreach ($this->recur["changed_occurrences"] as $entry) {
714
+                if (!$this->isSameDay($entry["basedate"], $base_date)) {
715
+                    $new[] = $entry;
716
+                }
717
+                else {
718
+                    $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
719
+                }
720
+            }
721
+
722
+            $this->recur["changed_occurrences"] = $new;
723
+        }
724
+
725
+        /**
726
+         * Function which saves the exception data in an attachment.
727
+         *
728
+         * @param array        $exception_props  the exception data (like any other MAPI appointment)
729
+         * @param array        $exception_recips list of recipients
730
+         * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
731
+         */
732
+        public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) {
733
+            // Create new attachment.
734
+            $attachment = mapi_message_createattach($this->message);
735
+            $props = [];
736
+            $props[PR_ATTACHMENT_FLAGS] = 2;
737
+            $props[PR_ATTACHMENT_HIDDEN] = true;
738
+            $props[PR_ATTACHMENT_LINKID] = 0;
739
+            $props[PR_ATTACH_FLAGS] = 0;
740
+            $props[PR_ATTACH_METHOD] = 5;
741
+            $props[PR_DISPLAY_NAME] = "Exception";
742
+            $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
743
+            $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
744
+            mapi_setprops($attachment, $props);
745
+
746
+            $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
747
+
748
+            if ($copy_attach_from) {
749
+                $attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
750
+                if ($attachmentTable) {
751
+                    $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
752
+
753
+                    foreach ($attachments as $attach_props) {
754
+                        $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
755
+                        $attach_newResourceMsg = mapi_message_createattach($imessage);
756
+                        mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
757
+                        mapi_savechanges($attach_newResourceMsg);
758
+                    }
759
+                }
760
+            }
761
+
762
+            $props = $props + $exception_props;
763
+
764
+            // FIXME: the following piece of code is written to fix the creation
765
+            // of an exception. This is only a quickfix as it is not yet possible
766
+            // to change an existing exception.
767
+            // remove mv properties when needed
768
+            foreach ($props as $propTag => $propVal) {
769
+                if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) {
770
+                    unset($props[$propTag]);
771
+                }
772
+            }
773
+
774
+            mapi_setprops($imessage, $props);
775
+
776
+            $this->setExceptionRecipients($imessage, $exception_recips, true);
777
+
778
+            mapi_savechanges($imessage);
779
+            mapi_savechanges($attachment);
780
+        }
781
+
782
+        /**
783
+         * Function which deletes the attachment of an exception.
784
+         *
785
+         * @param date $base_date base date of the attachment. Should be in GMT. The attachment
786
+         *                        actually saves the real time of the original date, so we have
787
+         *                        to check whether it's on the same day.
788
+         */
789
+        public function deleteExceptionAttachment($base_date) {
790
+            $attachments = mapi_message_getattachmenttable($this->message);
791
+            $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]);
792
+
793
+            foreach ($attachTable as $attachRow) {
794
+                $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
795
+                $exception = mapi_attach_openobj($tempattach);
796
+
797
+                $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
798
+
799
+                if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
800
+                    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
801
+                }
802
+            }
803
+        }
804
+
805
+        /**
806
+         * Function which deletes all attachments of a message.
807
+         */
808
+        public function deleteAttachments() {
809
+            $attachments = mapi_message_getattachmenttable($this->message);
810
+            $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
811
+
812
+            foreach ($attachTable as $attachRow) {
813
+                if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
814
+                    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
815
+                }
816
+            }
817
+        }
818
+
819
+        /**
820
+         * Get an exception attachment based on its basedate.
821
+         *
822
+         * @param mixed $base_date
823
+         */
824
+        public function getExceptionAttachment($base_date) {
825
+            // Retrieve only exceptions which are stored as embedded messages
826
+            $attach_res = [
827
+                RES_AND,
828
+                [
829
+                    [
830
+                        RES_PROPERTY,
831
+                        [
832
+                            RELOP => RELOP_EQ,
833
+                            ULPROPTAG => PR_ATTACH_METHOD,
834
+                            VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
835
+                        ],
836
+                    ],
837
+                ],
838
+            ];
839
+            $attachments = mapi_message_getattachmenttable($this->message);
840
+            $attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
841
+
842
+            if (is_array($attachRows)) {
843
+                foreach ($attachRows as $attachRow) {
844
+                    $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
845
+                    $exception = mapi_attach_openobj($tempattach);
846
+
847
+                    $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
848
+
849
+                    if (!isset($data[$this->proptags["basedate"]])) {
850
+                        // if no basedate found then it could be embedded message so ignore it
851
+                        // we need proper restriction to exclude embedded messages as well
852
+                        continue;
853
+                    }
854
+
855
+                    if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
856
+                        return $tempattach;
857
+                    }
858
+                }
859
+            }
860
+
861
+            return false;
862
+        }
863
+
864
+        /**
865
+         * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
866
+         * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
867
+         * - The occurrence isn't specified as a deleted occurrence.
868
+         *
869
+         * @param array $items        reference to the array to be added to
870
+         * @param date  $start        start of timeframe in GMT TIME
871
+         * @param date  $end          end of timeframe in GMT TIME
872
+         * @param date  $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
873
+         * @param int   $startocc     start of occurrence since beginning of day in minutes
874
+         * @param int   $endocc       end of occurrence since beginning of day in minutes
875
+         * @param int   $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
876
+         * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
877
+         */
878
+        public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
879
+            $exception = $this->isException($basedate);
880
+            if ($exception) {
881
+                return false;
882
+            }
883
+            $occstart = $basedate + $startocc * 60;
884
+            $occend = $basedate + $endocc * 60;
885
+
886
+            // Convert to GMT
887
+            $occstart = $this->toGMT($tz, $occstart);
888
+            $occend = $this->toGMT($tz, $occend);
889
+
890
+            /**
891
+             * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
892
+             * see any part of the appointment. Partial overlaps DO match.
893
+             *
894
+             * SECOND PART : check if occurrence is not a zero duration occurrence which
895
+             * starts at 00:00 and ends on 00:00. if it is so, then process
896
+             * the occurrence and send it in response.
897
+             */
898
+            if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
899
+                return;
900
+            }
901
+
902
+            // Properties for this occurrence are the same as the main object,
903
+            // With these properties overridden
904
+            $newitem = $this->messageprops;
905
+            $newitem[$this->proptags["startdate"]] = $occstart;
906
+            $newitem[$this->proptags["duedate"]] = $occend;
907
+            $newitem[$this->proptags["commonstart"]] = $occstart;
908
+            $newitem[$this->proptags["commonend"]] = $occend;
909
+            $newitem["basedate"] = $basedate;
910
+
911
+            // If reminderonly is set, only add reminders
912
+            if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
913
+                return;
914
+            }
915
+
916
+            $items[] = $newitem;
917
+        }
918
+
919
+        /**
920
+         * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
921
+         *
922
+         * @param array $items reference to the array to be added to
923
+         * @param date  $start start of timeframe in GMT TIME
924
+         * @param date  $end   end of timeframe in GMT TIME
925
+         */
926
+        public function processExceptionItems(&$items, $start, $end) {
927
+            $limit = 0;
928
+            foreach ($this->recur["changed_occurrences"] as $exception) {
929
+                // Convert to GMT
930
+                $occstart = $this->toGMT($this->tz, $exception["start"]);
931
+                $occend = $this->toGMT($this->tz, $exception["end"]);
932
+
933
+                // Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
934
+                // see any part of the appointment. Partial overlaps DO match.
935
+                if ($occstart >= $end || $occend <= $start) {
936
+                    continue;
937
+                }
938
+
939
+                array_push($items, $this->getExceptionProperties($exception));
940
+                if ((count($items) == $limit)) {
941
+                    break;
942
+                }
943
+            }
944
+        }
945
+
946
+        /**
947
+         * Function which verifies if on the given date an exception, delete or change, occurs.
948
+         *
949
+         * @param date  $date     the date
950
+         * @param mixed $basedate
951
+         *
952
+         * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date
953
+         */
954
+        public function isException($basedate) {
955
+            if ($this->isDeleteException($basedate)) {
956
+                return true;
957
+            }
958
+
959
+            if ($this->getChangeException($basedate) != false) {
960
+                return true;
961
+            }
962
+
963
+            return false;
964
+        }
965
+
966
+        /**
967
+         * Returns TRUE if there is a DELETE exception on the given base date.
968
+         *
969
+         * @param mixed $basedate
970
+         */
971
+        public function isDeleteException($basedate) {
972
+            // Check if the occurrence is deleted on the specified date
973
+            foreach ($this->recur["deleted_occurrences"] as $deleted) {
974
+                if ($this->isSameDay($deleted, $basedate)) {
975
+                    return true;
976
+                }
977
+            }
978
+
979
+            return false;
980
+        }
981
+
982
+        /**
983
+         * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
984
+         *
985
+         * @param mixed $basedate
986
+         */
987
+        public function getChangeException($basedate) {
988
+            // Check if the occurrence is modified on the specified date
989
+            foreach ($this->recur["changed_occurrences"] as $changed) {
990
+                if ($this->isSameDay($changed["basedate"], $basedate)) {
991
+                    return $changed;
992
+                }
993
+            }
994
+
995
+            return false;
996
+        }
997
+
998
+        /**
999
+         * Function to see if two dates are on the same day.
1000
+         *
1001
+         * @param date  $time1 date 1
1002
+         * @param date  $time2 date 2
1003
+         * @param mixed $date1
1004
+         * @param mixed $date2
1005
+         *
1006
+         * @return bool Returns TRUE when both dates are on the same day
1007
+         */
1008
+        public function isSameDay($date1, $date2) {
1009
+            $time1 = $this->gmtime($date1);
1010
+            $time2 = $this->gmtime($date2);
1011
+
1012
+            return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
1013
+        }
1014
+
1015
+        /**
1016
+         * Function to get all properties of a single changed exception.
1017
+         *
1018
+         * @param date  $date      base date of exception
1019
+         * @param mixed $exception
1020
+         *
1021
+         * @return array associative array of properties for the exception, compatible with
1022
+         */
1023
+        public function getExceptionProperties($exception) {
1024
+            // Exception has same properties as main object, with some properties overridden:
1025
+            $item = $this->messageprops;
1026
+
1027
+            // Special properties
1028
+            $item["exception"] = true;
1029
+            $item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time !
1030
+
1031
+            // MAPI-compatible properties (you can handle an exception as a normal calendar item like this)
1032
+            $item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]);
1033
+            $item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]);
1034
+            $item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]];
1035
+            $item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]];
1036
+
1037
+            if (isset($exception["subject"])) {
1038
+                $item[$this->proptags["subject"]] = $exception["subject"];
1039
+            }
1040
+
1041
+            if (isset($exception["label"])) {
1042
+                $item[$this->proptags["label"]] = $exception["label"];
1043
+            }
1044
+
1045
+            if (isset($exception["alldayevent"])) {
1046
+                $item[$this->proptags["alldayevent"]] = $exception["alldayevent"];
1047
+            }
1048
+
1049
+            if (isset($exception["location"])) {
1050
+                $item[$this->proptags["location"]] = $exception["location"];
1051
+            }
1052
+
1053
+            if (isset($exception["remind_before"])) {
1054
+                $item[$this->proptags["reminder_minutes"]] = $exception["remind_before"];
1055
+            }
1056
+
1057
+            if (isset($exception["reminder_set"])) {
1058
+                $item[$this->proptags["reminder"]] = $exception["reminder_set"];
1059
+            }
1060
+
1061
+            if (isset($exception["busystatus"])) {
1062
+                $item[$this->proptags["busystatus"]] = $exception["busystatus"];
1063
+            }
1064
+
1065
+            return $item;
1066
+        }
1067
+
1068
+        /**
1069
+         * Function which sets recipients for an exception.
1070
+         *
1071
+         * The $exception_recips can be provided in 2 ways:
1072
+         *  - A delta which indicates which recipients must be added, removed or deleted.
1073
+         *  - A complete array of the recipients which should be applied to the message.
1074
+         *
1075
+         * The first option is preferred as it will require less work to be executed.
1076
+         *
1077
+         * @param resource $message          exception attachment of recurring item
1078
+         * @param array    $exception_recips list of recipients
1079
+         * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1080
+         *                                   message to the attachment by default. False if only the $exception_recips changes should
1081
+         *                                   be applied.
1082
+         */
1083
+        public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1084
+            if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1085
+                $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1086
+            }
1087
+            else {
1088
+                $this->setAllExceptionRecipients($message, $exception_recips);
1089
+            }
1090
+        }
1091
+
1092
+        /**
1093
+         * Function which applies the provided delta for recipients changes to the exception.
1094
+         *
1095
+         * The $exception_recips should be an array containing the following keys:
1096
+         *  - "add": this contains an array of recipients which must be added
1097
+         *  - "remove": This contains an array of recipients which must be removed
1098
+         *  - "modify": This contains an array of recipients which must be modified
1099
+         *
1100
+         * @param resource $message          exception attachment of recurring item
1101
+         * @param array    $exception_recips list of recipients
1102
+         * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1103
+         *                                   message to the attachment by default. False if only the $exception_recips changes should
1104
+         *                                   be applied.
1105
+         * @param mixed    $exception
1106
+         */
1107
+        public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) {
1108
+            // Check if the recipients from the original message should be copied,
1109
+            // if so, open the recipient table of the parent message and apply all
1110
+            // rows on the target recipient.
1111
+            if ($copy_orig_recips === true) {
1112
+                $origTable = mapi_message_getrecipienttable($this->message);
1113
+                $recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
1114
+                mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
1115
+            }
1116
+
1117
+            // Add organizer to meeting only if it is not organized.
1118
+            $msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1119
+            if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1120
+                $this->addOrganizer($msgprops, $exception_recips['add']);
1121
+            }
1122
+
1123
+            // Remove all deleted recipients
1124
+            if (isset($exception_recips['remove'])) {
1125
+                foreach ($exception_recips['remove'] as &$recip) {
1126
+                    if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1127
+                        $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1128
+                    }
1129
+                    else {
1130
+                        $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1131
+                    }
1132
+                    $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;        // No Response required
1133
+                }
1134
+                unset($recip);
1135
+                mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1136
+            }
1137
+
1138
+            // Add all new recipients
1139
+            if (isset($exception_recips['add'])) {
1140
+                mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1141
+            }
1142
+
1143
+            // Modify the existing recipients
1144
+            if (isset($exception_recips['modify'])) {
1145
+                mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1146
+            }
1147
+        }
1148
+
1149
+        /**
1150
+         * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1151
+         *
1152
+         * The $exception_recips should be an array containing all recipients which must be applied
1153
+         * to the exception. This will copy all recipients from the original message and then start filter
1154
+         * out all recipients which are not provided by the $exception_recips list.
1155
+         *
1156
+         * @param resource $message          exception attachment of recurring item
1157
+         * @param array    $exception_recips list of recipients
1158
+         */
1159
+        public function setAllExceptionRecipients($message, $exception_recips) {
1160
+            $deletedRecipients = [];
1161
+            $useMessageRecipients = false;
1162
+
1163
+            $recipientTable = mapi_message_getrecipienttable($message);
1164
+            $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1165
+
1166
+            if (empty($recipientRows)) {
1167
+                $useMessageRecipients = true;
1168
+                $recipientTable = mapi_message_getrecipienttable($this->message);
1169
+                $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1170
+            }
1171
+
1172
+            // Add organizer to meeting only if it is not organized.
1173
+            $msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1174
+            if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1175
+                $this->addOrganizer($msgprops, $exception_recips);
1176
+            }
1177
+
1178
+            if (!empty($exception_recips)) {
1179
+                foreach ($recipientRows as $key => $recipient) {
1180
+                    $found = false;
1181
+                    foreach ($exception_recips as $excep_recip) {
1182
+                        if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
1183
+                            $found = true;
1184
+                        }
1185
+                    }
1186
+
1187
+                    if (!$found) {
1188
+                        $foundInDeletedRecipients = false;
1189
+                        // Look if the $recipient is in the list of deleted recipients
1190
+                        if (!empty($deletedRecipients)) {
1191
+                            foreach ($deletedRecipients as $recip) {
1192
+                                if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1193
+                                    $foundInDeletedRecipients = true;
1194
+
1195
+                                    break;
1196
+                                }
1197
+                            }
1198
+                        }
1199
+
1200
+                        // If recipient is not in list of deleted recipient, add him
1201
+                        if (!$foundInDeletedRecipients) {
1202
+                            if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1203
+                                $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1204
+                            }
1205
+                            else {
1206
+                                $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1207
+                            }
1208
+                            $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;    // No Response required
1209
+                            $deletedRecipients[] = $recipient;
1210
+                        }
1211
+                    }
1212
+
1213
+                    // When $message contains a non-empty recipienttable, we must delete the recipients
1214
+                    // before re-adding them. However, when $message is doesn't contain any recipients,
1215
+                    // we are using the recipient table of the original message ($this->message)
1216
+                    // rather then $message. In that case, we don't need to remove the recipients
1217
+                    // from the $message, as the recipient table is already empty, and
1218
+                    // mapi_message_modifyrecipients() will throw an error.
1219
+                    if ($useMessageRecipients === false) {
1220
+                        mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1221
+                    }
1222
+                }
1223
+                $exception_recips = array_merge($exception_recips, $deletedRecipients);
1224
+            }
1225
+            else {
1226
+                $exception_recips = $recipientRows;
1227
+            }
1228
+
1229
+            if (!empty($exception_recips)) {
1230
+                // Set the new list of recipients on the exception message, this also removes the existing recipients
1231
+                mapi_message_modifyrecipients($message, 0, $exception_recips);
1232
+            }
1233
+        }
1234
+
1235
+        /**
1236
+         * Function returns basedates of all changed occurrences.
1237
+         *
1238
+         *@return array array(
1239
+         * 0 => 123459321
1240
+         * )
1241
+         */
1242
+        public function getAllExceptions() {
1243
+            $result = false;
1244
+            if (!empty($this->recur["changed_occurrences"])) {
1245
+                $result = [];
1246
+                foreach ($this->recur["changed_occurrences"] as $exception) {
1247
+                    $result[] = $exception["basedate"];
1248
+                }
1249
+
1250
+                return $result;
1251
+            }
1252
+
1253
+            return $result;
1254
+        }
1255
+
1256
+        /**
1257
+         *  Function which adds organizer to recipient list which is passed.
1258
+         *  This function also checks if it has organizer.
1259
+         *
1260
+         * @param array $messageProps message properties
1261
+         * @param array $recipients   recipients list of message
1262
+         * @param bool  $isException  true if we are processing recipient of exception
1263
+         */
1264
+        public function addOrganizer($messageProps, &$recipients, $isException = false) {
1265
+            $hasOrganizer = false;
1266
+            // Check if meeting already has an organizer.
1267
+            foreach ($recipients as $key => $recipient) {
1268
+                if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1269
+                    $hasOrganizer = true;
1270
+                }
1271
+                elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1272
+                    // Recipients for an occurrence
1273
+                    $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1274
+                }
1275
+            }
1276
+
1277
+            if (!$hasOrganizer) {
1278
+                // Create organizer.
1279
+                $organizer = [];
1280
+                $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1281
+                $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1282
+                $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1283
+                $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
1284
+                $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1285
+                $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1286
+                $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1287
+                $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1288
+                $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
1289
+
1290
+                // Add organizer to recipients list.
1291
+                array_unshift($recipients, $organizer);
1292
+            }
1293
+        }
1294
+    }
1295
+
1296
+    /*
1297 1297
 
1298 1298
 	From http://www.ohelp-one.com/new-6765483-3268.html:
1299 1299
 
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -598,8 +598,8 @@  discard block
 block discarded – undo
598 598
 			}
599 599
 
600 600
 			// get timings of the first occurrence
601
-			$firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
602
-			$firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
601
+			$firstoccstartdate = isset($startocc) ? $start + (((int)$startocc) * 60) : $start;
602
+			$firstoccenddate = isset($endocc) ? $end + (((int)$endocc) * 60) : $end;
603 603
 
604 604
 			$start = gmdate(_('d-m-Y'), $firstoccstartdate);
605 605
 			$end = gmdate(_('d-m-Y'), $firstoccenddate);
@@ -743,7 +743,7 @@  discard block
 block discarded – undo
743 743
 			$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
744 744
 			mapi_setprops($attachment, $props);
745 745
 
746
-			$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
746
+			$imessage = mapi_attach_openobj($attachment, MAPI_CREATE|MAPI_MODIFY);
747 747
 
748 748
 			if ($copy_attach_from) {
749 749
 				$attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
@@ -751,7 +751,7 @@  discard block
 block discarded – undo
751 751
 					$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
752 752
 
753 753
 					foreach ($attachments as $attach_props) {
754
-						$attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
754
+						$attach_old = mapi_message_openattach($copy_attach_from, (int)$attach_props[PR_ATTACH_NUM]);
755 755
 						$attach_newResourceMsg = mapi_message_createattach($imessage);
756 756
 						mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
757 757
 						mapi_savechanges($attach_newResourceMsg);
@@ -1123,13 +1123,13 @@  discard block
 block discarded – undo
1123 1123
 			// Remove all deleted recipients
1124 1124
 			if (isset($exception_recips['remove'])) {
1125 1125
 				foreach ($exception_recips['remove'] as &$recip) {
1126
-					if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1127
-						$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1126
+					if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved|recipExceptionalDeleted|recipSendable)) {
1127
+						$recip[PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalDeleted;
1128 1128
 					}
1129 1129
 					else {
1130
-						$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1130
+						$recip[PR_RECIPIENT_FLAGS] = recipReserved|recipExceptionalDeleted|recipSendable;
1131 1131
 					}
1132
-					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;        // No Response required
1132
+					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required
1133 1133
 				}
1134 1134
 				unset($recip);
1135 1135
 				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
@@ -1199,13 +1199,13 @@  discard block
 block discarded – undo
1199 1199
 
1200 1200
 						// If recipient is not in list of deleted recipient, add him
1201 1201
 						if (!$foundInDeletedRecipients) {
1202
-							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1203
-								$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1202
+							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved|recipExceptionalDeleted|recipSendable)) {
1203
+								$recipient[PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalDeleted;
1204 1204
 							}
1205 1205
 							else {
1206
-								$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1206
+								$recipient[PR_RECIPIENT_FLAGS] = recipReserved|recipExceptionalDeleted|recipSendable;
1207 1207
 							}
1208
-							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;    // No Response required
1208
+							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required
1209 1209
 							$deletedRecipients[] = $recipient;
1210 1210
 						}
1211 1211
 					}
@@ -1265,12 +1265,12 @@  discard block
 block discarded – undo
1265 1265
 			$hasOrganizer = false;
1266 1266
 			// Check if meeting already has an organizer.
1267 1267
 			foreach ($recipients as $key => $recipient) {
1268
-				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1268
+				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable|recipOrganizer)) {
1269 1269
 					$hasOrganizer = true;
1270 1270
 				}
1271 1271
 				elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1272 1272
 					// Recipients for an occurrence
1273
-					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1273
+					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalResponse;
1274 1274
 				}
1275 1275
 			}
1276 1276
 
@@ -1284,7 +1284,7 @@  discard block
 block discarded – undo
1284 1284
 				$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1285 1285
 				$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1286 1286
 				$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1287
-				$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1287
+				$organizer[PR_RECIPIENT_FLAGS] = recipSendable|recipOrganizer;
1288 1288
 				$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
1289 1289
 
1290 1290
 				// Add organizer to recipients list.
Please login to merge, or discard this patch.
Braces   +28 added lines, -56 removed lines patch added patch discarded remove patch
@@ -51,8 +51,7 @@  discard block
 block discarded – undo
51 51
 		public function __construct($store, $message, $proptags = []) {
52 52
 			if ($proptags) {
53 53
 				$this->proptags = $proptags;
54
-			}
55
-			else {
54
+			} else {
56 55
 				$properties = [];
57 56
 				$properties["entryid"] = PR_ENTRYID;
58 57
 				$properties["parent_entryid"] = PR_PARENT_ENTRYID;
@@ -192,8 +191,7 @@  discard block
 block discarded – undo
192 191
 
193 192
 				// Add the changed occurrence to the list
194 193
 				array_push($this->recur["changed_occurrences"], $changed_item);
195
-			}
196
-			else {
194
+			} else {
197 195
 				// Delete the occurrence by placing it in the deleted occurrences list
198 196
 				array_push($this->recur["deleted_occurrences"], $baseday);
199 197
 			}
@@ -290,12 +288,10 @@  discard block
 block discarded – undo
290 288
 				if ($copy_attach_from) {
291 289
 					$this->deleteExceptionAttachment($base_date);
292 290
 					$this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
293
-				}
294
-				else {
291
+				} else {
295 292
 					$this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
296 293
 				}
297
-			}
298
-			else {
294
+			} else {
299 295
 				$message = mapi_attach_openobj($attach, MAPI_MODIFY);
300 296
 
301 297
 				// Set exception properties on embedded message and save
@@ -339,8 +335,7 @@  discard block
 block discarded – undo
339 335
 				// the exception used to be.
340 336
 				$oldexception = $this->getChangeException($basedate);
341 337
 				$prevday = $this->dayStartOf($oldexception["start"]);
342
-			}
343
-			else {
338
+			} else {
344 339
 				// If its a new exception, we want to look at the original placement of this item.
345 340
 				$prevday = $basedate;
346 341
 			}
@@ -350,8 +345,7 @@  discard block
 block discarded – undo
350 345
 			// Get all the occurrences on the days between the basedate (may be reversed)
351 346
 			if ($prevday < $startday) {
352 347
 				$items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
353
-			}
354
-			else {
348
+			} else {
355 349
 				$items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
356 350
 			}
357 351
 
@@ -545,12 +539,10 @@  discard block
 block discarded – undo
545 539
 					if ($everyn == 1) {
546 540
 						$type = _('workday');
547 541
 						$occSingleDayRank = true;
548
-					}
549
-					elseif ($everyn == (24 * 60)) {
542
+					} elseif ($everyn == (24 * 60)) {
550 543
 						$type = _('day');
551 544
 						$occSingleDayRank = true;
552
-					}
553
-					else {
545
+					} else {
554 546
 						$everyn /= (24 * 60);
555 547
 						$type = _('days');
556 548
 						$occSingleDayRank = false;
@@ -562,8 +554,7 @@  discard block
 block discarded – undo
562 554
 					if ($everyn == 1) {
563 555
 						$type = _('week');
564 556
 						$occSingleDayRank = true;
565
-					}
566
-					else {
557
+					} else {
567 558
 						$type = _('weeks');
568 559
 						$occSingleDayRank = false;
569 560
 					}
@@ -574,8 +565,7 @@  discard block
 block discarded – undo
574 565
 					if ($everyn == 1) {
575 566
 						$type = _('month');
576 567
 						$occSingleDayRank = true;
577
-					}
578
-					else {
568
+					} else {
579 569
 						$type = _('months');
580 570
 						$occSingleDayRank = false;
581 571
 					}
@@ -587,8 +577,7 @@  discard block
 block discarded – undo
587 577
 						$everyn = 1;
588 578
 						$type = _('year');
589 579
 						$occSingleDayRank = true;
590
-					}
591
-					else {
580
+					} else {
592 581
 						$everyn = $everyn / 12;
593 582
 						$type = _('years');
594 583
 						$occSingleDayRank = false;
@@ -618,21 +607,17 @@  discard block
 block discarded – undo
618 607
 				if ($occTimeRange) {
619 608
 					if ($occSingleDayRank) {
620 609
 						$pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
621
-					}
622
-					else {
610
+					} else {
623 611
 						$pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
624 612
 					}
625
-				}
626
-				else {
613
+				} else {
627 614
 					if ($occSingleDayRank) {
628 615
 						$pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start);
629
-					}
630
-					else {
616
+					} else {
631 617
 						$pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start);
632 618
 					}
633 619
 				}
634
-			}
635
-			elseif ($term == 0x22) {
620
+			} elseif ($term == 0x22) {
636 621
 				// After a number of times
637 622
 				if ($occTimeRange) {
638 623
 					if ($occSingleDayRank) {
@@ -641,24 +626,21 @@  discard block
 block discarded – undo
641 626
 							'Occurs every %s effective %s for %s occurrences from %s to %s.',
642 627
 							$numocc
643 628
 						), $type, $start, $numocc, $startocc, $endocc);
644
-					}
645
-					else {
629
+					} else {
646 630
 						$pattern = sprintf(ngettext(
647 631
 							'Occurs every %s %s effective %s for %s occurrence from %s to %s.',
648 632
 							'Occurs every %s %s effective %s for %s occurrences %s to %s.',
649 633
 							$numocc
650 634
 						), $everyn, $type, $start, $numocc, $startocc, $endocc);
651 635
 					}
652
-				}
653
-				else {
636
+				} else {
654 637
 					if ($occSingleDayRank) {
655 638
 						$pattern = sprintf(ngettext(
656 639
 							'Occurs every %s effective %s for %s occurrence.',
657 640
 							'Occurs every %s effective %s for %s occurrences.',
658 641
 							$numocc
659 642
 						), $type, $start, $numocc);
660
-					}
661
-					else {
643
+					} else {
662 644
 						$pattern = sprintf(ngettext(
663 645
 							'Occurs every %s %s effective %s for %s occurrence.',
664 646
 							'Occurs every %s %s effective %s for %s occurrences.',
@@ -666,22 +648,18 @@  discard block
 block discarded – undo
666 648
 						), $everyn, $type, $start, $numocc);
667 649
 					}
668 650
 				}
669
-			}
670
-			elseif ($term == 0x21) {
651
+			} elseif ($term == 0x21) {
671 652
 				// After the given enddate
672 653
 				if ($occTimeRange) {
673 654
 					if ($occSingleDayRank) {
674 655
 						$pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
675
-					}
676
-					else {
656
+					} else {
677 657
 						$pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
678 658
 					}
679
-				}
680
-				else {
659
+				} else {
681 660
 					if ($occSingleDayRank) {
682 661
 						$pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end);
683
-					}
684
-					else {
662
+					} else {
685 663
 						$pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
686 664
 					}
687 665
 				}
@@ -713,8 +691,7 @@  discard block
 block discarded – undo
713 691
 			foreach ($this->recur["changed_occurrences"] as $entry) {
714 692
 				if (!$this->isSameDay($entry["basedate"], $base_date)) {
715 693
 					$new[] = $entry;
716
-				}
717
-				else {
694
+				} else {
718 695
 					$this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
719 696
 				}
720 697
 			}
@@ -1083,8 +1060,7 @@  discard block
 block discarded – undo
1083 1060
 		public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1084 1061
 			if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1085 1062
 				$this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1086
-			}
1087
-			else {
1063
+			} else {
1088 1064
 				$this->setAllExceptionRecipients($message, $exception_recips);
1089 1065
 			}
1090 1066
 		}
@@ -1125,8 +1101,7 @@  discard block
 block discarded – undo
1125 1101
 				foreach ($exception_recips['remove'] as &$recip) {
1126 1102
 					if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1127 1103
 						$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1128
-					}
1129
-					else {
1104
+					} else {
1130 1105
 						$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1131 1106
 					}
1132 1107
 					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;        // No Response required
@@ -1201,8 +1176,7 @@  discard block
 block discarded – undo
1201 1176
 						if (!$foundInDeletedRecipients) {
1202 1177
 							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1203 1178
 								$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1204
-							}
1205
-							else {
1179
+							} else {
1206 1180
 								$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1207 1181
 							}
1208 1182
 							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;    // No Response required
@@ -1221,8 +1195,7 @@  discard block
 block discarded – undo
1221 1195
 					}
1222 1196
 				}
1223 1197
 				$exception_recips = array_merge($exception_recips, $deletedRecipients);
1224
-			}
1225
-			else {
1198
+			} else {
1226 1199
 				$exception_recips = $recipientRows;
1227 1200
 			}
1228 1201
 
@@ -1267,8 +1240,7 @@  discard block
 block discarded – undo
1267 1240
 			foreach ($recipients as $key => $recipient) {
1268 1241
 				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1269 1242
 					$hasOrganizer = true;
1270
-				}
1271
-				elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1243
+				} elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1272 1244
 					// Recipients for an occurrence
1273 1245
 					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1274 1246
 				}
Please login to merge, or discard this patch.
mapi/mapitags.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -6,7 +6,7 @@
 block discarded – undo
6 6
  */
7 7
 
8 8
 if (!function_exists("mapi_prop_tag")) {
9
-	throw new FatalMisconfigurationException("PHP-MAPI extension is not available");
9
+    throw new FatalMisconfigurationException("PHP-MAPI extension is not available");
10 10
 }
11 11
 
12 12
 define('PR_ACKNOWLEDGEMENT_MODE', mapi_prop_tag(PT_LONG, 0x0001));
Please login to merge, or discard this patch.
mapi/class.freebusypublish.php 3 patches
Indentation   +389 added lines, -389 removed lines patch added patch discarded remove patch
@@ -6,393 +6,393 @@
 block discarded – undo
6 6
  */
7 7
 
8 8
 class FreeBusyPublish {
9
-	public $session;
10
-	public $calendar;
11
-	public $entryid;
12
-	public $starttime;
13
-	public $length;
14
-	public $store;
15
-	public $proptags;
16
-
17
-	/**
18
-	 * Constructor.
19
-	 *
20
-	 * @param mapi_session $session  MAPI Session
21
-	 * @param mapi_folder  $calendar Calendar to publish
22
-	 * @param string       $entryid  AddressBook Entry ID for the user we're publishing for
23
-	 * @param mixed        $store
24
-	 */
25
-	public function __construct($session, $store, $calendar, $entryid) {
26
-		$properties["entryid"] = PR_ENTRYID;
27
-		$properties["parent_entryid"] = PR_PARENT_ENTRYID;
28
-		$properties["message_class"] = PR_MESSAGE_CLASS;
29
-		$properties["icon_index"] = PR_ICON_INDEX;
30
-		$properties["subject"] = PR_SUBJECT;
31
-		$properties["display_to"] = PR_DISPLAY_TO;
32
-		$properties["importance"] = PR_IMPORTANCE;
33
-		$properties["sensitivity"] = PR_SENSITIVITY;
34
-		$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
35
-		$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
36
-		$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
37
-		$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
38
-		$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
39
-		$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
40
-		$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
41
-		$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
42
-		$properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
43
-		$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
44
-		$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
45
-		$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
46
-		$properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
47
-		$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
48
-		$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
49
-		$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
50
-		$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
51
-		$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
52
-		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
53
-		$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
54
-		$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
55
-		$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
56
-		$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
57
-		$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
58
-		$this->proptags = getPropIdsFromStrings($store, $properties);
59
-
60
-		$this->session = $session;
61
-		$this->calendar = $calendar;
62
-		$this->entryid = $entryid;
63
-		$this->store = $store;
64
-	}
65
-
66
-	/**
67
-	 * Function is used to get the calendar data based on give date range.
68
-	 *
69
-	 * @param timestamp $starttime time from which to get the calendar data
70
-	 * @param timestamp $length    time up till now get the calendar data
71
-	 *
72
-	 * @return array return the calendar data array
73
-	 */
74
-	public function getCalendarData($starttime, $length) {
75
-		$start = $starttime;
76
-		$end = $length;
77
-
78
-		// Get all the items in the calendar that we need
79
-
80
-		$calendaritems = [];
81
-
82
-		$restrict = [
83
-			RES_OR,
84
-			[
85
-				// OR
86
-				// (item[start] >= start && item[start] <= end)
87
-				[
88
-					RES_AND,
89
-					[
90
-						[
91
-							RES_PROPERTY,
92
-							[
93
-								RELOP => RELOP_GE,
94
-								ULPROPTAG => $this->proptags["startdate"],
95
-								VALUE => $start,
96
-							],
97
-						],
98
-						[
99
-							RES_PROPERTY,
100
-							[
101
-								RELOP => RELOP_LE,
102
-								ULPROPTAG => $this->proptags["startdate"],
103
-								VALUE => $end,
104
-							],
105
-						],
106
-					],
107
-				],
108
-				// OR
109
-				// (item[end]   >= start && item[end]   <= end)
110
-				[
111
-					RES_AND,
112
-					[
113
-						[
114
-							RES_PROPERTY,
115
-							[
116
-								RELOP => RELOP_GE,
117
-								ULPROPTAG => $this->proptags["duedate"],
118
-								VALUE => $start,
119
-							],
120
-						],
121
-						[
122
-							RES_PROPERTY,
123
-							[
124
-								RELOP => RELOP_LE,
125
-								ULPROPTAG => $this->proptags["duedate"],
126
-								VALUE => $end,
127
-							],
128
-						],
129
-					],
130
-				],
131
-				// OR
132
-				// (item[start] <  start && item[end]   >  end)
133
-				[
134
-					RES_AND,
135
-					[
136
-						[
137
-							RES_PROPERTY,
138
-							[
139
-								RELOP => RELOP_LT,
140
-								ULPROPTAG => $this->proptags["startdate"],
141
-								VALUE => $start,
142
-							],
143
-						],
144
-						[
145
-							RES_PROPERTY,
146
-							[
147
-								RELOP => RELOP_GT,
148
-								ULPROPTAG => $this->proptags["duedate"],
149
-								VALUE => $end,
150
-							],
151
-						],
152
-					],
153
-				],
154
-				// OR
155
-				[
156
-					RES_OR,
157
-					[
158
-						// OR
159
-						// (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
160
-						[
161
-							RES_AND,
162
-							[
163
-								[
164
-									RES_EXIST,
165
-									[ULPROPTAG => $this->proptags["enddate_recurring"]],
166
-								],
167
-								[
168
-									RES_PROPERTY,
169
-									[
170
-										RELOP => RELOP_EQ,
171
-										ULPROPTAG => $this->proptags["recurring"],
172
-										VALUE => true,
173
-									],
174
-								],
175
-								[
176
-									RES_PROPERTY,
177
-									[
178
-										RELOP => RELOP_GE,
179
-										ULPROPTAG => $this->proptags["enddate_recurring"],
180
-										VALUE => $start,
181
-									],
182
-								],
183
-							],
184
-						],
185
-						// OR
186
-						// (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
187
-						[
188
-							RES_AND,
189
-							[
190
-								[
191
-									RES_NOT,
192
-									[
193
-										[
194
-											RES_EXIST,
195
-											[ULPROPTAG => $this->proptags["enddate_recurring"],
196
-											],
197
-										],
198
-									],
199
-								],
200
-								[
201
-									RES_PROPERTY,
202
-									[
203
-										RELOP => RELOP_LE,
204
-										ULPROPTAG => $this->proptags["startdate"],
205
-										VALUE => $end,
206
-									],
207
-								],
208
-								[
209
-									RES_PROPERTY,
210
-									[
211
-										RELOP => RELOP_EQ,
212
-										ULPROPTAG => $this->proptags["recurring"],
213
-										VALUE => true,
214
-									],
215
-								],
216
-							],
217
-						],
218
-					],
219
-				], // EXISTS OR
220
-			],
221
-		];        // global OR
222
-
223
-		$contents = mapi_folder_getcontentstable($this->calendar);
224
-		mapi_table_restrict($contents, $restrict);
225
-
226
-		while (1) {
227
-			$rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
228
-
229
-			if (!is_array($rows)) {
230
-				break;
231
-			}
232
-
233
-			if (empty($rows)) {
234
-				break;
235
-			}
236
-
237
-			foreach ($rows as $row) {
238
-				$occurrences = [];
239
-				if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
240
-					$recur = new Recurrence($this->store, $row);
241
-
242
-					$occurrences = $recur->getItems($starttime, $length);
243
-				}
244
-				else {
245
-					$occurrences[] = $row;
246
-				}
247
-
248
-				$calendaritems = array_merge($calendaritems, $occurrences);
249
-			}
250
-		}
251
-
252
-		// $calendaritems now contains all the calendar items in the specified time
253
-		// frame. We now need to merge these into a flat array of begin/end/status
254
-		// objects. This also filters out all the 'free' items (status 0)
255
-		return $this->mergeItemsFB($calendaritems);
256
-	}
257
-
258
-	/**
259
-	 * Publishes Free/Busy information of user.
260
-	 *
261
-	 * @param timestamp $starttime Time from which to publish data  (usually now)
262
-	 * @param timestamp $length    Time of seconds from $starttime we should publish
263
-	 * @param mixed     $start
264
-	 * @param mixed     $end
265
-	 */
266
-	public function publishFB($start, $end) {
267
-		$freebusy = $this->getCalendarData($start, $end);
268
-
269
-		// Get the FB interface
270
-		try {
271
-			$fbsupport = mapi_freebusysupport_open($this->session, $this->store);
272
-		}
273
-		catch (MAPIException $e) {
274
-			if ($e->getCode() == MAPI_E_NOT_FOUND) {
275
-				$e->setHandled();
276
-				SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object.");
277
-			}
278
-		}
279
-
280
-		// Open updater for this user
281
-		if (isset($fbsupport) && $fbsupport) {
282
-			$updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]);
283
-
284
-			$updater = $updaters[0];
285
-
286
-			// Send the data
287
-			mapi_freebusyupdate_reset($updater);
288
-			mapi_freebusyupdate_publish($updater, $freebusy);
289
-			mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end);
290
-
291
-			// We're finished
292
-			mapi_freebusysupport_close($fbsupport);
293
-		}
294
-		else {
295
-			SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available");
296
-		}
297
-	}
298
-
299
-	/**
300
-	 * Sorts by timestamp, if equal, then end before start.
301
-	 *
302
-	 * @param mixed $a
303
-	 * @param mixed $b
304
-	 */
305
-	public function cmp($a, $b) {
306
-		if ($a["time"] == $b["time"]) {
307
-			if ($a["type"] < $b["type"]) {
308
-				return 1;
309
-			}
310
-			if ($a["type"] > $b["type"]) {
311
-				return -1;
312
-			}
313
-
314
-			return 0;
315
-		}
316
-
317
-		return $a["time"] > $b["time"] ? 1 : -1;
318
-	}
319
-
320
-	/**
321
-	 * Function mergeItems.
322
-	 *
323
-	 * @author Steve Hardy
324
-	 *
325
-	 * @param mixed $items
326
-	 */
327
-	public function mergeItemsFB($items) {
328
-		$merged = [];
329
-		$timestamps = [];
330
-		$csubj = [];
331
-		$cbusy = [];
332
-		$level = 0;
333
-		$laststart = null;
334
-
335
-		foreach ($items as $item) {
336
-			$ts["type"] = 0;
337
-			$ts["time"] = $item[$this->proptags["startdate"]];
338
-			$ts["subject"] = $item[PR_SUBJECT];
339
-			$ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197
340
-			$timestamps[] = $ts;
341
-
342
-			$ts["type"] = 1;
343
-			$ts["time"] = $item[$this->proptags["duedate"]];
344
-			$ts["subject"] = $item[PR_SUBJECT];
345
-			$ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197
346
-			$timestamps[] = $ts;
347
-		}
348
-
349
-		usort($timestamps, [$this, "cmp"]);
350
-		$laststart = 0; // seb added
351
-
352
-		foreach ($timestamps as $ts) {
353
-			switch ($ts["type"]) {
354
-				case 0: // Start
355
-					if ($level != 0 && $laststart != $ts["time"]) {
356
-						$newitem["start"] = $laststart;
357
-						$newitem["end"] = $ts["time"];
358
-						$newitem["subject"] = join(",", $csubj);
359
-						$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
360
-						if ($newitem["status"] > 0) {
361
-							$merged[] = $newitem;
362
-						}
363
-					}
364
-
365
-					++$level;
366
-
367
-					$csubj[] = $ts["subject"];
368
-					$cbusy[] = $ts["status"];
369
-
370
-					$laststart = $ts["time"];
371
-
372
-					break;
373
-
374
-				case 1: // End
375
-					if ($laststart != $ts["time"]) {
376
-						$newitem["start"] = $laststart;
377
-						$newitem["end"] = $ts["time"];
378
-						$newitem["subject"] = join(",", $csubj);
379
-						$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
380
-						if ($newitem["status"] > 0) {
381
-							$merged[] = $newitem;
382
-						}
383
-					}
384
-
385
-					--$level;
386
-
387
-					array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
388
-					array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
389
-
390
-					$laststart = $ts["time"];
391
-
392
-					break;
393
-			}
394
-		}
395
-
396
-		return $merged;
397
-	}
9
+    public $session;
10
+    public $calendar;
11
+    public $entryid;
12
+    public $starttime;
13
+    public $length;
14
+    public $store;
15
+    public $proptags;
16
+
17
+    /**
18
+     * Constructor.
19
+     *
20
+     * @param mapi_session $session  MAPI Session
21
+     * @param mapi_folder  $calendar Calendar to publish
22
+     * @param string       $entryid  AddressBook Entry ID for the user we're publishing for
23
+     * @param mixed        $store
24
+     */
25
+    public function __construct($session, $store, $calendar, $entryid) {
26
+        $properties["entryid"] = PR_ENTRYID;
27
+        $properties["parent_entryid"] = PR_PARENT_ENTRYID;
28
+        $properties["message_class"] = PR_MESSAGE_CLASS;
29
+        $properties["icon_index"] = PR_ICON_INDEX;
30
+        $properties["subject"] = PR_SUBJECT;
31
+        $properties["display_to"] = PR_DISPLAY_TO;
32
+        $properties["importance"] = PR_IMPORTANCE;
33
+        $properties["sensitivity"] = PR_SENSITIVITY;
34
+        $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
35
+        $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
36
+        $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
37
+        $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
38
+        $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
39
+        $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
40
+        $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
41
+        $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
42
+        $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
43
+        $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
44
+        $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
45
+        $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
46
+        $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
47
+        $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
48
+        $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
49
+        $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
50
+        $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
51
+        $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
52
+        $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
53
+        $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
54
+        $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
55
+        $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
56
+        $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
57
+        $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
58
+        $this->proptags = getPropIdsFromStrings($store, $properties);
59
+
60
+        $this->session = $session;
61
+        $this->calendar = $calendar;
62
+        $this->entryid = $entryid;
63
+        $this->store = $store;
64
+    }
65
+
66
+    /**
67
+     * Function is used to get the calendar data based on give date range.
68
+     *
69
+     * @param timestamp $starttime time from which to get the calendar data
70
+     * @param timestamp $length    time up till now get the calendar data
71
+     *
72
+     * @return array return the calendar data array
73
+     */
74
+    public function getCalendarData($starttime, $length) {
75
+        $start = $starttime;
76
+        $end = $length;
77
+
78
+        // Get all the items in the calendar that we need
79
+
80
+        $calendaritems = [];
81
+
82
+        $restrict = [
83
+            RES_OR,
84
+            [
85
+                // OR
86
+                // (item[start] >= start && item[start] <= end)
87
+                [
88
+                    RES_AND,
89
+                    [
90
+                        [
91
+                            RES_PROPERTY,
92
+                            [
93
+                                RELOP => RELOP_GE,
94
+                                ULPROPTAG => $this->proptags["startdate"],
95
+                                VALUE => $start,
96
+                            ],
97
+                        ],
98
+                        [
99
+                            RES_PROPERTY,
100
+                            [
101
+                                RELOP => RELOP_LE,
102
+                                ULPROPTAG => $this->proptags["startdate"],
103
+                                VALUE => $end,
104
+                            ],
105
+                        ],
106
+                    ],
107
+                ],
108
+                // OR
109
+                // (item[end]   >= start && item[end]   <= end)
110
+                [
111
+                    RES_AND,
112
+                    [
113
+                        [
114
+                            RES_PROPERTY,
115
+                            [
116
+                                RELOP => RELOP_GE,
117
+                                ULPROPTAG => $this->proptags["duedate"],
118
+                                VALUE => $start,
119
+                            ],
120
+                        ],
121
+                        [
122
+                            RES_PROPERTY,
123
+                            [
124
+                                RELOP => RELOP_LE,
125
+                                ULPROPTAG => $this->proptags["duedate"],
126
+                                VALUE => $end,
127
+                            ],
128
+                        ],
129
+                    ],
130
+                ],
131
+                // OR
132
+                // (item[start] <  start && item[end]   >  end)
133
+                [
134
+                    RES_AND,
135
+                    [
136
+                        [
137
+                            RES_PROPERTY,
138
+                            [
139
+                                RELOP => RELOP_LT,
140
+                                ULPROPTAG => $this->proptags["startdate"],
141
+                                VALUE => $start,
142
+                            ],
143
+                        ],
144
+                        [
145
+                            RES_PROPERTY,
146
+                            [
147
+                                RELOP => RELOP_GT,
148
+                                ULPROPTAG => $this->proptags["duedate"],
149
+                                VALUE => $end,
150
+                            ],
151
+                        ],
152
+                    ],
153
+                ],
154
+                // OR
155
+                [
156
+                    RES_OR,
157
+                    [
158
+                        // OR
159
+                        // (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
160
+                        [
161
+                            RES_AND,
162
+                            [
163
+                                [
164
+                                    RES_EXIST,
165
+                                    [ULPROPTAG => $this->proptags["enddate_recurring"]],
166
+                                ],
167
+                                [
168
+                                    RES_PROPERTY,
169
+                                    [
170
+                                        RELOP => RELOP_EQ,
171
+                                        ULPROPTAG => $this->proptags["recurring"],
172
+                                        VALUE => true,
173
+                                    ],
174
+                                ],
175
+                                [
176
+                                    RES_PROPERTY,
177
+                                    [
178
+                                        RELOP => RELOP_GE,
179
+                                        ULPROPTAG => $this->proptags["enddate_recurring"],
180
+                                        VALUE => $start,
181
+                                    ],
182
+                                ],
183
+                            ],
184
+                        ],
185
+                        // OR
186
+                        // (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
187
+                        [
188
+                            RES_AND,
189
+                            [
190
+                                [
191
+                                    RES_NOT,
192
+                                    [
193
+                                        [
194
+                                            RES_EXIST,
195
+                                            [ULPROPTAG => $this->proptags["enddate_recurring"],
196
+                                            ],
197
+                                        ],
198
+                                    ],
199
+                                ],
200
+                                [
201
+                                    RES_PROPERTY,
202
+                                    [
203
+                                        RELOP => RELOP_LE,
204
+                                        ULPROPTAG => $this->proptags["startdate"],
205
+                                        VALUE => $end,
206
+                                    ],
207
+                                ],
208
+                                [
209
+                                    RES_PROPERTY,
210
+                                    [
211
+                                        RELOP => RELOP_EQ,
212
+                                        ULPROPTAG => $this->proptags["recurring"],
213
+                                        VALUE => true,
214
+                                    ],
215
+                                ],
216
+                            ],
217
+                        ],
218
+                    ],
219
+                ], // EXISTS OR
220
+            ],
221
+        ];        // global OR
222
+
223
+        $contents = mapi_folder_getcontentstable($this->calendar);
224
+        mapi_table_restrict($contents, $restrict);
225
+
226
+        while (1) {
227
+            $rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
228
+
229
+            if (!is_array($rows)) {
230
+                break;
231
+            }
232
+
233
+            if (empty($rows)) {
234
+                break;
235
+            }
236
+
237
+            foreach ($rows as $row) {
238
+                $occurrences = [];
239
+                if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
240
+                    $recur = new Recurrence($this->store, $row);
241
+
242
+                    $occurrences = $recur->getItems($starttime, $length);
243
+                }
244
+                else {
245
+                    $occurrences[] = $row;
246
+                }
247
+
248
+                $calendaritems = array_merge($calendaritems, $occurrences);
249
+            }
250
+        }
251
+
252
+        // $calendaritems now contains all the calendar items in the specified time
253
+        // frame. We now need to merge these into a flat array of begin/end/status
254
+        // objects. This also filters out all the 'free' items (status 0)
255
+        return $this->mergeItemsFB($calendaritems);
256
+    }
257
+
258
+    /**
259
+     * Publishes Free/Busy information of user.
260
+     *
261
+     * @param timestamp $starttime Time from which to publish data  (usually now)
262
+     * @param timestamp $length    Time of seconds from $starttime we should publish
263
+     * @param mixed     $start
264
+     * @param mixed     $end
265
+     */
266
+    public function publishFB($start, $end) {
267
+        $freebusy = $this->getCalendarData($start, $end);
268
+
269
+        // Get the FB interface
270
+        try {
271
+            $fbsupport = mapi_freebusysupport_open($this->session, $this->store);
272
+        }
273
+        catch (MAPIException $e) {
274
+            if ($e->getCode() == MAPI_E_NOT_FOUND) {
275
+                $e->setHandled();
276
+                SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object.");
277
+            }
278
+        }
279
+
280
+        // Open updater for this user
281
+        if (isset($fbsupport) && $fbsupport) {
282
+            $updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]);
283
+
284
+            $updater = $updaters[0];
285
+
286
+            // Send the data
287
+            mapi_freebusyupdate_reset($updater);
288
+            mapi_freebusyupdate_publish($updater, $freebusy);
289
+            mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end);
290
+
291
+            // We're finished
292
+            mapi_freebusysupport_close($fbsupport);
293
+        }
294
+        else {
295
+            SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available");
296
+        }
297
+    }
298
+
299
+    /**
300
+     * Sorts by timestamp, if equal, then end before start.
301
+     *
302
+     * @param mixed $a
303
+     * @param mixed $b
304
+     */
305
+    public function cmp($a, $b) {
306
+        if ($a["time"] == $b["time"]) {
307
+            if ($a["type"] < $b["type"]) {
308
+                return 1;
309
+            }
310
+            if ($a["type"] > $b["type"]) {
311
+                return -1;
312
+            }
313
+
314
+            return 0;
315
+        }
316
+
317
+        return $a["time"] > $b["time"] ? 1 : -1;
318
+    }
319
+
320
+    /**
321
+     * Function mergeItems.
322
+     *
323
+     * @author Steve Hardy
324
+     *
325
+     * @param mixed $items
326
+     */
327
+    public function mergeItemsFB($items) {
328
+        $merged = [];
329
+        $timestamps = [];
330
+        $csubj = [];
331
+        $cbusy = [];
332
+        $level = 0;
333
+        $laststart = null;
334
+
335
+        foreach ($items as $item) {
336
+            $ts["type"] = 0;
337
+            $ts["time"] = $item[$this->proptags["startdate"]];
338
+            $ts["subject"] = $item[PR_SUBJECT];
339
+            $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197
340
+            $timestamps[] = $ts;
341
+
342
+            $ts["type"] = 1;
343
+            $ts["time"] = $item[$this->proptags["duedate"]];
344
+            $ts["subject"] = $item[PR_SUBJECT];
345
+            $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197
346
+            $timestamps[] = $ts;
347
+        }
348
+
349
+        usort($timestamps, [$this, "cmp"]);
350
+        $laststart = 0; // seb added
351
+
352
+        foreach ($timestamps as $ts) {
353
+            switch ($ts["type"]) {
354
+                case 0: // Start
355
+                    if ($level != 0 && $laststart != $ts["time"]) {
356
+                        $newitem["start"] = $laststart;
357
+                        $newitem["end"] = $ts["time"];
358
+                        $newitem["subject"] = join(",", $csubj);
359
+                        $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
360
+                        if ($newitem["status"] > 0) {
361
+                            $merged[] = $newitem;
362
+                        }
363
+                    }
364
+
365
+                    ++$level;
366
+
367
+                    $csubj[] = $ts["subject"];
368
+                    $cbusy[] = $ts["status"];
369
+
370
+                    $laststart = $ts["time"];
371
+
372
+                    break;
373
+
374
+                case 1: // End
375
+                    if ($laststart != $ts["time"]) {
376
+                        $newitem["start"] = $laststart;
377
+                        $newitem["end"] = $ts["time"];
378
+                        $newitem["subject"] = join(",", $csubj);
379
+                        $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
380
+                        if ($newitem["status"] > 0) {
381
+                            $merged[] = $newitem;
382
+                        }
383
+                    }
384
+
385
+                    --$level;
386
+
387
+                    array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
388
+                    array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
389
+
390
+                    $laststart = $ts["time"];
391
+
392
+                    break;
393
+            }
394
+        }
395
+
396
+        return $merged;
397
+    }
398 398
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -218,7 +218,7 @@
 block discarded – undo
218 218
 					],
219 219
 				], // EXISTS OR
220 220
 			],
221
-		];        // global OR
221
+		]; // global OR
222 222
 
223 223
 		$contents = mapi_folder_getcontentstable($this->calendar);
224 224
 		mapi_table_restrict($contents, $restrict);
Please login to merge, or discard this patch.
Braces   +3 added lines, -6 removed lines patch added patch discarded remove patch
@@ -240,8 +240,7 @@  discard block
 block discarded – undo
240 240
 					$recur = new Recurrence($this->store, $row);
241 241
 
242 242
 					$occurrences = $recur->getItems($starttime, $length);
243
-				}
244
-				else {
243
+				} else {
245 244
 					$occurrences[] = $row;
246 245
 				}
247 246
 
@@ -269,8 +268,7 @@  discard block
 block discarded – undo
269 268
 		// Get the FB interface
270 269
 		try {
271 270
 			$fbsupport = mapi_freebusysupport_open($this->session, $this->store);
272
-		}
273
-		catch (MAPIException $e) {
271
+		} catch (MAPIException $e) {
274 272
 			if ($e->getCode() == MAPI_E_NOT_FOUND) {
275 273
 				$e->setHandled();
276 274
 				SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object.");
@@ -290,8 +288,7 @@  discard block
 block discarded – undo
290 288
 
291 289
 			// We're finished
292 290
 			mapi_freebusysupport_close($fbsupport);
293
-		}
294
-		else {
291
+		} else {
295 292
 			SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available");
296 293
 		}
297 294
 	}
Please login to merge, or discard this patch.
mapi/class.baserecurrence.php 4 patches
Indentation   +1978 added lines, -1978 removed lines patch added patch discarded remove patch
@@ -5,1983 +5,1983 @@
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6 6
  */
7 7
 
8
-	/**
9
-	 * BaseRecurrence
10
-	 * this class is superclass for recurrence for appointments and tasks. This class provides all
11
-	 * basic features of recurrence.
12
-	 */
13
-	class BaseRecurrence {
14
-		/**
15
-		 * @var object Mapi Message Store (may be null if readonly)
16
-		 */
17
-		public $store;
18
-
19
-		/**
20
-		 * @var object Mapi Message (may be null if readonly)
21
-		 */
22
-		public $message;
23
-
24
-		/**
25
-		 * @var array Message Properties
26
-		 */
27
-		public $messageprops;
28
-
29
-		/**
30
-		 * @var array list of property tags
31
-		 */
32
-		public $proptags;
33
-
34
-		/**
35
-		 * @var recurrence data of this calendar item
36
-		 */
37
-		public $recur;
38
-
39
-		/**
40
-		 * @var Timezone data of this calendar item
41
-		 */
42
-		public $tz;
43
-
44
-		/**
45
-		 * Constructor.
46
-		 *
47
-		 * @param resource $store      MAPI Message Store Object
48
-		 * @param resource $message    the MAPI (appointment) message
49
-		 * @param array    $properties the list of MAPI properties the message has
50
-		 */
51
-		public function __construct($store, $message) {
52
-			$this->store = $store;
53
-
54
-			if (is_array($message)) {
55
-				$this->messageprops = $message;
56
-			}
57
-			else {
58
-				$this->message = $message;
59
-				$this->messageprops = mapi_getprops($this->message, $this->proptags);
60
-			}
61
-
62
-			if (isset($this->messageprops[$this->proptags["recurring_data"]])) {
63
-				// There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
64
-				if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
65
-					$this->getFullRecurrenceBlob();
66
-				}
67
-
68
-				$this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
69
-			}
70
-			if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) {
71
-				$this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
72
-			}
73
-		}
74
-
75
-		public function getRecurrence() {
76
-			return $this->recur;
77
-		}
78
-
79
-		public function getFullRecurrenceBlob() {
80
-			$message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
81
-
82
-			$recurrBlob = '';
83
-			$stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
84
-			$stat = mapi_stream_stat($stream);
85
-
86
-			for ($i = 0; $i < $stat['cb']; $i += 1024) {
87
-				$recurrBlob .= mapi_stream_read($stream, 1024);
88
-			}
89
-
90
-			if (!empty($recurrBlob)) {
91
-				$this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
92
-			}
93
-		}
94
-
95
-		/**
96
-		 * Function for parsing the Recurrence value of a Calendar item.
97
-		 *
98
-		 * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
99
-		 * data to this function
100
-		 *
101
-		 * Returns a structure containing the data:
102
-		 *
103
-		 * type        - type of recurrence: day=10, week=11, month=12, year=13
104
-		 * subtype    - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday)
105
-		 * start    - unix timestamp of first occurrence
106
-		 * end        - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
107
-		 * numoccur     - occurrences (may be very large when there is no end data)
108
-		 *
109
-		 * then, for each type:
110
-		 *
111
-		 * Daily:
112
-		 *  everyn    - every [everyn] days in minutes
113
-		 *  regen    - regenerating event (like tasks)
114
-		 *
115
-		 * Weekly:
116
-		 *  everyn    - every [everyn] weeks in weeks
117
-		 *  regen    - regenerating event (like tasks)
118
-		 *  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
119
-		 *
120
-		 * Monthly:
121
-		 *  everyn    - every [everyn] months
122
-		 *  regen    - regenerating event (like tasks)
123
-		 *
124
-		 *  subtype 2:
125
-		 *      monthday - on day [monthday] of the month
126
-		 *
127
-		 *  subtype 3:
128
-		 *      weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
129
-		 *   nday    - on [nday]'th [weekdays] of the month
130
-		 *
131
-		 * Yearly:
132
-		 *  everyn    - every [everyn] months (12, 24, 36, ...)
133
-		 *  month    - in month [month] (although the month is encoded in minutes since the startning of the year ........)
134
-		 *  regen    - regenerating event (like tasks)
135
-		 *
136
-		 *  subtype 2:
137
-		 *   monthday - on day [monthday] of the month
138
-		 *
139
-		 *  subtype 3:
140
-		 *   weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
141
-		 *      nday    - on [nday]'th [weekdays] of the month [month]
142
-		 *
143
-		 * @param string $rdata Binary string
144
-		 *
145
-		 * @return array recurrence data
146
-		 */
147
-		public function parseRecurrence($rdata) {
148
-			if (strlen($rdata) < 10) {
149
-				return;
150
-			}
151
-
152
-			$ret["changed_occurrences"] = [];
153
-			$ret["deleted_occurrences"] = [];
154
-
155
-			$data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
156
-
157
-			$ret["type"] = $data["rtype"];
158
-			$ret["subtype"] = $data["rtype2"];
159
-			$rdata = substr($rdata, 10);
160
-
161
-			switch ($data["rtype"]) {
162
-				case 0x0A:
163
-					// Daily
164
-					if (strlen($rdata) < 12) {
165
-						return $ret;
166
-					}
167
-
168
-					$data = unpack("Vunknown/Veveryn/Vregen", $rdata);
169
-					$ret["everyn"] = $data["everyn"];
170
-					$ret["regen"] = $data["regen"];
171
-
172
-					switch ($ret["subtype"]) {
173
-						case 0:
174
-							$rdata = substr($rdata, 12);
175
-
176
-							break;
177
-
178
-						case 1:
179
-							$rdata = substr($rdata, 16);
180
-
181
-							break;
182
-					}
183
-
184
-					break;
185
-
186
-				case 0x0B:
187
-					// Weekly
188
-					if (strlen($rdata) < 16) {
189
-						return $ret;
190
-					}
191
-
192
-					$data = unpack("Vconst1/Veveryn/Vregen", $rdata);
193
-					$rdata = substr($rdata, 12);
194
-
195
-					$ret["everyn"] = $data["everyn"];
196
-					$ret["regen"] = $data["regen"];
197
-					$ret["weekdays"] = 0;
198
-
199
-					if ($data["regen"] == 0) {
200
-						$data = unpack("Vweekdays", $rdata);
201
-						$rdata = substr($rdata, 4);
202
-
203
-						$ret["weekdays"] = $data["weekdays"];
204
-					}
205
-
206
-					break;
207
-
208
-				case 0x0C:
209
-					// Monthly
210
-					if (strlen($rdata) < 16) {
211
-						return $ret;
212
-					}
213
-
214
-					$data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
215
-
216
-					$ret["everyn"] = $data["everyn"];
217
-					$ret["regen"] = $data["regen"];
218
-
219
-					if ($ret["subtype"] == 3) {
220
-						$ret["weekdays"] = $data["monthday"];
221
-					}
222
-					else {
223
-						$ret["monthday"] = $data["monthday"];
224
-					}
225
-
226
-					$rdata = substr($rdata, 16);
227
-
228
-					if ($ret["subtype"] == 3) {
229
-						$data = unpack("Vnday", $rdata);
230
-						$ret["nday"] = $data["nday"];
231
-						$rdata = substr($rdata, 4);
232
-					}
233
-
234
-					break;
235
-
236
-				case 0x0D:
237
-					// Yearly
238
-					if (strlen($rdata) < 16) {
239
-						return $ret;
240
-					}
241
-
242
-					$data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
243
-
244
-					$ret["month"] = $data["month"];
245
-					$ret["everyn"] = $data["everyn"];
246
-					$ret["regen"] = $data["regen"];
247
-
248
-					if ($ret["subtype"] == 3) {
249
-						$ret["weekdays"] = $data["monthday"];
250
-					}
251
-					else {
252
-						$ret["monthday"] = $data["monthday"];
253
-					}
254
-
255
-					$rdata = substr($rdata, 16);
256
-
257
-					if ($ret["subtype"] == 3) {
258
-						$data = unpack("Vnday", $rdata);
259
-						$ret["nday"] = $data["nday"];
260
-						$rdata = substr($rdata, 4);
261
-					}
262
-
263
-					break;
264
-			}
265
-
266
-			if (strlen($rdata) < 16) {
267
-				return $ret;
268
-			}
269
-
270
-			$data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
271
-
272
-			$rdata = substr($rdata, 16);
273
-
274
-			$ret["term"] = $data["term"];
275
-			$ret["numoccur"] = $data["numoccur"];
276
-			$ret["numexcept"] = $data["numexcept"];
277
-
278
-			// exc_base_dates are *all* the base dates that have been either deleted or modified
279
-			$exc_base_dates = [];
280
-			for ($i = 0; $i < $ret["numexcept"]; ++$i) {
281
-				if (strlen($rdata) < 4) {
282
-					// We shouldn't arrive here, because that implies
283
-					// numexcept does not match the amount of data
284
-					// which is available for the exceptions.
285
-					return $ret;
286
-				}
287
-				$data = unpack("Vbasedate", $rdata);
288
-				$rdata = substr($rdata, 4);
289
-				$exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
290
-			}
291
-
292
-			if (strlen($rdata) < 4) {
293
-				return $ret;
294
-			}
295
-
296
-			$data = unpack("Vnumexceptmod", $rdata);
297
-			$rdata = substr($rdata, 4);
298
-
299
-			$ret["numexceptmod"] = $data["numexceptmod"];
300
-
301
-			// exc_changed are the base dates of *modified* occurrences. exactly what is modified
302
-			// is in the attachments *and* in the data further down this function.
303
-			$exc_changed = [];
304
-			for ($i = 0; $i < $ret["numexceptmod"]; ++$i) {
305
-				if (strlen($rdata) < 4) {
306
-					// We shouldn't arrive here, because that implies
307
-					// numexceptmod does not match the amount of data
308
-					// which is available for the exceptions.
309
-					return $ret;
310
-				}
311
-				$data = unpack("Vstartdate", $rdata);
312
-				$rdata = substr($rdata, 4);
313
-				$exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
314
-			}
315
-
316
-			if (strlen($rdata) < 8) {
317
-				return $ret;
318
-			}
319
-
320
-			$data = unpack("Vstart/Vend", $rdata);
321
-			$rdata = substr($rdata, 8);
322
-
323
-			$ret["start"] = $this->recurDataToUnixData($data["start"]);
324
-			$ret["end"] = $this->recurDataToUnixData($data["end"]);
325
-
326
-			// this is where task recurrence stop
327
-			if (strlen($rdata) < 16) {
328
-				return $ret;
329
-			}
330
-
331
-			$data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
332
-			$rdata = substr($rdata, 16);
333
-
334
-			$ret["startocc"] = $data["startmin"];
335
-			$ret["endocc"] = $data["endmin"];
336
-			$writerversion = $data["writerversion"];
337
-
338
-			$data = unpack("vnumber", $rdata);
339
-			$rdata = substr($rdata, 2);
340
-
341
-			$nexceptions = $data["number"];
342
-			$exc_changed_details = [];
343
-
344
-			// Parse n modified exceptions
345
-			for ($i = 0; $i < $nexceptions; ++$i) {
346
-				$item = [];
347
-
348
-				// Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
349
-				$data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
350
-				$rdata = substr($rdata, 12);
351
-
352
-				// Convert recurtimestamp to unix timestamp
353
-				$startdate = $this->recurDataToUnixData($data["startdate"]);
354
-				$enddate = $this->recurDataToUnixData($data["enddate"]);
355
-				$basedate = $this->recurDataToUnixData($data["basedate"]);
356
-
357
-				// Set the right properties
358
-				$item["basedate"] = $this->dayStartOf($basedate);
359
-				$item["start"] = $startdate;
360
-				$item["end"] = $enddate;
361
-
362
-				$data = unpack("vbitmask", $rdata);
363
-				$rdata = substr($rdata, 2);
364
-				$item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
365
-
366
-				// Bitmask to verify what properties are changed
367
-				$bitmask = $data["bitmask"];
368
-
369
-				// ARO_SUBJECT: 0x0001
370
-				// Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
371
-				if (($bitmask & (1 << 0))) {
372
-					$data = unpack("vnull_length/vlength", $rdata);
373
-					$rdata = substr($rdata, 4);
374
-
375
-					$length = $data["length"];
376
-					$item["subject"] = ""; // Normalized subject
377
-					for ($j = 0; $j < $length && strlen($rdata); ++$j) {
378
-						$data = unpack("Cchar", $rdata);
379
-						$rdata = substr($rdata, 1);
380
-
381
-						$item["subject"] .= chr($data["char"]);
382
-					}
383
-				}
384
-
385
-				// ARO_MEETINGTYPE: 0x0002
386
-				if (($bitmask & (1 << 1))) {
387
-					$rdata = substr($rdata, 4);
388
-					// Attendees modified: no data here (only in attachment)
389
-				}
390
-
391
-				// ARO_REMINDERDELTA: 0x0004
392
-				// Look for field: ReminderDelta (4b)
393
-				if (($bitmask & (1 << 2))) {
394
-					$data = unpack("Vremind_before", $rdata);
395
-					$rdata = substr($rdata, 4);
396
-
397
-					$item["remind_before"] = $data["remind_before"];
398
-				}
399
-
400
-				// ARO_REMINDER: 0x0008
401
-				// Look field: ReminderSet (4b)
402
-				if (($bitmask & (1 << 3))) {
403
-					$data = unpack("Vreminder_set", $rdata);
404
-					$rdata = substr($rdata, 4);
405
-
406
-					$item["reminder_set"] = $data["reminder_set"];
407
-				}
408
-
409
-				// ARO_LOCATION: 0x0010
410
-				// Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
411
-				// Similar to ARO_SUBJECT above.
412
-				if (($bitmask & (1 << 4))) {
413
-					$data = unpack("vnull_length/vlength", $rdata);
414
-					$rdata = substr($rdata, 4);
415
-
416
-					$item["location"] = "";
417
-
418
-					$length = $data["length"];
419
-					$data = substr($rdata, 0, $length);
420
-					$rdata = substr($rdata, $length);
421
-
422
-					$item["location"] .= $data;
423
-				}
424
-
425
-				// ARO_BUSYSTATUS: 0x0020
426
-				// Look for field: BusyStatus (4b)
427
-				if (($bitmask & (1 << 5))) {
428
-					$data = unpack("Vbusystatus", $rdata);
429
-					$rdata = substr($rdata, 4);
430
-
431
-					$item["busystatus"] = $data["busystatus"];
432
-				}
433
-
434
-				// ARO_ATTACHMENT: 0x0040
435
-				if (($bitmask & (1 << 6))) {
436
-					// no data: RESERVED
437
-					$rdata = substr($rdata, 4);
438
-				}
439
-
440
-				// ARO_SUBTYPE: 0x0080
441
-				// Look for field: SubType (4b). Determines whether it is an allday event.
442
-				if (($bitmask & (1 << 7))) {
443
-					$data = unpack("Vallday", $rdata);
444
-					$rdata = substr($rdata, 4);
445
-
446
-					$item["alldayevent"] = $data["allday"];
447
-				}
448
-
449
-				// ARO_APPTCOLOR: 0x0100
450
-				// Look for field: AppointmentColor (4b)
451
-				if (($bitmask & (1 << 8))) {
452
-					$data = unpack("Vlabel", $rdata);
453
-					$rdata = substr($rdata, 4);
454
-
455
-					$item["label"] = $data["label"];
456
-				}
457
-
458
-				// ARO_EXCEPTIONAL_BODY: 0x0200
459
-				if (($bitmask & (1 << 9))) {
460
-					// Notes or Attachments modified: no data here (only in attachment)
461
-				}
462
-
463
-				array_push($exc_changed_details, $item);
464
-			}
465
-
466
-			/**
467
-			 * We now have $exc_changed, $exc_base_dates and $exc_changed_details
468
-			 * We will ignore $exc_changed, as this information is available in $exc_changed_details
469
-			 * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
470
-			 * has been deleted.
471
-			 */
472
-
473
-			// Find deleted occurrences
474
-			$deleted_occurrences = [];
475
-
476
-			foreach ($exc_base_dates as $base_date) {
477
-				$found = false;
478
-
479
-				foreach ($exc_changed_details as $details) {
480
-					if ($details["basedate"] == $base_date) {
481
-						$found = true;
482
-
483
-						break;
484
-					}
485
-				}
486
-				if (!$found) {
487
-					// item was not in exc_changed_details, so it must be deleted
488
-					$deleted_occurrences[] = $base_date;
489
-				}
490
-			}
491
-
492
-			$ret["deleted_occurrences"] = $deleted_occurrences;
493
-			$ret["changed_occurrences"] = $exc_changed_details;
494
-
495
-			// enough data for normal exception (no extended data)
496
-			if (strlen($rdata) < 16) {
497
-				return $ret;
498
-			}
499
-
500
-			$data = unpack("Vreservedsize", $rdata);
501
-			$rdata = substr($rdata, 4 + $data["reservedsize"]);
502
-
503
-			for ($i = 0; $i < $nexceptions; ++$i) {
504
-				// subject and location in ucs-2 to utf-8
505
-				if ($writerversion >= 0x3009) {
506
-					$data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
507
-					$rdata = substr($rdata, 4 + $data["size"]);
508
-				}
509
-
510
-				$data = unpack("Vreservedsize", $rdata);
511
-				$rdata = substr($rdata, 4 + $data["reservedsize"]);
512
-
513
-				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
514
-				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
515
-					$data = unpack("Vstart/Vend/Vorig", $rdata);
516
-					$rdata = substr($rdata, 4 * 3);
517
-
518
-					$exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
519
-					$exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
520
-					$exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
521
-				}
522
-
523
-				// ARO_SUBJECT
524
-				if ($exc_changed_details[$i]["bitmask"] & 0x01) {
525
-					// decode ucs2 string to utf-8
526
-					$data = unpack("vlength", $rdata);
527
-					$rdata = substr($rdata, 2);
528
-					$length = $data["length"];
529
-					$data = substr($rdata, 0, $length * 2);
530
-					$rdata = substr($rdata, $length * 2);
531
-					$subject = iconv("UCS-2LE", "UTF-8", $data);
532
-					// replace subject with unicode subject
533
-					$exc_changed_details[$i]["subject"] = $subject;
534
-				}
535
-
536
-				// ARO_LOCATION
537
-				if ($exc_changed_details[$i]["bitmask"] & 0x10) {
538
-					// decode ucs2 string to utf-8
539
-					$data = unpack("vlength", $rdata);
540
-					$rdata = substr($rdata, 2);
541
-					$length = $data["length"];
542
-					$data = substr($rdata, 0, $length * 2);
543
-					$rdata = substr($rdata, $length * 2);
544
-					$location = iconv("UCS-2LE", "UTF-8", $data);
545
-					// replace subject with unicode subject
546
-					$exc_changed_details[$i]["location"] = $location;
547
-				}
548
-
549
-				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
550
-				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
551
-					$data = unpack("Vreservedsize", $rdata);
552
-					$rdata = substr($rdata, 4 + $data["reservedsize"]);
553
-				}
554
-			}
555
-
556
-			// update with extended data
557
-			$ret["changed_occurrences"] = $exc_changed_details;
558
-
559
-			return $ret;
560
-		}
561
-
562
-		/**
563
-		 * Saves the recurrence data to the recurrence property.
564
-		 */
565
-		public function saveRecurrence() {
566
-			// Only save if a message was passed
567
-			if (!isset($this->message)) {
568
-				return;
569
-			}
570
-
571
-			// Abort if no recurrence was set
572
-			if (!isset($this->recur["type"], $this->recur["subtype"], $this->recur["start"], $this->recur["end"], $this->recur["startocc"], $this->recur["endocc"])) {
573
-				return;
574
-			}
575
-
576
-			$rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
577
-
578
-			$weekstart = 1; // monday
579
-			$forwardcount = 0;
580
-			$restocc = 0;
581
-			$dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
582
-
583
-			$term = (int) $this->recur["type"];
584
-
585
-			switch ($term) {
586
-				case 0x0A:
587
-					// Daily
588
-					if (!isset($this->recur["everyn"])) {
589
-						return;
590
-					}
591
-
592
-					if ($this->recur["subtype"] == 1) {
593
-						// Daily every workday
594
-						$rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
595
-					}
596
-					else {
597
-						// Daily every N days (everyN in minutes)
598
-
599
-						$everyn = ((int) $this->recur["everyn"]) / 1440;
600
-
601
-						// Calc first occ
602
-						$firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
603
-
604
-						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
605
-					}
606
-
607
-					break;
608
-
609
-				case 0x0B:
610
-					// Weekly
611
-					if (!isset($this->recur["everyn"])) {
612
-						return;
613
-					}
614
-
615
-					if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
616
-						return;
617
-					}
618
-
619
-					// No need to calculate startdate if sliding flag was set.
620
-					if (!$this->recur['regen']) {
621
-						// Calculate start date of recurrence
622
-
623
-						// Find the first day that matches one of the weekdays selected
624
-						$daycount = 0;
625
-						$dayskip = -1;
626
-						for ($j = 0; $j < 7; ++$j) {
627
-							if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
628
-								if ($dayskip == -1) {
629
-									$dayskip = $j;
630
-								}
631
-
632
-								++$daycount;
633
-							}
634
-						}
635
-
636
-						// $dayskip is the number of days to skip from the startdate until the first occurrence
637
-						// $daycount is the number of days per week that an occurrence occurs
638
-
639
-						$weekskip = 0;
640
-						if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) {
641
-							$weekskip = 1;
642
-						}
643
-
644
-						// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
645
-						// remaining occurrences based on the start of the recurrence.
646
-						if (((int) $this->recur["term"]) == 0x22) {
647
-							// $weekskip is the amount of weeks to skip from the startdate before the first occurrence
648
-							// $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
649
-							// is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
650
-							// (eg when numoccur = 2, and daycount = 1)
651
-							$forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount);
652
-
653
-							// $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
654
-							// for the occurrence on the first day
655
-							$restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
656
-
657
-							// $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
658
-							$forwardcount *= (int) $this->recur["everyn"];
659
-						}
660
-
661
-						// The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
662
-						$this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
663
-					}
664
-
665
-					// Calc first occ
666
-					$firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60);
667
-
668
-					$firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
669
-
670
-					if ($this->recur["regen"]) {
671
-						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
672
-					}
673
-					else {
674
-						$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
675
-					}
676
-
677
-					break;
678
-
679
-				case 0x0C:
680
-					// Monthly
681
-				case 0x0D:
682
-					// Yearly
683
-					if (!isset($this->recur["everyn"])) {
684
-						return;
685
-					}
686
-					if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) {
687
-						return;
688
-					}
689
-
690
-					if ($term == 0x0C /* monthly */) {
691
-						$everyn = (int) $this->recur["everyn"];
692
-					}
693
-					else {
694
-						$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
695
-					}
696
-
697
-					// Get montday/month/year of original start
698
-					$curmonthday = gmdate("j", (int) $this->recur["start"]);
699
-					$curyear = gmdate("Y", (int) $this->recur["start"]);
700
-					$curmonth = gmdate("n", (int) $this->recur["start"]);
701
-
702
-					// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
703
-					// remaining occurrences based on the start of the recurrence.
704
-					if (((int) $this->recur["term"]) == 0x22) {
705
-						// $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
706
-						// one to make sure there are always at least one occurrence left)
707
-						$forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn);
708
-					}
709
-
710
-					// Get month for yearly on D'th day of month M
711
-					if ($term == 0x0D /* yearly */) {
712
-						$selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg
713
-					}
714
-
715
-					switch ((int) $this->recur["subtype"]) {
716
-						// on D day of every M month
717
-						case 2:
718
-							if (!isset($this->recur["monthday"])) {
719
-								return;
720
-							}
721
-							// Recalc startdate
722
-
723
-							// Set on the right begin day
724
-
725
-							// Go the beginning of the month
726
-							$this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60;
727
-							// Go the the correct month day
728
-							$this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60;
729
-
730
-							// If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence
731
-							if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) ||
732
-								($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) {
733
-								if ($term == 0x0D /* yearly */) {
734
-									if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date
735
-										$count = $everyn - ($curmonth - $selmonth);
736
-									}
737
-									elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date
738
-										$count = $selmonth - $curmonth;
739
-									}
740
-									else {
741
-										// Go to next occurrence while recurrence start date is greater than occurrence date but within same month
742
-										if (((int) $this->recur["monthday"]) < $curmonthday) {
743
-											$count = $everyn;
744
-										}
745
-									}
746
-								}
747
-								else {
748
-									$count = $everyn; // Monthly, go to next occurrence in 'everyn' months
749
-								}
750
-
751
-								// Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
752
-								for ($i = 0; $i < $count; ++$i) {
753
-									$this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
754
-
755
-									if ($curmonth == 12) {
756
-										++$curyear;
757
-										$curmonth = 0;
758
-									}
759
-									++$curmonth;
760
-								}
761
-							}
762
-
763
-							// "start" is now pointing to the first occurrence, except that it will overshoot if the
764
-							// month in which it occurs has less days than specified as the day of the month. So 31st
765
-							// of each month will overshoot in february (29 days). We compensate for that by checking
766
-							// if the day of the month we got is wrong, and then back up to the last day of the previous
767
-							// month.
768
-							if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
769
-								gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) {
770
-								$this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60;
771
-							}
772
-
773
-							// "start" is now the first occurrence
774
-
775
-							if ($term == 0x0C /* monthly */) {
776
-								// Calc first occ
777
-								$monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
778
-
779
-								$firstocc = 0;
780
-								for ($i = 0; $i < $monthIndex; ++$i) {
781
-									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
782
-								}
783
-
784
-								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
785
-							}
786
-							else {
787
-								// Calc first occ
788
-								$firstocc = 0;
789
-								$monthIndex = (int) gmdate("n", $this->recur["start"]);
790
-								for ($i = 1; $i < $monthIndex; ++$i) {
791
-									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
792
-								}
793
-
794
-								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
795
-							}
796
-
797
-							break;
798
-
799
-						case 3:
800
-							// monthly: on Nth weekday of every M month
801
-							// yearly: on Nth weekday of M month
802
-							if (!isset($this->recur["weekdays"], $this->recur["nday"])) {
803
-								return;
804
-							}
805
-
806
-							$weekdays = (int) $this->recur["weekdays"];
807
-							$nday = (int) $this->recur["nday"];
808
-
809
-							// Calc startdate
810
-							$monthbegindow = (int) $this->recur["start"];
811
-
812
-							if ($nday == 5) {
813
-								// Set date on the last day of the last month
814
-								$monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
815
-							}
816
-							else {
817
-								// Set on the first day of the month
818
-								$monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
819
-							}
820
-
821
-							if ($term == 0x0D /* yearly */) {
822
-								// Set on right month
823
-								if ($selmonth < $curmonth) {
824
-									$tmp = 12 - $curmonth + $selmonth;
825
-								}
826
-								else {
827
-									$tmp = ($selmonth - $curmonth);
828
-								}
829
-
830
-								for ($i = 0; $i < $tmp; ++$i) {
831
-									$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
832
-
833
-									if ($curmonth == 12) {
834
-										++$curyear;
835
-										$curmonth = 0;
836
-									}
837
-									++$curmonth;
838
-								}
839
-							}
840
-							else {
841
-								// Check or you exist in the right month
842
-
843
-								$dayofweek = gmdate("w", $monthbegindow);
844
-								for ($i = 0; $i < 7; ++$i) {
845
-									if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
846
-										$day = gmdate("j", $monthbegindow) - $i;
847
-
848
-										break;
849
-									}
850
-									if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
851
-										$day = (($nday - 1) * 7) + ($i + 1);
852
-
853
-										break;
854
-									}
855
-								}
856
-
857
-								// Goto the next X month
858
-								if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) {
859
-									if ($nday == 5) {
860
-										$monthbegindow += 24 * 60 * 60;
861
-										if ($curmonth == 12) {
862
-											++$curyear;
863
-											$curmonth = 0;
864
-										}
865
-										++$curmonth;
866
-									}
867
-
868
-									for ($i = 0; $i < $everyn; ++$i) {
869
-										$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
870
-
871
-										if ($curmonth == 12) {
872
-											++$curyear;
873
-											$curmonth = 0;
874
-										}
875
-										++$curmonth;
876
-									}
877
-
878
-									if ($nday == 5) {
879
-										$monthbegindow -= 24 * 60 * 60;
880
-									}
881
-								}
882
-							}
883
-
884
-							// FIXME: weekstart?
885
-
886
-							$day = 0;
887
-							// Set start on the right day
888
-							$dayofweek = gmdate("w", $monthbegindow);
889
-							for ($i = 0; $i < 7; ++$i) {
890
-								if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
891
-									$day = $i;
892
-
893
-									break;
894
-								}
895
-								if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
896
-									$day = ($nday - 1) * 7 + ($i + 1);
897
-
898
-									break;
899
-								}
900
-							}
901
-							if ($nday == 5) {
902
-								$monthbegindow -= $day * 24 * 60 * 60;
903
-							}
904
-							else {
905
-								$monthbegindow += ($day - 1) * 24 * 60 * 60;
906
-							}
907
-
908
-							$firstocc = 0;
909
-
910
-							if ($term == 0x0C /* monthly */) {
911
-								// Calc first occ
912
-								$monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
913
-
914
-								for ($i = 0; $i < $monthIndex; ++$i) {
915
-									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
916
-								}
917
-
918
-								$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
919
-							}
920
-							else {
921
-								// Calc first occ
922
-								$monthIndex = (int) gmdate("n", $this->recur["start"]);
923
-
924
-								for ($i = 1; $i < $monthIndex; ++$i) {
925
-									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
926
-								}
927
-
928
-								$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
929
-							}
930
-
931
-							break;
932
-					}
933
-
934
-					break;
935
-			}
936
-
937
-			if (!isset($this->recur["term"])) {
938
-				return;
939
-			}
940
-
941
-			// Terminate
942
-			$term = (int) $this->recur["term"];
943
-			$rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
944
-
945
-			switch ($term) {
946
-				// After the given enddate
947
-				case 0x21:
948
-					$rdata .= pack("V", 10);
949
-
950
-					break;
951
-				// After a number of times
952
-				case 0x22:
953
-					if (!isset($this->recur["numoccur"])) {
954
-						return;
955
-					}
956
-
957
-					$rdata .= pack("V", (int) $this->recur["numoccur"]);
958
-
959
-					break;
960
-				// Never ends
961
-				case 0x23:
962
-					$rdata .= pack("V", 0);
963
-
964
-					break;
965
-			}
966
-
967
-			// Strange little thing for the recurrence type "every workday"
968
-			if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
969
-				$rdata .= pack("V", 1);
970
-			}
971
-			else { // Other recurrences
972
-				$rdata .= pack("V", 0);
973
-			}
974
-
975
-			// Exception data
976
-
977
-			// Get all exceptions
978
-			$deleted_items = $this->recur["deleted_occurrences"];
979
-			$changed_items = $this->recur["changed_occurrences"];
980
-
981
-			// Merge deleted and changed items into one list
982
-			$items = $deleted_items;
983
-
984
-			foreach ($changed_items as $changed_item) {
985
-				array_push($items, $changed_item["basedate"]);
986
-			}
987
-
988
-			sort($items);
989
-
990
-			// Add the merged list in to the rdata
991
-			$rdata .= pack("V", count($items));
992
-			foreach ($items as $item) {
993
-				$rdata .= pack("V", $this->unixDataToRecurData($item));
994
-			}
995
-
996
-			// Loop through the changed exceptions (not deleted)
997
-			$rdata .= pack("V", count($changed_items));
998
-			$items = [];
999
-
1000
-			foreach ($changed_items as $changed_item) {
1001
-				$items[] = $this->dayStartOf($changed_item["start"]);
1002
-			}
1003
-
1004
-			sort($items);
1005
-
1006
-			// Add the changed items list int the rdata
1007
-			foreach ($items as $item) {
1008
-				$rdata .= pack("V", $this->unixDataToRecurData($item));
1009
-			}
1010
-
1011
-			// Set start date
1012
-			$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
1013
-
1014
-			// Set enddate
1015
-			switch ($term) {
1016
-				// After the given enddate
1017
-				case 0x21:
1018
-					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1019
-
1020
-					break;
1021
-				// After a number of times
1022
-				case 0x22:
1023
-					// @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
1024
-					$occenddate = (int) $this->recur["start"];
1025
-
1026
-					switch ((int) $this->recur["type"]) {
1027
-						case 0x0A: // daily
1028
-							if ($this->recur["subtype"] == 1) {
1029
-								// Daily every workday
1030
-								$restocc = (int) $this->recur["numoccur"];
1031
-
1032
-								// Get starting weekday
1033
-								$nowtime = $this->gmtime($occenddate);
1034
-								$j = $nowtime["tm_wday"];
1035
-
1036
-								while (1) {
1037
-									if (($j % 7) > 0 && ($j % 7) < 6) {
1038
-										--$restocc;
1039
-									}
1040
-
1041
-									++$j;
1042
-
1043
-									if ($restocc <= 0) {
1044
-										break;
1045
-									}
1046
-
1047
-									$occenddate += 24 * 60 * 60;
1048
-								}
1049
-							}
1050
-							else {
1051
-								// -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
1052
-								$occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
1053
-							}
1054
-
1055
-							break;
1056
-
1057
-						case 0x0B: // weekly
1058
-							// Needed values
1059
-							// $forwardcount - number of weeks we can skip forward
1060
-							// $restocc - number of remaining occurrences after the week skip
1061
-
1062
-							// Add the weeks till the last item
1063
-							$occenddate += ($forwardcount * 7 * 24 * 60 * 60);
1064
-
1065
-							$dayofweek = gmdate("w", $occenddate);
1066
-
1067
-							// Loop through the last occurrences until we have had them all
1068
-							for ($j = 1; $restocc > 0; ++$j) {
1069
-								// Jump to the next week (which may be N weeks away) when going over the week boundary
1070
-								if ((($dayofweek + $j) % 7) == $weekstart) {
1071
-									$occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1072
-								}
1073
-
1074
-								// If this is a matching day, once less occurrence to process
1075
-								if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1076
-									--$restocc;
1077
-								}
1078
-
1079
-								// Next day
1080
-								$occenddate += 24 * 60 * 60;
1081
-							}
1082
-
1083
-							break;
1084
-
1085
-						case 0x0C: // monthly
1086
-						case 0x0D: // yearly
1087
-							$curyear = gmdate("Y", (int) $this->recur["start"]);
1088
-							$curmonth = gmdate("n", (int) $this->recur["start"]);
1089
-							// $forwardcount = months
1090
-
1091
-							switch ((int) $this->recur["subtype"]) {
1092
-								case 2: // on D day of every M month
1093
-									while ($forwardcount > 0) {
1094
-										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1095
-
1096
-										if ($curmonth >= 12) {
1097
-											$curmonth = 1;
1098
-											++$curyear;
1099
-										}
1100
-										else {
1101
-											++$curmonth;
1102
-										}
1103
-										--$forwardcount;
1104
-									}
1105
-
1106
-									// compensation between 28 and 31
1107
-									if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
1108
-										gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1109
-										if (gmdate("j", $occenddate) < 28) {
1110
-											$occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1111
-										}
1112
-										else {
1113
-											$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1114
-										}
1115
-									}
1116
-
1117
-									break;
1118
-
1119
-								case 3: // on Nth weekday of every M month
1120
-									$nday = (int) $this->recur["nday"]; // 1 tot 5
1121
-									$weekdays = (int) $this->recur["weekdays"];
1122
-
1123
-									while ($forwardcount > 0) {
1124
-										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1125
-										if ($curmonth >= 12) {
1126
-											$curmonth = 1;
1127
-											++$curyear;
1128
-										}
1129
-										else {
1130
-											++$curmonth;
1131
-										}
1132
-
1133
-										--$forwardcount;
1134
-									}
1135
-
1136
-									if ($nday == 5) {
1137
-										// Set date on the last day of the last month
1138
-										$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1139
-									}
1140
-									else {
1141
-										// Set date on the first day of the last month
1142
-										$occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1143
-									}
1144
-
1145
-									$dayofweek = gmdate("w", $occenddate);
1146
-									for ($i = 0; $i < 7; ++$i) {
1147
-										if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
1148
-											$occenddate -= $i * 24 * 60 * 60;
1149
-
1150
-											break;
1151
-										}
1152
-										if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
1153
-											$occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60;
1154
-
1155
-											break;
1156
-										}
1157
-									}
1158
-
1159
-								break; // case 3:
1160
-								}
1161
-
1162
-							break;
1163
-					}
1164
-
1165
-					if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) {
1166
-						$occenddate = PHP_INT_MAX;
1167
-					}
1168
-
1169
-					$this->recur["end"] = $occenddate;
1170
-
1171
-					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1172
-
1173
-					break;
1174
-				// Never ends
1175
-				case 0x23:
1176
-				default:
1177
-					$this->recur["end"] = 0x7FFFFFFF; // max date -> 2038
1178
-					$rdata .= pack("V", 0x5AE980DF);
1179
-
1180
-					break;
1181
-			}
1182
-
1183
-			// UTC date
1184
-			$utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1185
-			$utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1186
-
1187
-			// utc date+time
1188
-			$utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart;
1189
-			$utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1190
-
1191
-			$propsToSet = [];
1192
-			// update reminder time
1193
-			$propsToSet[$this->proptags["reminder_time"]] = $utcfirstoccstartdatetime;
1194
-
1195
-			// update first occurrence date
1196
-			$propsToSet[$this->proptags["startdate"]] = $propsToSet[$this->proptags["commonstart"]] = $utcfirstoccstartdatetime;
1197
-			$propsToSet[$this->proptags["duedate"]] = $propsToSet[$this->proptags["commonend"]] = $utcfirstoccenddatetime;
1198
-
1199
-			// Set Outlook properties, if it is an appointment
1200
-			if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") {
1201
-				// update real begin and real end date
1202
-				$propsToSet[$this->proptags["startdate_recurring"]] = $utcstart;
1203
-				$propsToSet[$this->proptags["enddate_recurring"]] = $utcend;
1204
-
1205
-				// recurrencetype
1206
-				// Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1207
-				$propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9;
1208
-
1209
-				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1210
-				$propsToSet[$this->proptags["side_effects"]] = 369;
1211
-			}
1212
-			else {
1213
-				$propsToSet[$this->proptags["side_effects"]] = 3441;
1214
-			}
1215
-
1216
-			// FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
1217
-			// Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
1218
-			// to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to
1219
-			// the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
1220
-			// with the reminder flag set.
1221
-			$reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]);
1222
-			if (isset($reminderprops[$this->proptags["reminder_minutes"]])) {
1223
-				$occ = false;
1224
-				$occurrences = $this->getItems(time(), 0x7FF00000, 3, true);
1225
-
1226
-				for ($i = 0, $len = count($occurrences); $i < $len; ++$i) {
1227
-					// This will actually also give us appointments that have already started, but not yet ended. Since we want the next
1228
-					// reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
1229
-					// number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
1230
-					// time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
1231
-					// can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
1232
-
1233
-					if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
1234
-						$occ = $occurrences[$i];
1235
-
1236
-						break;
1237
-					}
1238
-				}
1239
-
1240
-				if ($occ) {
1241
-					$propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60);
1242
-				}
1243
-				else {
1244
-					// Last reminder passed, no reminders any more.
1245
-					$propsToSet[$this->proptags["reminder"]] = false;
1246
-					$propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000;
1247
-				}
1248
-			}
1249
-
1250
-			// Default data
1251
-			// Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
1252
-			$rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
1253
-
1254
-			if (isset($this->recur["startocc"], $this->recur["endocc"])) {
1255
-				// Set start and endtime in minutes
1256
-				$rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1257
-			}
1258
-
1259
-			// Detailed exception data
1260
-
1261
-			$changed_items = $this->recur["changed_occurrences"];
1262
-
1263
-			$rdata .= pack("v", count($changed_items));
1264
-
1265
-			foreach ($changed_items as $changed_item) {
1266
-				// Set start and end time of exception
1267
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1268
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1269
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1270
-
1271
-				// Bitmask
1272
-				$bitmask = 0;
1273
-
1274
-				// Check for changed strings
1275
-				if (isset($changed_item["subject"])) {
1276
-					$bitmask |= 1 << 0;
1277
-				}
1278
-
1279
-				if (isset($changed_item["remind_before"])) {
1280
-					$bitmask |= 1 << 2;
1281
-				}
1282
-
1283
-				if (isset($changed_item["reminder_set"])) {
1284
-					$bitmask |= 1 << 3;
1285
-				}
1286
-
1287
-				if (isset($changed_item["location"])) {
1288
-					$bitmask |= 1 << 4;
1289
-				}
1290
-
1291
-				if (isset($changed_item["busystatus"])) {
1292
-					$bitmask |= 1 << 5;
1293
-				}
1294
-
1295
-				if (isset($changed_item["alldayevent"])) {
1296
-					$bitmask |= 1 << 7;
1297
-				}
1298
-
1299
-				if (isset($changed_item["label"])) {
1300
-					$bitmask |= 1 << 8;
1301
-				}
1302
-
1303
-				$rdata .= pack("v", $bitmask);
1304
-
1305
-				// Set "subject"
1306
-				if (isset($changed_item["subject"])) {
1307
-					// convert utf-8 to non-unicode blob string (us-ascii?)
1308
-					$subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1309
-					$length = strlen($subject);
1310
-					$rdata .= pack("vv", $length + 1, $length);
1311
-					$rdata .= pack("a" . $length, $subject);
1312
-				}
1313
-
1314
-				if (isset($changed_item["remind_before"])) {
1315
-					$rdata .= pack("V", $changed_item["remind_before"]);
1316
-				}
1317
-
1318
-				if (isset($changed_item["reminder_set"])) {
1319
-					$rdata .= pack("V", $changed_item["reminder_set"]);
1320
-				}
1321
-
1322
-				if (isset($changed_item["location"])) {
1323
-					$location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1324
-					$length = strlen($location);
1325
-					$rdata .= pack("vv", $length + 1, $length);
1326
-					$rdata .= pack("a" . $length, $location);
1327
-				}
1328
-
1329
-				if (isset($changed_item["busystatus"])) {
1330
-					$rdata .= pack("V", $changed_item["busystatus"]);
1331
-				}
1332
-
1333
-				if (isset($changed_item["alldayevent"])) {
1334
-					$rdata .= pack("V", $changed_item["alldayevent"]);
1335
-				}
1336
-
1337
-				if (isset($changed_item["label"])) {
1338
-					$rdata .= pack("V", $changed_item["label"]);
1339
-				}
1340
-			}
1341
-
1342
-			$rdata .= pack("V", 0);
1343
-
1344
-			// write extended data
1345
-			foreach ($changed_items as $changed_item) {
1346
-				$rdata .= pack("V", 0);
1347
-				if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1348
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1349
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1350
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1351
-				}
1352
-
1353
-				if (isset($changed_item["subject"])) {
1354
-					$subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1355
-					$length = iconv_strlen($subject, "UCS-2LE");
1356
-					$rdata .= pack("v", $length);
1357
-					$rdata .= pack("a" . $length * 2, $subject);
1358
-				}
1359
-
1360
-				if (isset($changed_item["location"])) {
1361
-					$location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1362
-					$length = iconv_strlen($location, "UCS-2LE");
1363
-					$rdata .= pack("v", $length);
1364
-					$rdata .= pack("a" . $length * 2, $location);
1365
-				}
1366
-
1367
-				if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1368
-					$rdata .= pack("V", 0);
1369
-				}
1370
-			}
1371
-
1372
-			$rdata .= pack("V", 0);
1373
-
1374
-			// Set props
1375
-			$propsToSet[$this->proptags["recurring_data"]] = $rdata;
1376
-			$propsToSet[$this->proptags["recurring"]] = true;
1377
-
1378
-			if (isset($this->tz) && $this->tz) {
1379
-				$timezone = "GMT";
1380
-				if ($this->tz["timezone"] != 0) {
1381
-					// Create user readable timezone information
1382
-					$timezone = sprintf(
1383
-						"(GMT %s%02d:%02d)",
1384
-						(-$this->tz["timezone"] > 0 ? "+" : "-"),
1385
-						abs($this->tz["timezone"] / 60),
1386
-						abs($this->tz["timezone"] % 60)
1387
-					);
1388
-				}
1389
-				$propsToSet[$this->proptags["timezone_data"]] = $this->getTimezoneData($this->tz);
1390
-				$propsToSet[$this->proptags["timezone"]] = $timezone;
1391
-			}
1392
-			mapi_setprops($this->message, $propsToSet);
1393
-		}
1394
-
1395
-		/**
1396
-		 * Function which converts a recurrence date timestamp to an unix date timestamp.
1397
-		 *
1398
-		 * @author Steve Hardy
1399
-		 *
1400
-		 * @param int $rdate the date which will be converted
1401
-		 *
1402
-		 * @return int the converted date
1403
-		 */
1404
-		public function recurDataToUnixData($rdate) {
1405
-			return ($rdate - 194074560) * 60;
1406
-		}
1407
-
1408
-		/**
1409
-		 * Function which converts an unix date timestamp to recurrence date timestamp.
1410
-		 *
1411
-		 * @author Johnny Biemans
1412
-		 *
1413
-		 * @param Date $date the date which will be converted
1414
-		 *
1415
-		 * @return int the converted date in minutes
1416
-		 */
1417
-		public function unixDataToRecurData($date) {
1418
-			return ($date / 60) + 194074560;
1419
-		}
1420
-
1421
-		/**
1422
-		 * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1423
-		 *
1424
-		 * @author Steve Hardy
1425
-		 *
1426
-		 * @param mixed $ts
1427
-		 */
1428
-		public function GetTZOffset($ts) {
1429
-			$Offset = date("O", $ts);
1430
-
1431
-			$Parity = $Offset < 0 ? -1 : 1;
1432
-			$Offset = $Parity * $Offset;
1433
-			$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1434
-
1435
-			return $Parity * $Offset;
1436
-		}
1437
-
1438
-		/**
1439
-		 * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1440
-		 *
1441
-		 * @author Steve Hardy
1442
-		 *
1443
-		 * @param Date $time
1444
-		 *
1445
-		 * @return Date GMT Time
1446
-		 */
1447
-		public function gmtime($time) {
1448
-			$TZOffset = $this->GetTZOffset($time);
1449
-
1450
-			$t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1451
-
1452
-			return localtime($t_time, 1);
1453
-		}
1454
-
1455
-		public function isLeapYear($year) {
1456
-			return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0);
1457
-		}
1458
-
1459
-		public function getMonthInSeconds($year, $month) {
1460
-			if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1461
-				$day = 31;
1462
-			}
1463
-			elseif (in_array($month, [4, 6, 9, 11])) {
1464
-				$day = 30;
1465
-			}
1466
-			else {
1467
-				$day = 28;
1468
-				if ($this->isLeapYear($year) == 1) {
1469
-					++$day;
1470
-				}
1471
-			}
1472
-
1473
-			return $day * 24 * 60 * 60;
1474
-		}
1475
-
1476
-		/**
1477
-		 * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.
1478
-		 *
1479
-		 * @param int $year
1480
-		 * @param int $month
1481
-		 * @param int $week
1482
-		 * @param int $day
1483
-		 * @param int $hour
1484
-		 *
1485
-		 * @return returns the timestamp of the given date, timezone-independant
1486
-		 */
1487
-		public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) {
1488
-			// get first day of month
1489
-			$date = gmmktime(0, 0, 0, $month, 0, $year + 1900);
1490
-
1491
-			// get wday info
1492
-			$gmdate = $this->gmtime($date);
1493
-
1494
-			$date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
1495
-
1496
-			$date += $week * 7 * 24 * 60 * 60; // go to correct week nr
1497
-			$date += $day * 24 * 60 * 60;
1498
-			$date += $hour * 60 * 60;
1499
-
1500
-			$gmdate = $this->gmtime($date);
1501
-
1502
-			// if we are in the next month, then back up a week, because week '5' means
1503
-			// 'last week of month'
1504
-
1505
-			if ($month != $gmdate["tm_mon"] + 1) {
1506
-				$date -= 7 * 24 * 60 * 60;
1507
-			}
1508
-
1509
-			return $date;
1510
-		}
1511
-
1512
-		/**
1513
-		 * getTimezone gives the timezone offset (in minutes) of the given
1514
-		 * local date/time according to the given TZ info.
1515
-		 *
1516
-		 * @param mixed $tz
1517
-		 * @param mixed $date
1518
-		 */
1519
-		public function getTimezone($tz, $date) {
1520
-			// No timezone -> GMT (+0)
1521
-			if (!isset($tz["timezone"])) {
1522
-				return 0;
1523
-			}
1524
-
1525
-			$dst = false;
1526
-			$gmdate = $this->gmtime($date);
1527
-
1528
-			$dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
1529
-			$dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
1530
-
1531
-			if ($dststart <= $dstend) {
1532
-				// Northern hemisphere, eg DST is during Mar-Oct
1533
-				if ($date > $dststart && $date < $dstend) {
1534
-					$dst = true;
1535
-				}
1536
-			}
1537
-			else {
1538
-				// Southern hemisphere, eg DST is during Oct-Mar
1539
-				if ($date < $dstend || $date > $dststart) {
1540
-					$dst = true;
1541
-				}
1542
-			}
1543
-
1544
-			if ($dst) {
1545
-				return $tz["timezone"] + $tz["timezonedst"];
1546
-			}
1547
-
1548
-			return $tz["timezone"];
1549
-		}
1550
-
1551
-		/**
1552
-		 * getWeekNr() returns the week nr of the month (ie first week of february is 1).
1553
-		 *
1554
-		 * @param mixed $date
1555
-		 */
1556
-		public function getWeekNr($date) {
1557
-			$gmdate = gmtime($date);
1558
-			$gmdate["tm_mday"] = 0;
1559
-
1560
-			return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
1561
-		}
1562
-
1563
-		/**
1564
-		 * parseTimezone parses the timezone as specified in named property 0x8233
1565
-		 * in Outlook calendar messages. Returns the timezone in minutes negative
1566
-		 * offset (GMT +2:00 -> -120).
1567
-		 *
1568
-		 * @param mixed $data
1569
-		 */
1570
-		public function parseTimezone($data) {
1571
-			if (strlen($data) < 48) {
1572
-				return;
1573
-			}
1574
-
1575
-			return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
1576
-		}
1577
-
1578
-		public function getTimezoneData($tz) {
1579
-			return pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0, 0);
1580
-		}
1581
-
1582
-		/**
1583
-		 * createTimezone creates the timezone as specified in the named property 0x8233
1584
-		 * see also parseTimezone()
1585
-		 * $tz is an array with the timezone data.
1586
-		 *
1587
-		 * @param mixed $tz
1588
-		 */
1589
-		public function createTimezone($tz) {
1590
-			return pack(
1591
-				"lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
1592
-				$tz["timezone"],
1593
-				array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0,
1594
-				array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0,
1595
-				array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0,
1596
-				array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0,
1597
-				array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0,
1598
-				array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0,
1599
-				array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0
1600
-			);
1601
-		}
1602
-
1603
-		/**
1604
-		 * toGMT returns a timestamp in GMT time for the time and timezone given.
1605
-		 *
1606
-		 * @param mixed $tz
1607
-		 * @param mixed $date
1608
-		 */
1609
-		public function toGMT($tz, $date) {
1610
-			if (!isset($tz['timezone'])) {
1611
-				return $date;
1612
-			}
1613
-			$offset = $this->getTimezone($tz, $date);
1614
-
1615
-			return $date + $offset * 60;
1616
-		}
1617
-
1618
-		/**
1619
-		 * fromGMT returns a timestamp in the local timezone given from the GMT time given.
1620
-		 *
1621
-		 * @param mixed $tz
1622
-		 * @param mixed $date
1623
-		 */
1624
-		public function fromGMT($tz, $date) {
1625
-			$offset = $this->getTimezone($tz, $date);
1626
-
1627
-			return $date - $offset * 60;
1628
-		}
1629
-
1630
-		/**
1631
-		 * Function to get timestamp of the beginning of the day of the timestamp given.
1632
-		 *
1633
-		 * @param date $date
1634
-		 *
1635
-		 * @return date timestamp referring to same day but at 00:00:00
1636
-		 */
1637
-		public function dayStartOf($date) {
1638
-			$time1 = $this->gmtime($date);
1639
-
1640
-			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
1641
-		}
1642
-
1643
-		/**
1644
-		 * Function to get timestamp of the beginning of the month of the timestamp given.
1645
-		 *
1646
-		 * @param date $date
1647
-		 *
1648
-		 * @return date Timestamp referring to same month but on the first day, and at 00:00:00
1649
-		 */
1650
-		public function monthStartOf($date) {
1651
-			$time1 = $this->gmtime($date);
1652
-
1653
-			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
1654
-		}
1655
-
1656
-		/**
1657
-		 * Function to get timestamp of the beginning of the year of the timestamp given.
1658
-		 *
1659
-		 * @param date $date
1660
-		 *
1661
-		 * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00
1662
-		 */
1663
-		public function yearStartOf($date) {
1664
-			$time1 = $this->gmtime($date);
1665
-
1666
-			return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
1667
-		}
1668
-
1669
-		/**
1670
-		 * Function which returns the items in a given interval. This included expansion of the recurrence and
1671
-		 * processing of exceptions (modified and deleted).
1672
-		 *
1673
-		 * @param string $entryid       the entryid of the message
1674
-		 * @param array  $props         the properties of the message
1675
-		 * @param date   $start         start time of the interval (GMT)
1676
-		 * @param date   $end           end time of the interval (GMT)
1677
-		 * @param mixed  $limit
1678
-		 * @param mixed  $remindersonly
1679
-		 */
1680
-		public function getItems($start, $end, $limit = 0, $remindersonly = false) {
1681
-			$items = [];
1682
-
1683
-			if (isset($this->recur)) {
1684
-				// Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
1685
-				// exceptions are in range and have a reminder set
1686
-				if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
1687
-					// Sort exceptions by start time
1688
-					uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]);
1689
-
1690
-					// Loop through all changed exceptions
1691
-					foreach ($this->recur["changed_occurrences"] as $exception) {
1692
-						// Check reminder set
1693
-						if (!isset($exception["reminder"]) || $exception["reminder"] == false) {
1694
-							continue;
1695
-						}
1696
-
1697
-						// Convert to GMT
1698
-						$occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz
1699
-						$occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz
1700
-
1701
-						// Check range criterium
1702
-						if ($occstart > $end || $occend < $start) {
1703
-							continue;
1704
-						}
1705
-
1706
-						// OK, add to items.
1707
-						array_push($items, $this->getExceptionProperties($exception));
1708
-						if ($limit && (count($items) == $limit)) {
1709
-							break;
1710
-						}
1711
-					}
1712
-
1713
-					uasort($items, [$this, "sortStarttime"]);
1714
-
1715
-					return $items;
1716
-				}
1717
-
1718
-				// From here on, the dates of the occurrences are calculated in local time, so the days we're looking
1719
-				// at are calculated from the local time dates of $start and $end
1720
-				// TODO use one isset
1721
-				if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) {
1722
-					$daystart = $this->dayStartOf($this->action['datecompleted']);
1723
-				}
1724
-				else {
1725
-					$daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
1726
-				}
1727
-
1728
-				// Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
1729
-				// or the end of the recurrence, whichever comes first
1730
-				if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1731
-					$rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1732
-				}
1733
-				else {
1734
-					$rangeend = $end;
1735
-				}
1736
-
1737
-				$dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
1738
-
1739
-				// Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1740
-
1741
-				switch ($this->recur["type"]) {
1742
-				case 10:
1743
-					// Daily
1744
-					if ($this->recur["everyn"] <= 0) {
1745
-						$this->recur["everyn"] = 1440;
1746
-					}
1747
-
1748
-					if ($this->recur["subtype"] == 0) {
1749
-						// Every Nth day
1750
-						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1751
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1752
-						}
1753
-					}
1754
-					else {
1755
-						// Every workday
1756
-						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1757
-							$nowtime = $this->gmtime($now);
1758
-							if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1759
-								$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1760
-							}
1761
-						}
1762
-					}
1763
-
1764
-					break;
1765
-
1766
-				case 11:
1767
-					// Weekly
1768
-					if ($this->recur["everyn"] <= 0) {
1769
-						$this->recur["everyn"] = 1;
1770
-					}
1771
-
1772
-					// If sliding flag is set then move to 'n' weeks
1773
-					if ($this->recur['regen']) {
1774
-						$daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1775
-					}
1776
-
1777
-					for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1778
-						if ($this->recur['regen']) {
1779
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1780
-						}
1781
-						else {
1782
-							// Loop through the whole following week to the first occurrence of the week, add each day that is specified
1783
-							for ($wday = 0; $wday < 7; ++$wday) {
1784
-								$daynow = $now + $wday * 60 * 60 * 24;
1785
-								// checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1786
-								if ($daynow <= $dayend) {
1787
-									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1788
-									if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1789
-										$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1790
-									}
1791
-								}
1792
-							}
1793
-						}
1794
-					}
1795
-
1796
-					break;
1797
-
1798
-				case 12:
1799
-					// Monthly
1800
-					if ($this->recur["everyn"] <= 0) {
1801
-						$this->recur["everyn"] = 1;
1802
-					}
1803
-
1804
-					// Loop through all months from start to end of occurrence, starting at beginning of first month
1805
-					for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1806
-						if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1807
-							$difference = 1;
1808
-							if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1809
-								$difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1810
-							}
1811
-							$daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1812
-							// checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1813
-							if ($daynow <= $dayend) {
1814
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1815
-							}
1816
-						}
1817
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1818
-							// Sanitize input
1819
-							if ($this->recur["weekdays"] == 0) {
1820
-								$this->recur["weekdays"] = 1;
1821
-							}
1822
-
1823
-							// If nday is not set to the last day in the month
1824
-							if ($this->recur["nday"] < 5) {
1825
-								// keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1826
-								$ndaycounter = 0;
1827
-								// Find matching weekday in this month
1828
-								for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) {
1829
-									$daynow = $now + $day * 60 * 60 * 24;
1830
-									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1831
-
1832
-									if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1833
-										++$ndaycounter;
1834
-									}
1835
-									// check the selected pattern is same as asked Nth weekday,If so set the firstday
1836
-									if ($this->recur["nday"] == $ndaycounter) {
1837
-										$firstday = $day;
1838
-
1839
-										break;
1840
-									}
1841
-								}
1842
-								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1843
-								$daynow = $now + $firstday * 60 * 60 * 24;
1844
-							}
1845
-							else {
1846
-								// Find last day in the month ($now is the firstday of the month)
1847
-								$NumDaysInMonth = $this->daysInMonth($now, 1);
1848
-								$daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1849
-
1850
-								$nowtime = $this->gmtime($daynow);
1851
-								while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1852
-									$daynow -= 86400;
1853
-									$nowtime = $this->gmtime($daynow);
1854
-								}
1855
-							}
1856
-
1857
-							/*
8
+    /**
9
+     * BaseRecurrence
10
+     * this class is superclass for recurrence for appointments and tasks. This class provides all
11
+     * basic features of recurrence.
12
+     */
13
+    class BaseRecurrence {
14
+        /**
15
+         * @var object Mapi Message Store (may be null if readonly)
16
+         */
17
+        public $store;
18
+
19
+        /**
20
+         * @var object Mapi Message (may be null if readonly)
21
+         */
22
+        public $message;
23
+
24
+        /**
25
+         * @var array Message Properties
26
+         */
27
+        public $messageprops;
28
+
29
+        /**
30
+         * @var array list of property tags
31
+         */
32
+        public $proptags;
33
+
34
+        /**
35
+         * @var recurrence data of this calendar item
36
+         */
37
+        public $recur;
38
+
39
+        /**
40
+         * @var Timezone data of this calendar item
41
+         */
42
+        public $tz;
43
+
44
+        /**
45
+         * Constructor.
46
+         *
47
+         * @param resource $store      MAPI Message Store Object
48
+         * @param resource $message    the MAPI (appointment) message
49
+         * @param array    $properties the list of MAPI properties the message has
50
+         */
51
+        public function __construct($store, $message) {
52
+            $this->store = $store;
53
+
54
+            if (is_array($message)) {
55
+                $this->messageprops = $message;
56
+            }
57
+            else {
58
+                $this->message = $message;
59
+                $this->messageprops = mapi_getprops($this->message, $this->proptags);
60
+            }
61
+
62
+            if (isset($this->messageprops[$this->proptags["recurring_data"]])) {
63
+                // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
64
+                if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
65
+                    $this->getFullRecurrenceBlob();
66
+                }
67
+
68
+                $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
69
+            }
70
+            if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) {
71
+                $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
72
+            }
73
+        }
74
+
75
+        public function getRecurrence() {
76
+            return $this->recur;
77
+        }
78
+
79
+        public function getFullRecurrenceBlob() {
80
+            $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
81
+
82
+            $recurrBlob = '';
83
+            $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
84
+            $stat = mapi_stream_stat($stream);
85
+
86
+            for ($i = 0; $i < $stat['cb']; $i += 1024) {
87
+                $recurrBlob .= mapi_stream_read($stream, 1024);
88
+            }
89
+
90
+            if (!empty($recurrBlob)) {
91
+                $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
92
+            }
93
+        }
94
+
95
+        /**
96
+         * Function for parsing the Recurrence value of a Calendar item.
97
+         *
98
+         * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
99
+         * data to this function
100
+         *
101
+         * Returns a structure containing the data:
102
+         *
103
+         * type        - type of recurrence: day=10, week=11, month=12, year=13
104
+         * subtype    - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday)
105
+         * start    - unix timestamp of first occurrence
106
+         * end        - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
107
+         * numoccur     - occurrences (may be very large when there is no end data)
108
+         *
109
+         * then, for each type:
110
+         *
111
+         * Daily:
112
+         *  everyn    - every [everyn] days in minutes
113
+         *  regen    - regenerating event (like tasks)
114
+         *
115
+         * Weekly:
116
+         *  everyn    - every [everyn] weeks in weeks
117
+         *  regen    - regenerating event (like tasks)
118
+         *  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
119
+         *
120
+         * Monthly:
121
+         *  everyn    - every [everyn] months
122
+         *  regen    - regenerating event (like tasks)
123
+         *
124
+         *  subtype 2:
125
+         *      monthday - on day [monthday] of the month
126
+         *
127
+         *  subtype 3:
128
+         *      weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
129
+         *   nday    - on [nday]'th [weekdays] of the month
130
+         *
131
+         * Yearly:
132
+         *  everyn    - every [everyn] months (12, 24, 36, ...)
133
+         *  month    - in month [month] (although the month is encoded in minutes since the startning of the year ........)
134
+         *  regen    - regenerating event (like tasks)
135
+         *
136
+         *  subtype 2:
137
+         *   monthday - on day [monthday] of the month
138
+         *
139
+         *  subtype 3:
140
+         *   weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
141
+         *      nday    - on [nday]'th [weekdays] of the month [month]
142
+         *
143
+         * @param string $rdata Binary string
144
+         *
145
+         * @return array recurrence data
146
+         */
147
+        public function parseRecurrence($rdata) {
148
+            if (strlen($rdata) < 10) {
149
+                return;
150
+            }
151
+
152
+            $ret["changed_occurrences"] = [];
153
+            $ret["deleted_occurrences"] = [];
154
+
155
+            $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
156
+
157
+            $ret["type"] = $data["rtype"];
158
+            $ret["subtype"] = $data["rtype2"];
159
+            $rdata = substr($rdata, 10);
160
+
161
+            switch ($data["rtype"]) {
162
+                case 0x0A:
163
+                    // Daily
164
+                    if (strlen($rdata) < 12) {
165
+                        return $ret;
166
+                    }
167
+
168
+                    $data = unpack("Vunknown/Veveryn/Vregen", $rdata);
169
+                    $ret["everyn"] = $data["everyn"];
170
+                    $ret["regen"] = $data["regen"];
171
+
172
+                    switch ($ret["subtype"]) {
173
+                        case 0:
174
+                            $rdata = substr($rdata, 12);
175
+
176
+                            break;
177
+
178
+                        case 1:
179
+                            $rdata = substr($rdata, 16);
180
+
181
+                            break;
182
+                    }
183
+
184
+                    break;
185
+
186
+                case 0x0B:
187
+                    // Weekly
188
+                    if (strlen($rdata) < 16) {
189
+                        return $ret;
190
+                    }
191
+
192
+                    $data = unpack("Vconst1/Veveryn/Vregen", $rdata);
193
+                    $rdata = substr($rdata, 12);
194
+
195
+                    $ret["everyn"] = $data["everyn"];
196
+                    $ret["regen"] = $data["regen"];
197
+                    $ret["weekdays"] = 0;
198
+
199
+                    if ($data["regen"] == 0) {
200
+                        $data = unpack("Vweekdays", $rdata);
201
+                        $rdata = substr($rdata, 4);
202
+
203
+                        $ret["weekdays"] = $data["weekdays"];
204
+                    }
205
+
206
+                    break;
207
+
208
+                case 0x0C:
209
+                    // Monthly
210
+                    if (strlen($rdata) < 16) {
211
+                        return $ret;
212
+                    }
213
+
214
+                    $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
215
+
216
+                    $ret["everyn"] = $data["everyn"];
217
+                    $ret["regen"] = $data["regen"];
218
+
219
+                    if ($ret["subtype"] == 3) {
220
+                        $ret["weekdays"] = $data["monthday"];
221
+                    }
222
+                    else {
223
+                        $ret["monthday"] = $data["monthday"];
224
+                    }
225
+
226
+                    $rdata = substr($rdata, 16);
227
+
228
+                    if ($ret["subtype"] == 3) {
229
+                        $data = unpack("Vnday", $rdata);
230
+                        $ret["nday"] = $data["nday"];
231
+                        $rdata = substr($rdata, 4);
232
+                    }
233
+
234
+                    break;
235
+
236
+                case 0x0D:
237
+                    // Yearly
238
+                    if (strlen($rdata) < 16) {
239
+                        return $ret;
240
+                    }
241
+
242
+                    $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
243
+
244
+                    $ret["month"] = $data["month"];
245
+                    $ret["everyn"] = $data["everyn"];
246
+                    $ret["regen"] = $data["regen"];
247
+
248
+                    if ($ret["subtype"] == 3) {
249
+                        $ret["weekdays"] = $data["monthday"];
250
+                    }
251
+                    else {
252
+                        $ret["monthday"] = $data["monthday"];
253
+                    }
254
+
255
+                    $rdata = substr($rdata, 16);
256
+
257
+                    if ($ret["subtype"] == 3) {
258
+                        $data = unpack("Vnday", $rdata);
259
+                        $ret["nday"] = $data["nday"];
260
+                        $rdata = substr($rdata, 4);
261
+                    }
262
+
263
+                    break;
264
+            }
265
+
266
+            if (strlen($rdata) < 16) {
267
+                return $ret;
268
+            }
269
+
270
+            $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
271
+
272
+            $rdata = substr($rdata, 16);
273
+
274
+            $ret["term"] = $data["term"];
275
+            $ret["numoccur"] = $data["numoccur"];
276
+            $ret["numexcept"] = $data["numexcept"];
277
+
278
+            // exc_base_dates are *all* the base dates that have been either deleted or modified
279
+            $exc_base_dates = [];
280
+            for ($i = 0; $i < $ret["numexcept"]; ++$i) {
281
+                if (strlen($rdata) < 4) {
282
+                    // We shouldn't arrive here, because that implies
283
+                    // numexcept does not match the amount of data
284
+                    // which is available for the exceptions.
285
+                    return $ret;
286
+                }
287
+                $data = unpack("Vbasedate", $rdata);
288
+                $rdata = substr($rdata, 4);
289
+                $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
290
+            }
291
+
292
+            if (strlen($rdata) < 4) {
293
+                return $ret;
294
+            }
295
+
296
+            $data = unpack("Vnumexceptmod", $rdata);
297
+            $rdata = substr($rdata, 4);
298
+
299
+            $ret["numexceptmod"] = $data["numexceptmod"];
300
+
301
+            // exc_changed are the base dates of *modified* occurrences. exactly what is modified
302
+            // is in the attachments *and* in the data further down this function.
303
+            $exc_changed = [];
304
+            for ($i = 0; $i < $ret["numexceptmod"]; ++$i) {
305
+                if (strlen($rdata) < 4) {
306
+                    // We shouldn't arrive here, because that implies
307
+                    // numexceptmod does not match the amount of data
308
+                    // which is available for the exceptions.
309
+                    return $ret;
310
+                }
311
+                $data = unpack("Vstartdate", $rdata);
312
+                $rdata = substr($rdata, 4);
313
+                $exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
314
+            }
315
+
316
+            if (strlen($rdata) < 8) {
317
+                return $ret;
318
+            }
319
+
320
+            $data = unpack("Vstart/Vend", $rdata);
321
+            $rdata = substr($rdata, 8);
322
+
323
+            $ret["start"] = $this->recurDataToUnixData($data["start"]);
324
+            $ret["end"] = $this->recurDataToUnixData($data["end"]);
325
+
326
+            // this is where task recurrence stop
327
+            if (strlen($rdata) < 16) {
328
+                return $ret;
329
+            }
330
+
331
+            $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
332
+            $rdata = substr($rdata, 16);
333
+
334
+            $ret["startocc"] = $data["startmin"];
335
+            $ret["endocc"] = $data["endmin"];
336
+            $writerversion = $data["writerversion"];
337
+
338
+            $data = unpack("vnumber", $rdata);
339
+            $rdata = substr($rdata, 2);
340
+
341
+            $nexceptions = $data["number"];
342
+            $exc_changed_details = [];
343
+
344
+            // Parse n modified exceptions
345
+            for ($i = 0; $i < $nexceptions; ++$i) {
346
+                $item = [];
347
+
348
+                // Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
349
+                $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
350
+                $rdata = substr($rdata, 12);
351
+
352
+                // Convert recurtimestamp to unix timestamp
353
+                $startdate = $this->recurDataToUnixData($data["startdate"]);
354
+                $enddate = $this->recurDataToUnixData($data["enddate"]);
355
+                $basedate = $this->recurDataToUnixData($data["basedate"]);
356
+
357
+                // Set the right properties
358
+                $item["basedate"] = $this->dayStartOf($basedate);
359
+                $item["start"] = $startdate;
360
+                $item["end"] = $enddate;
361
+
362
+                $data = unpack("vbitmask", $rdata);
363
+                $rdata = substr($rdata, 2);
364
+                $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
365
+
366
+                // Bitmask to verify what properties are changed
367
+                $bitmask = $data["bitmask"];
368
+
369
+                // ARO_SUBJECT: 0x0001
370
+                // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
371
+                if (($bitmask & (1 << 0))) {
372
+                    $data = unpack("vnull_length/vlength", $rdata);
373
+                    $rdata = substr($rdata, 4);
374
+
375
+                    $length = $data["length"];
376
+                    $item["subject"] = ""; // Normalized subject
377
+                    for ($j = 0; $j < $length && strlen($rdata); ++$j) {
378
+                        $data = unpack("Cchar", $rdata);
379
+                        $rdata = substr($rdata, 1);
380
+
381
+                        $item["subject"] .= chr($data["char"]);
382
+                    }
383
+                }
384
+
385
+                // ARO_MEETINGTYPE: 0x0002
386
+                if (($bitmask & (1 << 1))) {
387
+                    $rdata = substr($rdata, 4);
388
+                    // Attendees modified: no data here (only in attachment)
389
+                }
390
+
391
+                // ARO_REMINDERDELTA: 0x0004
392
+                // Look for field: ReminderDelta (4b)
393
+                if (($bitmask & (1 << 2))) {
394
+                    $data = unpack("Vremind_before", $rdata);
395
+                    $rdata = substr($rdata, 4);
396
+
397
+                    $item["remind_before"] = $data["remind_before"];
398
+                }
399
+
400
+                // ARO_REMINDER: 0x0008
401
+                // Look field: ReminderSet (4b)
402
+                if (($bitmask & (1 << 3))) {
403
+                    $data = unpack("Vreminder_set", $rdata);
404
+                    $rdata = substr($rdata, 4);
405
+
406
+                    $item["reminder_set"] = $data["reminder_set"];
407
+                }
408
+
409
+                // ARO_LOCATION: 0x0010
410
+                // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
411
+                // Similar to ARO_SUBJECT above.
412
+                if (($bitmask & (1 << 4))) {
413
+                    $data = unpack("vnull_length/vlength", $rdata);
414
+                    $rdata = substr($rdata, 4);
415
+
416
+                    $item["location"] = "";
417
+
418
+                    $length = $data["length"];
419
+                    $data = substr($rdata, 0, $length);
420
+                    $rdata = substr($rdata, $length);
421
+
422
+                    $item["location"] .= $data;
423
+                }
424
+
425
+                // ARO_BUSYSTATUS: 0x0020
426
+                // Look for field: BusyStatus (4b)
427
+                if (($bitmask & (1 << 5))) {
428
+                    $data = unpack("Vbusystatus", $rdata);
429
+                    $rdata = substr($rdata, 4);
430
+
431
+                    $item["busystatus"] = $data["busystatus"];
432
+                }
433
+
434
+                // ARO_ATTACHMENT: 0x0040
435
+                if (($bitmask & (1 << 6))) {
436
+                    // no data: RESERVED
437
+                    $rdata = substr($rdata, 4);
438
+                }
439
+
440
+                // ARO_SUBTYPE: 0x0080
441
+                // Look for field: SubType (4b). Determines whether it is an allday event.
442
+                if (($bitmask & (1 << 7))) {
443
+                    $data = unpack("Vallday", $rdata);
444
+                    $rdata = substr($rdata, 4);
445
+
446
+                    $item["alldayevent"] = $data["allday"];
447
+                }
448
+
449
+                // ARO_APPTCOLOR: 0x0100
450
+                // Look for field: AppointmentColor (4b)
451
+                if (($bitmask & (1 << 8))) {
452
+                    $data = unpack("Vlabel", $rdata);
453
+                    $rdata = substr($rdata, 4);
454
+
455
+                    $item["label"] = $data["label"];
456
+                }
457
+
458
+                // ARO_EXCEPTIONAL_BODY: 0x0200
459
+                if (($bitmask & (1 << 9))) {
460
+                    // Notes or Attachments modified: no data here (only in attachment)
461
+                }
462
+
463
+                array_push($exc_changed_details, $item);
464
+            }
465
+
466
+            /**
467
+             * We now have $exc_changed, $exc_base_dates and $exc_changed_details
468
+             * We will ignore $exc_changed, as this information is available in $exc_changed_details
469
+             * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
470
+             * has been deleted.
471
+             */
472
+
473
+            // Find deleted occurrences
474
+            $deleted_occurrences = [];
475
+
476
+            foreach ($exc_base_dates as $base_date) {
477
+                $found = false;
478
+
479
+                foreach ($exc_changed_details as $details) {
480
+                    if ($details["basedate"] == $base_date) {
481
+                        $found = true;
482
+
483
+                        break;
484
+                    }
485
+                }
486
+                if (!$found) {
487
+                    // item was not in exc_changed_details, so it must be deleted
488
+                    $deleted_occurrences[] = $base_date;
489
+                }
490
+            }
491
+
492
+            $ret["deleted_occurrences"] = $deleted_occurrences;
493
+            $ret["changed_occurrences"] = $exc_changed_details;
494
+
495
+            // enough data for normal exception (no extended data)
496
+            if (strlen($rdata) < 16) {
497
+                return $ret;
498
+            }
499
+
500
+            $data = unpack("Vreservedsize", $rdata);
501
+            $rdata = substr($rdata, 4 + $data["reservedsize"]);
502
+
503
+            for ($i = 0; $i < $nexceptions; ++$i) {
504
+                // subject and location in ucs-2 to utf-8
505
+                if ($writerversion >= 0x3009) {
506
+                    $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
507
+                    $rdata = substr($rdata, 4 + $data["size"]);
508
+                }
509
+
510
+                $data = unpack("Vreservedsize", $rdata);
511
+                $rdata = substr($rdata, 4 + $data["reservedsize"]);
512
+
513
+                // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
514
+                if ($exc_changed_details[$i]["bitmask"] & 0x11) {
515
+                    $data = unpack("Vstart/Vend/Vorig", $rdata);
516
+                    $rdata = substr($rdata, 4 * 3);
517
+
518
+                    $exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
519
+                    $exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
520
+                    $exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
521
+                }
522
+
523
+                // ARO_SUBJECT
524
+                if ($exc_changed_details[$i]["bitmask"] & 0x01) {
525
+                    // decode ucs2 string to utf-8
526
+                    $data = unpack("vlength", $rdata);
527
+                    $rdata = substr($rdata, 2);
528
+                    $length = $data["length"];
529
+                    $data = substr($rdata, 0, $length * 2);
530
+                    $rdata = substr($rdata, $length * 2);
531
+                    $subject = iconv("UCS-2LE", "UTF-8", $data);
532
+                    // replace subject with unicode subject
533
+                    $exc_changed_details[$i]["subject"] = $subject;
534
+                }
535
+
536
+                // ARO_LOCATION
537
+                if ($exc_changed_details[$i]["bitmask"] & 0x10) {
538
+                    // decode ucs2 string to utf-8
539
+                    $data = unpack("vlength", $rdata);
540
+                    $rdata = substr($rdata, 2);
541
+                    $length = $data["length"];
542
+                    $data = substr($rdata, 0, $length * 2);
543
+                    $rdata = substr($rdata, $length * 2);
544
+                    $location = iconv("UCS-2LE", "UTF-8", $data);
545
+                    // replace subject with unicode subject
546
+                    $exc_changed_details[$i]["location"] = $location;
547
+                }
548
+
549
+                // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
550
+                if ($exc_changed_details[$i]["bitmask"] & 0x11) {
551
+                    $data = unpack("Vreservedsize", $rdata);
552
+                    $rdata = substr($rdata, 4 + $data["reservedsize"]);
553
+                }
554
+            }
555
+
556
+            // update with extended data
557
+            $ret["changed_occurrences"] = $exc_changed_details;
558
+
559
+            return $ret;
560
+        }
561
+
562
+        /**
563
+         * Saves the recurrence data to the recurrence property.
564
+         */
565
+        public function saveRecurrence() {
566
+            // Only save if a message was passed
567
+            if (!isset($this->message)) {
568
+                return;
569
+            }
570
+
571
+            // Abort if no recurrence was set
572
+            if (!isset($this->recur["type"], $this->recur["subtype"], $this->recur["start"], $this->recur["end"], $this->recur["startocc"], $this->recur["endocc"])) {
573
+                return;
574
+            }
575
+
576
+            $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
577
+
578
+            $weekstart = 1; // monday
579
+            $forwardcount = 0;
580
+            $restocc = 0;
581
+            $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
582
+
583
+            $term = (int) $this->recur["type"];
584
+
585
+            switch ($term) {
586
+                case 0x0A:
587
+                    // Daily
588
+                    if (!isset($this->recur["everyn"])) {
589
+                        return;
590
+                    }
591
+
592
+                    if ($this->recur["subtype"] == 1) {
593
+                        // Daily every workday
594
+                        $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
595
+                    }
596
+                    else {
597
+                        // Daily every N days (everyN in minutes)
598
+
599
+                        $everyn = ((int) $this->recur["everyn"]) / 1440;
600
+
601
+                        // Calc first occ
602
+                        $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
603
+
604
+                        $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
605
+                    }
606
+
607
+                    break;
608
+
609
+                case 0x0B:
610
+                    // Weekly
611
+                    if (!isset($this->recur["everyn"])) {
612
+                        return;
613
+                    }
614
+
615
+                    if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
616
+                        return;
617
+                    }
618
+
619
+                    // No need to calculate startdate if sliding flag was set.
620
+                    if (!$this->recur['regen']) {
621
+                        // Calculate start date of recurrence
622
+
623
+                        // Find the first day that matches one of the weekdays selected
624
+                        $daycount = 0;
625
+                        $dayskip = -1;
626
+                        for ($j = 0; $j < 7; ++$j) {
627
+                            if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
628
+                                if ($dayskip == -1) {
629
+                                    $dayskip = $j;
630
+                                }
631
+
632
+                                ++$daycount;
633
+                            }
634
+                        }
635
+
636
+                        // $dayskip is the number of days to skip from the startdate until the first occurrence
637
+                        // $daycount is the number of days per week that an occurrence occurs
638
+
639
+                        $weekskip = 0;
640
+                        if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) {
641
+                            $weekskip = 1;
642
+                        }
643
+
644
+                        // Check if the recurrence ends after a number of occurrences, in that case we must calculate the
645
+                        // remaining occurrences based on the start of the recurrence.
646
+                        if (((int) $this->recur["term"]) == 0x22) {
647
+                            // $weekskip is the amount of weeks to skip from the startdate before the first occurrence
648
+                            // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
649
+                            // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
650
+                            // (eg when numoccur = 2, and daycount = 1)
651
+                            $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount);
652
+
653
+                            // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
654
+                            // for the occurrence on the first day
655
+                            $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
656
+
657
+                            // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
658
+                            $forwardcount *= (int) $this->recur["everyn"];
659
+                        }
660
+
661
+                        // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
662
+                        $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
663
+                    }
664
+
665
+                    // Calc first occ
666
+                    $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60);
667
+
668
+                    $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
669
+
670
+                    if ($this->recur["regen"]) {
671
+                        $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
672
+                    }
673
+                    else {
674
+                        $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
675
+                    }
676
+
677
+                    break;
678
+
679
+                case 0x0C:
680
+                    // Monthly
681
+                case 0x0D:
682
+                    // Yearly
683
+                    if (!isset($this->recur["everyn"])) {
684
+                        return;
685
+                    }
686
+                    if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) {
687
+                        return;
688
+                    }
689
+
690
+                    if ($term == 0x0C /* monthly */) {
691
+                        $everyn = (int) $this->recur["everyn"];
692
+                    }
693
+                    else {
694
+                        $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
695
+                    }
696
+
697
+                    // Get montday/month/year of original start
698
+                    $curmonthday = gmdate("j", (int) $this->recur["start"]);
699
+                    $curyear = gmdate("Y", (int) $this->recur["start"]);
700
+                    $curmonth = gmdate("n", (int) $this->recur["start"]);
701
+
702
+                    // Check if the recurrence ends after a number of occurrences, in that case we must calculate the
703
+                    // remaining occurrences based on the start of the recurrence.
704
+                    if (((int) $this->recur["term"]) == 0x22) {
705
+                        // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
706
+                        // one to make sure there are always at least one occurrence left)
707
+                        $forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn);
708
+                    }
709
+
710
+                    // Get month for yearly on D'th day of month M
711
+                    if ($term == 0x0D /* yearly */) {
712
+                        $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg
713
+                    }
714
+
715
+                    switch ((int) $this->recur["subtype"]) {
716
+                        // on D day of every M month
717
+                        case 2:
718
+                            if (!isset($this->recur["monthday"])) {
719
+                                return;
720
+                            }
721
+                            // Recalc startdate
722
+
723
+                            // Set on the right begin day
724
+
725
+                            // Go the beginning of the month
726
+                            $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60;
727
+                            // Go the the correct month day
728
+                            $this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60;
729
+
730
+                            // If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence
731
+                            if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) ||
732
+                                ($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) {
733
+                                if ($term == 0x0D /* yearly */) {
734
+                                    if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date
735
+                                        $count = $everyn - ($curmonth - $selmonth);
736
+                                    }
737
+                                    elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date
738
+                                        $count = $selmonth - $curmonth;
739
+                                    }
740
+                                    else {
741
+                                        // Go to next occurrence while recurrence start date is greater than occurrence date but within same month
742
+                                        if (((int) $this->recur["monthday"]) < $curmonthday) {
743
+                                            $count = $everyn;
744
+                                        }
745
+                                    }
746
+                                }
747
+                                else {
748
+                                    $count = $everyn; // Monthly, go to next occurrence in 'everyn' months
749
+                                }
750
+
751
+                                // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
752
+                                for ($i = 0; $i < $count; ++$i) {
753
+                                    $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
754
+
755
+                                    if ($curmonth == 12) {
756
+                                        ++$curyear;
757
+                                        $curmonth = 0;
758
+                                    }
759
+                                    ++$curmonth;
760
+                                }
761
+                            }
762
+
763
+                            // "start" is now pointing to the first occurrence, except that it will overshoot if the
764
+                            // month in which it occurs has less days than specified as the day of the month. So 31st
765
+                            // of each month will overshoot in february (29 days). We compensate for that by checking
766
+                            // if the day of the month we got is wrong, and then back up to the last day of the previous
767
+                            // month.
768
+                            if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
769
+                                gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) {
770
+                                $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60;
771
+                            }
772
+
773
+                            // "start" is now the first occurrence
774
+
775
+                            if ($term == 0x0C /* monthly */) {
776
+                                // Calc first occ
777
+                                $monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
778
+
779
+                                $firstocc = 0;
780
+                                for ($i = 0; $i < $monthIndex; ++$i) {
781
+                                    $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
782
+                                }
783
+
784
+                                $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
785
+                            }
786
+                            else {
787
+                                // Calc first occ
788
+                                $firstocc = 0;
789
+                                $monthIndex = (int) gmdate("n", $this->recur["start"]);
790
+                                for ($i = 1; $i < $monthIndex; ++$i) {
791
+                                    $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
792
+                                }
793
+
794
+                                $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
795
+                            }
796
+
797
+                            break;
798
+
799
+                        case 3:
800
+                            // monthly: on Nth weekday of every M month
801
+                            // yearly: on Nth weekday of M month
802
+                            if (!isset($this->recur["weekdays"], $this->recur["nday"])) {
803
+                                return;
804
+                            }
805
+
806
+                            $weekdays = (int) $this->recur["weekdays"];
807
+                            $nday = (int) $this->recur["nday"];
808
+
809
+                            // Calc startdate
810
+                            $monthbegindow = (int) $this->recur["start"];
811
+
812
+                            if ($nday == 5) {
813
+                                // Set date on the last day of the last month
814
+                                $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
815
+                            }
816
+                            else {
817
+                                // Set on the first day of the month
818
+                                $monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
819
+                            }
820
+
821
+                            if ($term == 0x0D /* yearly */) {
822
+                                // Set on right month
823
+                                if ($selmonth < $curmonth) {
824
+                                    $tmp = 12 - $curmonth + $selmonth;
825
+                                }
826
+                                else {
827
+                                    $tmp = ($selmonth - $curmonth);
828
+                                }
829
+
830
+                                for ($i = 0; $i < $tmp; ++$i) {
831
+                                    $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
832
+
833
+                                    if ($curmonth == 12) {
834
+                                        ++$curyear;
835
+                                        $curmonth = 0;
836
+                                    }
837
+                                    ++$curmonth;
838
+                                }
839
+                            }
840
+                            else {
841
+                                // Check or you exist in the right month
842
+
843
+                                $dayofweek = gmdate("w", $monthbegindow);
844
+                                for ($i = 0; $i < 7; ++$i) {
845
+                                    if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
846
+                                        $day = gmdate("j", $monthbegindow) - $i;
847
+
848
+                                        break;
849
+                                    }
850
+                                    if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
851
+                                        $day = (($nday - 1) * 7) + ($i + 1);
852
+
853
+                                        break;
854
+                                    }
855
+                                }
856
+
857
+                                // Goto the next X month
858
+                                if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) {
859
+                                    if ($nday == 5) {
860
+                                        $monthbegindow += 24 * 60 * 60;
861
+                                        if ($curmonth == 12) {
862
+                                            ++$curyear;
863
+                                            $curmonth = 0;
864
+                                        }
865
+                                        ++$curmonth;
866
+                                    }
867
+
868
+                                    for ($i = 0; $i < $everyn; ++$i) {
869
+                                        $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
870
+
871
+                                        if ($curmonth == 12) {
872
+                                            ++$curyear;
873
+                                            $curmonth = 0;
874
+                                        }
875
+                                        ++$curmonth;
876
+                                    }
877
+
878
+                                    if ($nday == 5) {
879
+                                        $monthbegindow -= 24 * 60 * 60;
880
+                                    }
881
+                                }
882
+                            }
883
+
884
+                            // FIXME: weekstart?
885
+
886
+                            $day = 0;
887
+                            // Set start on the right day
888
+                            $dayofweek = gmdate("w", $monthbegindow);
889
+                            for ($i = 0; $i < 7; ++$i) {
890
+                                if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
891
+                                    $day = $i;
892
+
893
+                                    break;
894
+                                }
895
+                                if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
896
+                                    $day = ($nday - 1) * 7 + ($i + 1);
897
+
898
+                                    break;
899
+                                }
900
+                            }
901
+                            if ($nday == 5) {
902
+                                $monthbegindow -= $day * 24 * 60 * 60;
903
+                            }
904
+                            else {
905
+                                $monthbegindow += ($day - 1) * 24 * 60 * 60;
906
+                            }
907
+
908
+                            $firstocc = 0;
909
+
910
+                            if ($term == 0x0C /* monthly */) {
911
+                                // Calc first occ
912
+                                $monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
913
+
914
+                                for ($i = 0; $i < $monthIndex; ++$i) {
915
+                                    $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
916
+                                }
917
+
918
+                                $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
919
+                            }
920
+                            else {
921
+                                // Calc first occ
922
+                                $monthIndex = (int) gmdate("n", $this->recur["start"]);
923
+
924
+                                for ($i = 1; $i < $monthIndex; ++$i) {
925
+                                    $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
926
+                                }
927
+
928
+                                $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
929
+                            }
930
+
931
+                            break;
932
+                    }
933
+
934
+                    break;
935
+            }
936
+
937
+            if (!isset($this->recur["term"])) {
938
+                return;
939
+            }
940
+
941
+            // Terminate
942
+            $term = (int) $this->recur["term"];
943
+            $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
944
+
945
+            switch ($term) {
946
+                // After the given enddate
947
+                case 0x21:
948
+                    $rdata .= pack("V", 10);
949
+
950
+                    break;
951
+                // After a number of times
952
+                case 0x22:
953
+                    if (!isset($this->recur["numoccur"])) {
954
+                        return;
955
+                    }
956
+
957
+                    $rdata .= pack("V", (int) $this->recur["numoccur"]);
958
+
959
+                    break;
960
+                // Never ends
961
+                case 0x23:
962
+                    $rdata .= pack("V", 0);
963
+
964
+                    break;
965
+            }
966
+
967
+            // Strange little thing for the recurrence type "every workday"
968
+            if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
969
+                $rdata .= pack("V", 1);
970
+            }
971
+            else { // Other recurrences
972
+                $rdata .= pack("V", 0);
973
+            }
974
+
975
+            // Exception data
976
+
977
+            // Get all exceptions
978
+            $deleted_items = $this->recur["deleted_occurrences"];
979
+            $changed_items = $this->recur["changed_occurrences"];
980
+
981
+            // Merge deleted and changed items into one list
982
+            $items = $deleted_items;
983
+
984
+            foreach ($changed_items as $changed_item) {
985
+                array_push($items, $changed_item["basedate"]);
986
+            }
987
+
988
+            sort($items);
989
+
990
+            // Add the merged list in to the rdata
991
+            $rdata .= pack("V", count($items));
992
+            foreach ($items as $item) {
993
+                $rdata .= pack("V", $this->unixDataToRecurData($item));
994
+            }
995
+
996
+            // Loop through the changed exceptions (not deleted)
997
+            $rdata .= pack("V", count($changed_items));
998
+            $items = [];
999
+
1000
+            foreach ($changed_items as $changed_item) {
1001
+                $items[] = $this->dayStartOf($changed_item["start"]);
1002
+            }
1003
+
1004
+            sort($items);
1005
+
1006
+            // Add the changed items list int the rdata
1007
+            foreach ($items as $item) {
1008
+                $rdata .= pack("V", $this->unixDataToRecurData($item));
1009
+            }
1010
+
1011
+            // Set start date
1012
+            $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
1013
+
1014
+            // Set enddate
1015
+            switch ($term) {
1016
+                // After the given enddate
1017
+                case 0x21:
1018
+                    $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1019
+
1020
+                    break;
1021
+                // After a number of times
1022
+                case 0x22:
1023
+                    // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
1024
+                    $occenddate = (int) $this->recur["start"];
1025
+
1026
+                    switch ((int) $this->recur["type"]) {
1027
+                        case 0x0A: // daily
1028
+                            if ($this->recur["subtype"] == 1) {
1029
+                                // Daily every workday
1030
+                                $restocc = (int) $this->recur["numoccur"];
1031
+
1032
+                                // Get starting weekday
1033
+                                $nowtime = $this->gmtime($occenddate);
1034
+                                $j = $nowtime["tm_wday"];
1035
+
1036
+                                while (1) {
1037
+                                    if (($j % 7) > 0 && ($j % 7) < 6) {
1038
+                                        --$restocc;
1039
+                                    }
1040
+
1041
+                                    ++$j;
1042
+
1043
+                                    if ($restocc <= 0) {
1044
+                                        break;
1045
+                                    }
1046
+
1047
+                                    $occenddate += 24 * 60 * 60;
1048
+                                }
1049
+                            }
1050
+                            else {
1051
+                                // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
1052
+                                $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
1053
+                            }
1054
+
1055
+                            break;
1056
+
1057
+                        case 0x0B: // weekly
1058
+                            // Needed values
1059
+                            // $forwardcount - number of weeks we can skip forward
1060
+                            // $restocc - number of remaining occurrences after the week skip
1061
+
1062
+                            // Add the weeks till the last item
1063
+                            $occenddate += ($forwardcount * 7 * 24 * 60 * 60);
1064
+
1065
+                            $dayofweek = gmdate("w", $occenddate);
1066
+
1067
+                            // Loop through the last occurrences until we have had them all
1068
+                            for ($j = 1; $restocc > 0; ++$j) {
1069
+                                // Jump to the next week (which may be N weeks away) when going over the week boundary
1070
+                                if ((($dayofweek + $j) % 7) == $weekstart) {
1071
+                                    $occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1072
+                                }
1073
+
1074
+                                // If this is a matching day, once less occurrence to process
1075
+                                if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1076
+                                    --$restocc;
1077
+                                }
1078
+
1079
+                                // Next day
1080
+                                $occenddate += 24 * 60 * 60;
1081
+                            }
1082
+
1083
+                            break;
1084
+
1085
+                        case 0x0C: // monthly
1086
+                        case 0x0D: // yearly
1087
+                            $curyear = gmdate("Y", (int) $this->recur["start"]);
1088
+                            $curmonth = gmdate("n", (int) $this->recur["start"]);
1089
+                            // $forwardcount = months
1090
+
1091
+                            switch ((int) $this->recur["subtype"]) {
1092
+                                case 2: // on D day of every M month
1093
+                                    while ($forwardcount > 0) {
1094
+                                        $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1095
+
1096
+                                        if ($curmonth >= 12) {
1097
+                                            $curmonth = 1;
1098
+                                            ++$curyear;
1099
+                                        }
1100
+                                        else {
1101
+                                            ++$curmonth;
1102
+                                        }
1103
+                                        --$forwardcount;
1104
+                                    }
1105
+
1106
+                                    // compensation between 28 and 31
1107
+                                    if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
1108
+                                        gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1109
+                                        if (gmdate("j", $occenddate) < 28) {
1110
+                                            $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1111
+                                        }
1112
+                                        else {
1113
+                                            $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1114
+                                        }
1115
+                                    }
1116
+
1117
+                                    break;
1118
+
1119
+                                case 3: // on Nth weekday of every M month
1120
+                                    $nday = (int) $this->recur["nday"]; // 1 tot 5
1121
+                                    $weekdays = (int) $this->recur["weekdays"];
1122
+
1123
+                                    while ($forwardcount > 0) {
1124
+                                        $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1125
+                                        if ($curmonth >= 12) {
1126
+                                            $curmonth = 1;
1127
+                                            ++$curyear;
1128
+                                        }
1129
+                                        else {
1130
+                                            ++$curmonth;
1131
+                                        }
1132
+
1133
+                                        --$forwardcount;
1134
+                                    }
1135
+
1136
+                                    if ($nday == 5) {
1137
+                                        // Set date on the last day of the last month
1138
+                                        $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1139
+                                    }
1140
+                                    else {
1141
+                                        // Set date on the first day of the last month
1142
+                                        $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1143
+                                    }
1144
+
1145
+                                    $dayofweek = gmdate("w", $occenddate);
1146
+                                    for ($i = 0; $i < 7; ++$i) {
1147
+                                        if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
1148
+                                            $occenddate -= $i * 24 * 60 * 60;
1149
+
1150
+                                            break;
1151
+                                        }
1152
+                                        if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
1153
+                                            $occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60;
1154
+
1155
+                                            break;
1156
+                                        }
1157
+                                    }
1158
+
1159
+                                break; // case 3:
1160
+                                }
1161
+
1162
+                            break;
1163
+                    }
1164
+
1165
+                    if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) {
1166
+                        $occenddate = PHP_INT_MAX;
1167
+                    }
1168
+
1169
+                    $this->recur["end"] = $occenddate;
1170
+
1171
+                    $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1172
+
1173
+                    break;
1174
+                // Never ends
1175
+                case 0x23:
1176
+                default:
1177
+                    $this->recur["end"] = 0x7FFFFFFF; // max date -> 2038
1178
+                    $rdata .= pack("V", 0x5AE980DF);
1179
+
1180
+                    break;
1181
+            }
1182
+
1183
+            // UTC date
1184
+            $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1185
+            $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1186
+
1187
+            // utc date+time
1188
+            $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart;
1189
+            $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1190
+
1191
+            $propsToSet = [];
1192
+            // update reminder time
1193
+            $propsToSet[$this->proptags["reminder_time"]] = $utcfirstoccstartdatetime;
1194
+
1195
+            // update first occurrence date
1196
+            $propsToSet[$this->proptags["startdate"]] = $propsToSet[$this->proptags["commonstart"]] = $utcfirstoccstartdatetime;
1197
+            $propsToSet[$this->proptags["duedate"]] = $propsToSet[$this->proptags["commonend"]] = $utcfirstoccenddatetime;
1198
+
1199
+            // Set Outlook properties, if it is an appointment
1200
+            if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") {
1201
+                // update real begin and real end date
1202
+                $propsToSet[$this->proptags["startdate_recurring"]] = $utcstart;
1203
+                $propsToSet[$this->proptags["enddate_recurring"]] = $utcend;
1204
+
1205
+                // recurrencetype
1206
+                // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1207
+                $propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9;
1208
+
1209
+                // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1210
+                $propsToSet[$this->proptags["side_effects"]] = 369;
1211
+            }
1212
+            else {
1213
+                $propsToSet[$this->proptags["side_effects"]] = 3441;
1214
+            }
1215
+
1216
+            // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
1217
+            // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
1218
+            // to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to
1219
+            // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
1220
+            // with the reminder flag set.
1221
+            $reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]);
1222
+            if (isset($reminderprops[$this->proptags["reminder_minutes"]])) {
1223
+                $occ = false;
1224
+                $occurrences = $this->getItems(time(), 0x7FF00000, 3, true);
1225
+
1226
+                for ($i = 0, $len = count($occurrences); $i < $len; ++$i) {
1227
+                    // This will actually also give us appointments that have already started, but not yet ended. Since we want the next
1228
+                    // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
1229
+                    // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
1230
+                    // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
1231
+                    // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
1232
+
1233
+                    if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
1234
+                        $occ = $occurrences[$i];
1235
+
1236
+                        break;
1237
+                    }
1238
+                }
1239
+
1240
+                if ($occ) {
1241
+                    $propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60);
1242
+                }
1243
+                else {
1244
+                    // Last reminder passed, no reminders any more.
1245
+                    $propsToSet[$this->proptags["reminder"]] = false;
1246
+                    $propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000;
1247
+                }
1248
+            }
1249
+
1250
+            // Default data
1251
+            // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
1252
+            $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
1253
+
1254
+            if (isset($this->recur["startocc"], $this->recur["endocc"])) {
1255
+                // Set start and endtime in minutes
1256
+                $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1257
+            }
1258
+
1259
+            // Detailed exception data
1260
+
1261
+            $changed_items = $this->recur["changed_occurrences"];
1262
+
1263
+            $rdata .= pack("v", count($changed_items));
1264
+
1265
+            foreach ($changed_items as $changed_item) {
1266
+                // Set start and end time of exception
1267
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1268
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1269
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1270
+
1271
+                // Bitmask
1272
+                $bitmask = 0;
1273
+
1274
+                // Check for changed strings
1275
+                if (isset($changed_item["subject"])) {
1276
+                    $bitmask |= 1 << 0;
1277
+                }
1278
+
1279
+                if (isset($changed_item["remind_before"])) {
1280
+                    $bitmask |= 1 << 2;
1281
+                }
1282
+
1283
+                if (isset($changed_item["reminder_set"])) {
1284
+                    $bitmask |= 1 << 3;
1285
+                }
1286
+
1287
+                if (isset($changed_item["location"])) {
1288
+                    $bitmask |= 1 << 4;
1289
+                }
1290
+
1291
+                if (isset($changed_item["busystatus"])) {
1292
+                    $bitmask |= 1 << 5;
1293
+                }
1294
+
1295
+                if (isset($changed_item["alldayevent"])) {
1296
+                    $bitmask |= 1 << 7;
1297
+                }
1298
+
1299
+                if (isset($changed_item["label"])) {
1300
+                    $bitmask |= 1 << 8;
1301
+                }
1302
+
1303
+                $rdata .= pack("v", $bitmask);
1304
+
1305
+                // Set "subject"
1306
+                if (isset($changed_item["subject"])) {
1307
+                    // convert utf-8 to non-unicode blob string (us-ascii?)
1308
+                    $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1309
+                    $length = strlen($subject);
1310
+                    $rdata .= pack("vv", $length + 1, $length);
1311
+                    $rdata .= pack("a" . $length, $subject);
1312
+                }
1313
+
1314
+                if (isset($changed_item["remind_before"])) {
1315
+                    $rdata .= pack("V", $changed_item["remind_before"]);
1316
+                }
1317
+
1318
+                if (isset($changed_item["reminder_set"])) {
1319
+                    $rdata .= pack("V", $changed_item["reminder_set"]);
1320
+                }
1321
+
1322
+                if (isset($changed_item["location"])) {
1323
+                    $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1324
+                    $length = strlen($location);
1325
+                    $rdata .= pack("vv", $length + 1, $length);
1326
+                    $rdata .= pack("a" . $length, $location);
1327
+                }
1328
+
1329
+                if (isset($changed_item["busystatus"])) {
1330
+                    $rdata .= pack("V", $changed_item["busystatus"]);
1331
+                }
1332
+
1333
+                if (isset($changed_item["alldayevent"])) {
1334
+                    $rdata .= pack("V", $changed_item["alldayevent"]);
1335
+                }
1336
+
1337
+                if (isset($changed_item["label"])) {
1338
+                    $rdata .= pack("V", $changed_item["label"]);
1339
+                }
1340
+            }
1341
+
1342
+            $rdata .= pack("V", 0);
1343
+
1344
+            // write extended data
1345
+            foreach ($changed_items as $changed_item) {
1346
+                $rdata .= pack("V", 0);
1347
+                if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1348
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1349
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1350
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1351
+                }
1352
+
1353
+                if (isset($changed_item["subject"])) {
1354
+                    $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1355
+                    $length = iconv_strlen($subject, "UCS-2LE");
1356
+                    $rdata .= pack("v", $length);
1357
+                    $rdata .= pack("a" . $length * 2, $subject);
1358
+                }
1359
+
1360
+                if (isset($changed_item["location"])) {
1361
+                    $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1362
+                    $length = iconv_strlen($location, "UCS-2LE");
1363
+                    $rdata .= pack("v", $length);
1364
+                    $rdata .= pack("a" . $length * 2, $location);
1365
+                }
1366
+
1367
+                if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1368
+                    $rdata .= pack("V", 0);
1369
+                }
1370
+            }
1371
+
1372
+            $rdata .= pack("V", 0);
1373
+
1374
+            // Set props
1375
+            $propsToSet[$this->proptags["recurring_data"]] = $rdata;
1376
+            $propsToSet[$this->proptags["recurring"]] = true;
1377
+
1378
+            if (isset($this->tz) && $this->tz) {
1379
+                $timezone = "GMT";
1380
+                if ($this->tz["timezone"] != 0) {
1381
+                    // Create user readable timezone information
1382
+                    $timezone = sprintf(
1383
+                        "(GMT %s%02d:%02d)",
1384
+                        (-$this->tz["timezone"] > 0 ? "+" : "-"),
1385
+                        abs($this->tz["timezone"] / 60),
1386
+                        abs($this->tz["timezone"] % 60)
1387
+                    );
1388
+                }
1389
+                $propsToSet[$this->proptags["timezone_data"]] = $this->getTimezoneData($this->tz);
1390
+                $propsToSet[$this->proptags["timezone"]] = $timezone;
1391
+            }
1392
+            mapi_setprops($this->message, $propsToSet);
1393
+        }
1394
+
1395
+        /**
1396
+         * Function which converts a recurrence date timestamp to an unix date timestamp.
1397
+         *
1398
+         * @author Steve Hardy
1399
+         *
1400
+         * @param int $rdate the date which will be converted
1401
+         *
1402
+         * @return int the converted date
1403
+         */
1404
+        public function recurDataToUnixData($rdate) {
1405
+            return ($rdate - 194074560) * 60;
1406
+        }
1407
+
1408
+        /**
1409
+         * Function which converts an unix date timestamp to recurrence date timestamp.
1410
+         *
1411
+         * @author Johnny Biemans
1412
+         *
1413
+         * @param Date $date the date which will be converted
1414
+         *
1415
+         * @return int the converted date in minutes
1416
+         */
1417
+        public function unixDataToRecurData($date) {
1418
+            return ($date / 60) + 194074560;
1419
+        }
1420
+
1421
+        /**
1422
+         * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1423
+         *
1424
+         * @author Steve Hardy
1425
+         *
1426
+         * @param mixed $ts
1427
+         */
1428
+        public function GetTZOffset($ts) {
1429
+            $Offset = date("O", $ts);
1430
+
1431
+            $Parity = $Offset < 0 ? -1 : 1;
1432
+            $Offset = $Parity * $Offset;
1433
+            $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1434
+
1435
+            return $Parity * $Offset;
1436
+        }
1437
+
1438
+        /**
1439
+         * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1440
+         *
1441
+         * @author Steve Hardy
1442
+         *
1443
+         * @param Date $time
1444
+         *
1445
+         * @return Date GMT Time
1446
+         */
1447
+        public function gmtime($time) {
1448
+            $TZOffset = $this->GetTZOffset($time);
1449
+
1450
+            $t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1451
+
1452
+            return localtime($t_time, 1);
1453
+        }
1454
+
1455
+        public function isLeapYear($year) {
1456
+            return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0);
1457
+        }
1458
+
1459
+        public function getMonthInSeconds($year, $month) {
1460
+            if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1461
+                $day = 31;
1462
+            }
1463
+            elseif (in_array($month, [4, 6, 9, 11])) {
1464
+                $day = 30;
1465
+            }
1466
+            else {
1467
+                $day = 28;
1468
+                if ($this->isLeapYear($year) == 1) {
1469
+                    ++$day;
1470
+                }
1471
+            }
1472
+
1473
+            return $day * 24 * 60 * 60;
1474
+        }
1475
+
1476
+        /**
1477
+         * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.
1478
+         *
1479
+         * @param int $year
1480
+         * @param int $month
1481
+         * @param int $week
1482
+         * @param int $day
1483
+         * @param int $hour
1484
+         *
1485
+         * @return returns the timestamp of the given date, timezone-independant
1486
+         */
1487
+        public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) {
1488
+            // get first day of month
1489
+            $date = gmmktime(0, 0, 0, $month, 0, $year + 1900);
1490
+
1491
+            // get wday info
1492
+            $gmdate = $this->gmtime($date);
1493
+
1494
+            $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
1495
+
1496
+            $date += $week * 7 * 24 * 60 * 60; // go to correct week nr
1497
+            $date += $day * 24 * 60 * 60;
1498
+            $date += $hour * 60 * 60;
1499
+
1500
+            $gmdate = $this->gmtime($date);
1501
+
1502
+            // if we are in the next month, then back up a week, because week '5' means
1503
+            // 'last week of month'
1504
+
1505
+            if ($month != $gmdate["tm_mon"] + 1) {
1506
+                $date -= 7 * 24 * 60 * 60;
1507
+            }
1508
+
1509
+            return $date;
1510
+        }
1511
+
1512
+        /**
1513
+         * getTimezone gives the timezone offset (in minutes) of the given
1514
+         * local date/time according to the given TZ info.
1515
+         *
1516
+         * @param mixed $tz
1517
+         * @param mixed $date
1518
+         */
1519
+        public function getTimezone($tz, $date) {
1520
+            // No timezone -> GMT (+0)
1521
+            if (!isset($tz["timezone"])) {
1522
+                return 0;
1523
+            }
1524
+
1525
+            $dst = false;
1526
+            $gmdate = $this->gmtime($date);
1527
+
1528
+            $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
1529
+            $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
1530
+
1531
+            if ($dststart <= $dstend) {
1532
+                // Northern hemisphere, eg DST is during Mar-Oct
1533
+                if ($date > $dststart && $date < $dstend) {
1534
+                    $dst = true;
1535
+                }
1536
+            }
1537
+            else {
1538
+                // Southern hemisphere, eg DST is during Oct-Mar
1539
+                if ($date < $dstend || $date > $dststart) {
1540
+                    $dst = true;
1541
+                }
1542
+            }
1543
+
1544
+            if ($dst) {
1545
+                return $tz["timezone"] + $tz["timezonedst"];
1546
+            }
1547
+
1548
+            return $tz["timezone"];
1549
+        }
1550
+
1551
+        /**
1552
+         * getWeekNr() returns the week nr of the month (ie first week of february is 1).
1553
+         *
1554
+         * @param mixed $date
1555
+         */
1556
+        public function getWeekNr($date) {
1557
+            $gmdate = gmtime($date);
1558
+            $gmdate["tm_mday"] = 0;
1559
+
1560
+            return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
1561
+        }
1562
+
1563
+        /**
1564
+         * parseTimezone parses the timezone as specified in named property 0x8233
1565
+         * in Outlook calendar messages. Returns the timezone in minutes negative
1566
+         * offset (GMT +2:00 -> -120).
1567
+         *
1568
+         * @param mixed $data
1569
+         */
1570
+        public function parseTimezone($data) {
1571
+            if (strlen($data) < 48) {
1572
+                return;
1573
+            }
1574
+
1575
+            return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
1576
+        }
1577
+
1578
+        public function getTimezoneData($tz) {
1579
+            return pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0, 0);
1580
+        }
1581
+
1582
+        /**
1583
+         * createTimezone creates the timezone as specified in the named property 0x8233
1584
+         * see also parseTimezone()
1585
+         * $tz is an array with the timezone data.
1586
+         *
1587
+         * @param mixed $tz
1588
+         */
1589
+        public function createTimezone($tz) {
1590
+            return pack(
1591
+                "lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
1592
+                $tz["timezone"],
1593
+                array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0,
1594
+                array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0,
1595
+                array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0,
1596
+                array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0,
1597
+                array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0,
1598
+                array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0,
1599
+                array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0
1600
+            );
1601
+        }
1602
+
1603
+        /**
1604
+         * toGMT returns a timestamp in GMT time for the time and timezone given.
1605
+         *
1606
+         * @param mixed $tz
1607
+         * @param mixed $date
1608
+         */
1609
+        public function toGMT($tz, $date) {
1610
+            if (!isset($tz['timezone'])) {
1611
+                return $date;
1612
+            }
1613
+            $offset = $this->getTimezone($tz, $date);
1614
+
1615
+            return $date + $offset * 60;
1616
+        }
1617
+
1618
+        /**
1619
+         * fromGMT returns a timestamp in the local timezone given from the GMT time given.
1620
+         *
1621
+         * @param mixed $tz
1622
+         * @param mixed $date
1623
+         */
1624
+        public function fromGMT($tz, $date) {
1625
+            $offset = $this->getTimezone($tz, $date);
1626
+
1627
+            return $date - $offset * 60;
1628
+        }
1629
+
1630
+        /**
1631
+         * Function to get timestamp of the beginning of the day of the timestamp given.
1632
+         *
1633
+         * @param date $date
1634
+         *
1635
+         * @return date timestamp referring to same day but at 00:00:00
1636
+         */
1637
+        public function dayStartOf($date) {
1638
+            $time1 = $this->gmtime($date);
1639
+
1640
+            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
1641
+        }
1642
+
1643
+        /**
1644
+         * Function to get timestamp of the beginning of the month of the timestamp given.
1645
+         *
1646
+         * @param date $date
1647
+         *
1648
+         * @return date Timestamp referring to same month but on the first day, and at 00:00:00
1649
+         */
1650
+        public function monthStartOf($date) {
1651
+            $time1 = $this->gmtime($date);
1652
+
1653
+            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
1654
+        }
1655
+
1656
+        /**
1657
+         * Function to get timestamp of the beginning of the year of the timestamp given.
1658
+         *
1659
+         * @param date $date
1660
+         *
1661
+         * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00
1662
+         */
1663
+        public function yearStartOf($date) {
1664
+            $time1 = $this->gmtime($date);
1665
+
1666
+            return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
1667
+        }
1668
+
1669
+        /**
1670
+         * Function which returns the items in a given interval. This included expansion of the recurrence and
1671
+         * processing of exceptions (modified and deleted).
1672
+         *
1673
+         * @param string $entryid       the entryid of the message
1674
+         * @param array  $props         the properties of the message
1675
+         * @param date   $start         start time of the interval (GMT)
1676
+         * @param date   $end           end time of the interval (GMT)
1677
+         * @param mixed  $limit
1678
+         * @param mixed  $remindersonly
1679
+         */
1680
+        public function getItems($start, $end, $limit = 0, $remindersonly = false) {
1681
+            $items = [];
1682
+
1683
+            if (isset($this->recur)) {
1684
+                // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
1685
+                // exceptions are in range and have a reminder set
1686
+                if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
1687
+                    // Sort exceptions by start time
1688
+                    uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]);
1689
+
1690
+                    // Loop through all changed exceptions
1691
+                    foreach ($this->recur["changed_occurrences"] as $exception) {
1692
+                        // Check reminder set
1693
+                        if (!isset($exception["reminder"]) || $exception["reminder"] == false) {
1694
+                            continue;
1695
+                        }
1696
+
1697
+                        // Convert to GMT
1698
+                        $occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz
1699
+                        $occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz
1700
+
1701
+                        // Check range criterium
1702
+                        if ($occstart > $end || $occend < $start) {
1703
+                            continue;
1704
+                        }
1705
+
1706
+                        // OK, add to items.
1707
+                        array_push($items, $this->getExceptionProperties($exception));
1708
+                        if ($limit && (count($items) == $limit)) {
1709
+                            break;
1710
+                        }
1711
+                    }
1712
+
1713
+                    uasort($items, [$this, "sortStarttime"]);
1714
+
1715
+                    return $items;
1716
+                }
1717
+
1718
+                // From here on, the dates of the occurrences are calculated in local time, so the days we're looking
1719
+                // at are calculated from the local time dates of $start and $end
1720
+                // TODO use one isset
1721
+                if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) {
1722
+                    $daystart = $this->dayStartOf($this->action['datecompleted']);
1723
+                }
1724
+                else {
1725
+                    $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
1726
+                }
1727
+
1728
+                // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
1729
+                // or the end of the recurrence, whichever comes first
1730
+                if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1731
+                    $rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1732
+                }
1733
+                else {
1734
+                    $rangeend = $end;
1735
+                }
1736
+
1737
+                $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
1738
+
1739
+                // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1740
+
1741
+                switch ($this->recur["type"]) {
1742
+                case 10:
1743
+                    // Daily
1744
+                    if ($this->recur["everyn"] <= 0) {
1745
+                        $this->recur["everyn"] = 1440;
1746
+                    }
1747
+
1748
+                    if ($this->recur["subtype"] == 0) {
1749
+                        // Every Nth day
1750
+                        for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1751
+                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1752
+                        }
1753
+                    }
1754
+                    else {
1755
+                        // Every workday
1756
+                        for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1757
+                            $nowtime = $this->gmtime($now);
1758
+                            if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1759
+                                $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1760
+                            }
1761
+                        }
1762
+                    }
1763
+
1764
+                    break;
1765
+
1766
+                case 11:
1767
+                    // Weekly
1768
+                    if ($this->recur["everyn"] <= 0) {
1769
+                        $this->recur["everyn"] = 1;
1770
+                    }
1771
+
1772
+                    // If sliding flag is set then move to 'n' weeks
1773
+                    if ($this->recur['regen']) {
1774
+                        $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1775
+                    }
1776
+
1777
+                    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1778
+                        if ($this->recur['regen']) {
1779
+                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1780
+                        }
1781
+                        else {
1782
+                            // Loop through the whole following week to the first occurrence of the week, add each day that is specified
1783
+                            for ($wday = 0; $wday < 7; ++$wday) {
1784
+                                $daynow = $now + $wday * 60 * 60 * 24;
1785
+                                // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1786
+                                if ($daynow <= $dayend) {
1787
+                                    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1788
+                                    if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1789
+                                        $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1790
+                                    }
1791
+                                }
1792
+                            }
1793
+                        }
1794
+                    }
1795
+
1796
+                    break;
1797
+
1798
+                case 12:
1799
+                    // Monthly
1800
+                    if ($this->recur["everyn"] <= 0) {
1801
+                        $this->recur["everyn"] = 1;
1802
+                    }
1803
+
1804
+                    // Loop through all months from start to end of occurrence, starting at beginning of first month
1805
+                    for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1806
+                        if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1807
+                            $difference = 1;
1808
+                            if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1809
+                                $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1810
+                            }
1811
+                            $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1812
+                            // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1813
+                            if ($daynow <= $dayend) {
1814
+                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1815
+                            }
1816
+                        }
1817
+                        elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1818
+                            // Sanitize input
1819
+                            if ($this->recur["weekdays"] == 0) {
1820
+                                $this->recur["weekdays"] = 1;
1821
+                            }
1822
+
1823
+                            // If nday is not set to the last day in the month
1824
+                            if ($this->recur["nday"] < 5) {
1825
+                                // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1826
+                                $ndaycounter = 0;
1827
+                                // Find matching weekday in this month
1828
+                                for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) {
1829
+                                    $daynow = $now + $day * 60 * 60 * 24;
1830
+                                    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1831
+
1832
+                                    if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1833
+                                        ++$ndaycounter;
1834
+                                    }
1835
+                                    // check the selected pattern is same as asked Nth weekday,If so set the firstday
1836
+                                    if ($this->recur["nday"] == $ndaycounter) {
1837
+                                        $firstday = $day;
1838
+
1839
+                                        break;
1840
+                                    }
1841
+                                }
1842
+                                // $firstday is the day of the month on which the asked pattern of nth weekday matches
1843
+                                $daynow = $now + $firstday * 60 * 60 * 24;
1844
+                            }
1845
+                            else {
1846
+                                // Find last day in the month ($now is the firstday of the month)
1847
+                                $NumDaysInMonth = $this->daysInMonth($now, 1);
1848
+                                $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1849
+
1850
+                                $nowtime = $this->gmtime($daynow);
1851
+                                while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1852
+                                    $daynow -= 86400;
1853
+                                    $nowtime = $this->gmtime($daynow);
1854
+                                }
1855
+                            }
1856
+
1857
+                            /*
1858 1858
 							 * checks weather the next coming day in recurrence pattern is less than or equal to end day of the            * recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed.
1859 1859
 							 */
1860
-							if ($daynow <= $dayend && $daynow >= $daystart) {
1861
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1862
-							}
1863
-						}
1864
-						elseif ($this->recur['regen']) {
1865
-							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1866
-							$now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1867
-
1868
-							if ($now <= $dayend) {
1869
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1870
-							}
1871
-						}
1872
-					}
1873
-
1874
-					break;
1875
-
1876
-				case 13:
1877
-					// Yearly
1878
-					if ($this->recur["everyn"] <= 0) {
1879
-						$this->recur["everyn"] = 12;
1880
-					}
1881
-
1882
-					for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1883
-						if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1884
-							// recur["month"] is in minutes since the beginning of the year
1885
-							$month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1886
-							$monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1887
-							$monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1888
-							if ($monthday > $this->daysInMonth($monthstart, 1)) {
1889
-								$monthday = $this->daysInMonth($monthstart, 1);
1890
-							}    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
1891
-							$daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1892
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1893
-						}
1894
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1895
-							// Go the correct month
1896
-							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1897
-
1898
-							// Find first matching weekday in this month
1899
-							for ($wday = 0; $wday < 7; ++$wday) {
1900
-								$daynow = $monthnow + $wday * 60 * 60 * 24;
1901
-								$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1902
-
1903
-								if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1904
-									$firstday = $wday;
1905
-
1906
-									break;
1907
-								}
1908
-							}
1909
-
1910
-							// Same as above (monthly)
1911
-							$daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1912
-
1913
-							while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1914
-								$daynow -= 7 * 60 * 60 * 24;
1915
-							}
1916
-
1917
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1918
-						}
1919
-						elseif ($this->recur['regen']) {
1920
-							$year_starttime = $this->gmtime($now);
1921
-							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1922
-							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1923
-
1924
-							if ($now <= $dayend) {
1925
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1926
-							}
1927
-						}
1928
-					}
1929
-				}
1930
-				// to get all exception items
1931
-				if (!empty($this->recur['changed_occurrences'])) {
1932
-					$this->processExceptionItems($items, $start, $end);
1933
-				}
1934
-			}
1935
-
1936
-			// sort items on starttime
1937
-			usort($items, [$this, "sortStarttime"]);
1938
-
1939
-			// Return the MAPI-compatible list of items for this object
1940
-			return $items;
1941
-		}
1942
-
1943
-		public function sortStarttime($a, $b) {
1944
-			$aTime = $a[$this->proptags["startdate"]];
1945
-			$bTime = $b[$this->proptags["startdate"]];
1946
-
1947
-			return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1);
1948
-		}
1949
-
1950
-		/**
1951
-		 * daysInMonth.
1952
-		 *
1953
-		 * Returns the number of days in the upcoming number of months. If you specify 1 month as
1954
-		 * $months it will give you the number of days in the month of $date. If you specify more it
1955
-		 * will also count the days in the upcoming months and add that to the number of days. So
1956
-		 * if you have a date in march and you specify $months as 2 it will return 61.
1957
-		 *
1958
-		 * @param int $date   specified date as timestamp from which you want to know the number
1959
-		 *                    of days in the month
1960
-		 * @param int $months number of months you want to know the number of days in
1961
-		 * @returns Integer Number of days in the specified amount of months.
1962
-		 */
1963
-		public function daysInMonth($date, $months) {
1964
-			$days = 0;
1965
-
1966
-			for ($i = 0; $i < $months; ++$i) {
1967
-				$days += date("t", $date + $days * 24 * 60 * 60);
1968
-			}
1969
-
1970
-			return $days;
1971
-		}
1972
-
1973
-		// Converts MAPI-style 'minutes' into the month of the year [0..11]
1974
-		public function monthOfYear($minutes) {
1975
-			$d = gmmktime(0, 0, 0, 1, 1, 2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes
1976
-
1977
-			$d += $minutes * 60;
1978
-
1979
-			$dtime = $this->gmtime($d);
1980
-
1981
-			return $dtime["tm_mon"];
1982
-		}
1983
-
1984
-		public function sortExceptionStart($a, $b) {
1985
-			return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1);
1986
-		}
1987
-	}
1860
+                            if ($daynow <= $dayend && $daynow >= $daystart) {
1861
+                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1862
+                            }
1863
+                        }
1864
+                        elseif ($this->recur['regen']) {
1865
+                            $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1866
+                            $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1867
+
1868
+                            if ($now <= $dayend) {
1869
+                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1870
+                            }
1871
+                        }
1872
+                    }
1873
+
1874
+                    break;
1875
+
1876
+                case 13:
1877
+                    // Yearly
1878
+                    if ($this->recur["everyn"] <= 0) {
1879
+                        $this->recur["everyn"] = 12;
1880
+                    }
1881
+
1882
+                    for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1883
+                        if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1884
+                            // recur["month"] is in minutes since the beginning of the year
1885
+                            $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1886
+                            $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1887
+                            $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1888
+                            if ($monthday > $this->daysInMonth($monthstart, 1)) {
1889
+                                $monthday = $this->daysInMonth($monthstart, 1);
1890
+                            }    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
1891
+                            $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1892
+                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1893
+                        }
1894
+                        elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1895
+                            // Go the correct month
1896
+                            $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1897
+
1898
+                            // Find first matching weekday in this month
1899
+                            for ($wday = 0; $wday < 7; ++$wday) {
1900
+                                $daynow = $monthnow + $wday * 60 * 60 * 24;
1901
+                                $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1902
+
1903
+                                if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1904
+                                    $firstday = $wday;
1905
+
1906
+                                    break;
1907
+                                }
1908
+                            }
1909
+
1910
+                            // Same as above (monthly)
1911
+                            $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1912
+
1913
+                            while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1914
+                                $daynow -= 7 * 60 * 60 * 24;
1915
+                            }
1916
+
1917
+                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1918
+                        }
1919
+                        elseif ($this->recur['regen']) {
1920
+                            $year_starttime = $this->gmtime($now);
1921
+                            $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1922
+                            $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1923
+
1924
+                            if ($now <= $dayend) {
1925
+                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1926
+                            }
1927
+                        }
1928
+                    }
1929
+                }
1930
+                // to get all exception items
1931
+                if (!empty($this->recur['changed_occurrences'])) {
1932
+                    $this->processExceptionItems($items, $start, $end);
1933
+                }
1934
+            }
1935
+
1936
+            // sort items on starttime
1937
+            usort($items, [$this, "sortStarttime"]);
1938
+
1939
+            // Return the MAPI-compatible list of items for this object
1940
+            return $items;
1941
+        }
1942
+
1943
+        public function sortStarttime($a, $b) {
1944
+            $aTime = $a[$this->proptags["startdate"]];
1945
+            $bTime = $b[$this->proptags["startdate"]];
1946
+
1947
+            return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1);
1948
+        }
1949
+
1950
+        /**
1951
+         * daysInMonth.
1952
+         *
1953
+         * Returns the number of days in the upcoming number of months. If you specify 1 month as
1954
+         * $months it will give you the number of days in the month of $date. If you specify more it
1955
+         * will also count the days in the upcoming months and add that to the number of days. So
1956
+         * if you have a date in march and you specify $months as 2 it will return 61.
1957
+         *
1958
+         * @param int $date   specified date as timestamp from which you want to know the number
1959
+         *                    of days in the month
1960
+         * @param int $months number of months you want to know the number of days in
1961
+         * @returns Integer Number of days in the specified amount of months.
1962
+         */
1963
+        public function daysInMonth($date, $months) {
1964
+            $days = 0;
1965
+
1966
+            for ($i = 0; $i < $months; ++$i) {
1967
+                $days += date("t", $date + $days * 24 * 60 * 60);
1968
+            }
1969
+
1970
+            return $days;
1971
+        }
1972
+
1973
+        // Converts MAPI-style 'minutes' into the month of the year [0..11]
1974
+        public function monthOfYear($minutes) {
1975
+            $d = gmmktime(0, 0, 0, 1, 1, 2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes
1976
+
1977
+            $d += $minutes * 60;
1978
+
1979
+            $dtime = $this->gmtime($d);
1980
+
1981
+            return $dtime["tm_mon"];
1982
+        }
1983
+
1984
+        public function sortExceptionStart($a, $b) {
1985
+            return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1);
1986
+        }
1987
+    }
Please login to merge, or discard this patch.
Switch Indentation   +185 added lines, -185 removed lines patch added patch discarded remove patch
@@ -1739,193 +1739,193 @@
 block discarded – undo
1739 1739
 				// Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1740 1740
 
1741 1741
 				switch ($this->recur["type"]) {
1742
-				case 10:
1743
-					// Daily
1744
-					if ($this->recur["everyn"] <= 0) {
1745
-						$this->recur["everyn"] = 1440;
1746
-					}
1747
-
1748
-					if ($this->recur["subtype"] == 0) {
1749
-						// Every Nth day
1750
-						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1751
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1752
-						}
1753
-					}
1754
-					else {
1755
-						// Every workday
1756
-						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1757
-							$nowtime = $this->gmtime($now);
1758
-							if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1759
-								$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1760
-							}
1761
-						}
1762
-					}
1763
-
1764
-					break;
1765
-
1766
-				case 11:
1767
-					// Weekly
1768
-					if ($this->recur["everyn"] <= 0) {
1769
-						$this->recur["everyn"] = 1;
1770
-					}
1771
-
1772
-					// If sliding flag is set then move to 'n' weeks
1773
-					if ($this->recur['regen']) {
1774
-						$daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1775
-					}
1776
-
1777
-					for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1778
-						if ($this->recur['regen']) {
1779
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1780
-						}
1781
-						else {
1782
-							// Loop through the whole following week to the first occurrence of the week, add each day that is specified
1783
-							for ($wday = 0; $wday < 7; ++$wday) {
1784
-								$daynow = $now + $wday * 60 * 60 * 24;
1785
-								// checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1786
-								if ($daynow <= $dayend) {
1787
-									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1788
-									if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1789
-										$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1790
-									}
1791
-								}
1792
-							}
1793
-						}
1794
-					}
1795
-
1796
-					break;
1797
-
1798
-				case 12:
1799
-					// Monthly
1800
-					if ($this->recur["everyn"] <= 0) {
1801
-						$this->recur["everyn"] = 1;
1802
-					}
1803
-
1804
-					// Loop through all months from start to end of occurrence, starting at beginning of first month
1805
-					for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1806
-						if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1807
-							$difference = 1;
1808
-							if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1809
-								$difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1810
-							}
1811
-							$daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1812
-							// checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1813
-							if ($daynow <= $dayend) {
1814
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1815
-							}
1816
-						}
1817
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1818
-							// Sanitize input
1819
-							if ($this->recur["weekdays"] == 0) {
1820
-								$this->recur["weekdays"] = 1;
1821
-							}
1822
-
1823
-							// If nday is not set to the last day in the month
1824
-							if ($this->recur["nday"] < 5) {
1825
-								// keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1826
-								$ndaycounter = 0;
1827
-								// Find matching weekday in this month
1828
-								for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) {
1829
-									$daynow = $now + $day * 60 * 60 * 24;
1830
-									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1831
-
1832
-									if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1833
-										++$ndaycounter;
1834
-									}
1835
-									// check the selected pattern is same as asked Nth weekday,If so set the firstday
1836
-									if ($this->recur["nday"] == $ndaycounter) {
1837
-										$firstday = $day;
1838
-
1839
-										break;
1840
-									}
1841
-								}
1842
-								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1843
-								$daynow = $now + $firstday * 60 * 60 * 24;
1844
-							}
1845
-							else {
1846
-								// Find last day in the month ($now is the firstday of the month)
1847
-								$NumDaysInMonth = $this->daysInMonth($now, 1);
1848
-								$daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1849
-
1850
-								$nowtime = $this->gmtime($daynow);
1851
-								while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1852
-									$daynow -= 86400;
1853
-									$nowtime = $this->gmtime($daynow);
1854
-								}
1855
-							}
1856
-
1857
-							/*
1742
+				    case 10:
1743
+					    // Daily
1744
+					    if ($this->recur["everyn"] <= 0) {
1745
+						    $this->recur["everyn"] = 1440;
1746
+					    }
1747
+
1748
+					    if ($this->recur["subtype"] == 0) {
1749
+						    // Every Nth day
1750
+						    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1751
+							    $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1752
+						    }
1753
+					    }
1754
+					    else {
1755
+						    // Every workday
1756
+						    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1757
+							    $nowtime = $this->gmtime($now);
1758
+							    if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1759
+								    $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1760
+							    }
1761
+						    }
1762
+					    }
1763
+
1764
+					    break;
1765
+
1766
+				    case 11:
1767
+					    // Weekly
1768
+					    if ($this->recur["everyn"] <= 0) {
1769
+						    $this->recur["everyn"] = 1;
1770
+					    }
1771
+
1772
+					    // If sliding flag is set then move to 'n' weeks
1773
+					    if ($this->recur['regen']) {
1774
+						    $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1775
+					    }
1776
+
1777
+					    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1778
+						    if ($this->recur['regen']) {
1779
+							    $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1780
+						    }
1781
+						    else {
1782
+							    // Loop through the whole following week to the first occurrence of the week, add each day that is specified
1783
+							    for ($wday = 0; $wday < 7; ++$wday) {
1784
+								    $daynow = $now + $wday * 60 * 60 * 24;
1785
+								    // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1786
+								    if ($daynow <= $dayend) {
1787
+									    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1788
+									    if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1789
+										    $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1790
+									    }
1791
+								    }
1792
+							    }
1793
+						    }
1794
+					    }
1795
+
1796
+					    break;
1797
+
1798
+				    case 12:
1799
+					    // Monthly
1800
+					    if ($this->recur["everyn"] <= 0) {
1801
+						    $this->recur["everyn"] = 1;
1802
+					    }
1803
+
1804
+					    // Loop through all months from start to end of occurrence, starting at beginning of first month
1805
+					    for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1806
+						    if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1807
+							    $difference = 1;
1808
+							    if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1809
+								    $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1810
+							    }
1811
+							    $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1812
+							    // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1813
+							    if ($daynow <= $dayend) {
1814
+								    $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1815
+							    }
1816
+						    }
1817
+						    elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1818
+							    // Sanitize input
1819
+							    if ($this->recur["weekdays"] == 0) {
1820
+								    $this->recur["weekdays"] = 1;
1821
+							    }
1822
+
1823
+							    // If nday is not set to the last day in the month
1824
+							    if ($this->recur["nday"] < 5) {
1825
+								    // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1826
+								    $ndaycounter = 0;
1827
+								    // Find matching weekday in this month
1828
+								    for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) {
1829
+									    $daynow = $now + $day * 60 * 60 * 24;
1830
+									    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1831
+
1832
+									    if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1833
+										    ++$ndaycounter;
1834
+									    }
1835
+									    // check the selected pattern is same as asked Nth weekday,If so set the firstday
1836
+									    if ($this->recur["nday"] == $ndaycounter) {
1837
+										    $firstday = $day;
1838
+
1839
+										    break;
1840
+									    }
1841
+								    }
1842
+								    // $firstday is the day of the month on which the asked pattern of nth weekday matches
1843
+								    $daynow = $now + $firstday * 60 * 60 * 24;
1844
+							    }
1845
+							    else {
1846
+								    // Find last day in the month ($now is the firstday of the month)
1847
+								    $NumDaysInMonth = $this->daysInMonth($now, 1);
1848
+								    $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1849
+
1850
+								    $nowtime = $this->gmtime($daynow);
1851
+								    while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1852
+									    $daynow -= 86400;
1853
+									    $nowtime = $this->gmtime($daynow);
1854
+								    }
1855
+							    }
1856
+
1857
+							    /*
1858 1858
 							 * checks weather the next coming day in recurrence pattern is less than or equal to end day of the            * recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed.
1859 1859
 							 */
1860
-							if ($daynow <= $dayend && $daynow >= $daystart) {
1861
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1862
-							}
1863
-						}
1864
-						elseif ($this->recur['regen']) {
1865
-							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1866
-							$now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1867
-
1868
-							if ($now <= $dayend) {
1869
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1870
-							}
1871
-						}
1872
-					}
1873
-
1874
-					break;
1875
-
1876
-				case 13:
1877
-					// Yearly
1878
-					if ($this->recur["everyn"] <= 0) {
1879
-						$this->recur["everyn"] = 12;
1880
-					}
1881
-
1882
-					for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1883
-						if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1884
-							// recur["month"] is in minutes since the beginning of the year
1885
-							$month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1886
-							$monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1887
-							$monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1888
-							if ($monthday > $this->daysInMonth($monthstart, 1)) {
1889
-								$monthday = $this->daysInMonth($monthstart, 1);
1890
-							}    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
1891
-							$daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1892
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1893
-						}
1894
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1895
-							// Go the correct month
1896
-							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1897
-
1898
-							// Find first matching weekday in this month
1899
-							for ($wday = 0; $wday < 7; ++$wday) {
1900
-								$daynow = $monthnow + $wday * 60 * 60 * 24;
1901
-								$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1902
-
1903
-								if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1904
-									$firstday = $wday;
1905
-
1906
-									break;
1907
-								}
1908
-							}
1909
-
1910
-							// Same as above (monthly)
1911
-							$daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1912
-
1913
-							while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1914
-								$daynow -= 7 * 60 * 60 * 24;
1915
-							}
1916
-
1917
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1918
-						}
1919
-						elseif ($this->recur['regen']) {
1920
-							$year_starttime = $this->gmtime($now);
1921
-							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1922
-							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1923
-
1924
-							if ($now <= $dayend) {
1925
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1926
-							}
1927
-						}
1928
-					}
1860
+							    if ($daynow <= $dayend && $daynow >= $daystart) {
1861
+								    $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1862
+							    }
1863
+						    }
1864
+						    elseif ($this->recur['regen']) {
1865
+							    $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1866
+							    $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1867
+
1868
+							    if ($now <= $dayend) {
1869
+								    $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1870
+							    }
1871
+						    }
1872
+					    }
1873
+
1874
+					    break;
1875
+
1876
+				    case 13:
1877
+					    // Yearly
1878
+					    if ($this->recur["everyn"] <= 0) {
1879
+						    $this->recur["everyn"] = 12;
1880
+					    }
1881
+
1882
+					    for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1883
+						    if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1884
+							    // recur["month"] is in minutes since the beginning of the year
1885
+							    $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1886
+							    $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1887
+							    $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1888
+							    if ($monthday > $this->daysInMonth($monthstart, 1)) {
1889
+								    $monthday = $this->daysInMonth($monthstart, 1);
1890
+							    }    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
1891
+							    $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1892
+							    $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1893
+						    }
1894
+						    elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1895
+							    // Go the correct month
1896
+							    $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1897
+
1898
+							    // Find first matching weekday in this month
1899
+							    for ($wday = 0; $wday < 7; ++$wday) {
1900
+								    $daynow = $monthnow + $wday * 60 * 60 * 24;
1901
+								    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1902
+
1903
+								    if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1904
+									    $firstday = $wday;
1905
+
1906
+									    break;
1907
+								    }
1908
+							    }
1909
+
1910
+							    // Same as above (monthly)
1911
+							    $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1912
+
1913
+							    while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1914
+								    $daynow -= 7 * 60 * 60 * 24;
1915
+							    }
1916
+
1917
+							    $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1918
+						    }
1919
+						    elseif ($this->recur['regen']) {
1920
+							    $year_starttime = $this->gmtime($now);
1921
+							    $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1922
+							    $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1923
+
1924
+							    if ($now <= $dayend) {
1925
+								    $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1926
+							    }
1927
+						    }
1928
+					    }
1929 1929
 				}
1930 1930
 				// to get all exception items
1931 1931
 				if (!empty($this->recur['changed_occurrences'])) {
Please login to merge, or discard this patch.
Spacing   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -573,14 +573,14 @@  discard block
 block discarded – undo
573 573
 				return;
574 574
 			}
575 575
 
576
-			$rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
576
+			$rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int)$this->recur["type"], 0x20, (int)$this->recur["subtype"]);
577 577
 
578 578
 			$weekstart = 1; // monday
579 579
 			$forwardcount = 0;
580 580
 			$restocc = 0;
581
-			$dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
581
+			$dayofweek = (int)gmdate("w", (int)$this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
582 582
 
583
-			$term = (int) $this->recur["type"];
583
+			$term = (int)$this->recur["type"];
584 584
 
585 585
 			switch ($term) {
586 586
 				case 0x0A:
@@ -596,12 +596,12 @@  discard block
 block discarded – undo
596 596
 					else {
597 597
 						// Daily every N days (everyN in minutes)
598 598
 
599
-						$everyn = ((int) $this->recur["everyn"]) / 1440;
599
+						$everyn = ((int)$this->recur["everyn"]) / 1440;
600 600
 
601 601
 						// Calc first occ
602
-						$firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
602
+						$firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int)$this->recur["everyn"]);
603 603
 
604
-						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
604
+						$rdata .= pack("VVV", $firstocc, (int)$this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
605 605
 					}
606 606
 
607 607
 					break;
@@ -624,7 +624,7 @@  discard block
 block discarded – undo
624 624
 						$daycount = 0;
625 625
 						$dayskip = -1;
626 626
 						for ($j = 0; $j < 7; ++$j) {
627
-							if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
627
+							if (((int)$this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
628 628
 								if ($dayskip == -1) {
629 629
 									$dayskip = $j;
630 630
 								}
@@ -643,35 +643,35 @@  discard block
 block discarded – undo
643 643
 
644 644
 						// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
645 645
 						// remaining occurrences based on the start of the recurrence.
646
-						if (((int) $this->recur["term"]) == 0x22) {
646
+						if (((int)$this->recur["term"]) == 0x22) {
647 647
 							// $weekskip is the amount of weeks to skip from the startdate before the first occurrence
648 648
 							// $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
649 649
 							// is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
650 650
 							// (eg when numoccur = 2, and daycount = 1)
651
-							$forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount);
651
+							$forwardcount = floor((int)($this->recur["numoccur"] - 1) / $daycount);
652 652
 
653 653
 							// $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
654 654
 							// for the occurrence on the first day
655
-							$restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
655
+							$restocc = ((int)$this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
656 656
 
657 657
 							// $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
658
-							$forwardcount *= (int) $this->recur["everyn"];
658
+							$forwardcount *= (int)$this->recur["everyn"];
659 659
 						}
660 660
 
661 661
 						// The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
662
-						$this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
662
+						$this->recur["start"] = ((int)$this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int)$this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
663 663
 					}
664 664
 
665 665
 					// Calc first occ
666
-					$firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60);
666
+					$firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int)$this->recur["everyn"]) * 7 * 24 * 60);
667 667
 
668
-					$firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
668
+					$firstocc -= (((int)gmdate("w", (int)$this->recur["start"])) - 1) * 24 * 60;
669 669
 
670 670
 					if ($this->recur["regen"]) {
671
-						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
671
+						$rdata .= pack("VVV", $firstocc, (int)$this->recur["everyn"], 1);
672 672
 					}
673 673
 					else {
674
-						$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
674
+						$rdata .= pack("VVVV", $firstocc, (int)$this->recur["everyn"], 0, (int)$this->recur["weekdays"]);
675 675
 					}
676 676
 
677 677
 					break;
@@ -688,31 +688,31 @@  discard block
 block discarded – undo
688 688
 					}
689 689
 
690 690
 					if ($term == 0x0C /* monthly */) {
691
-						$everyn = (int) $this->recur["everyn"];
691
+						$everyn = (int)$this->recur["everyn"];
692 692
 					}
693 693
 					else {
694
-						$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
694
+						$everyn = $this->recur["regen"] ? ((int)$this->recur["everyn"]) * 12 : 12;
695 695
 					}
696 696
 
697 697
 					// Get montday/month/year of original start
698
-					$curmonthday = gmdate("j", (int) $this->recur["start"]);
699
-					$curyear = gmdate("Y", (int) $this->recur["start"]);
700
-					$curmonth = gmdate("n", (int) $this->recur["start"]);
698
+					$curmonthday = gmdate("j", (int)$this->recur["start"]);
699
+					$curyear = gmdate("Y", (int)$this->recur["start"]);
700
+					$curmonth = gmdate("n", (int)$this->recur["start"]);
701 701
 
702 702
 					// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
703 703
 					// remaining occurrences based on the start of the recurrence.
704
-					if (((int) $this->recur["term"]) == 0x22) {
704
+					if (((int)$this->recur["term"]) == 0x22) {
705 705
 						// $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
706 706
 						// one to make sure there are always at least one occurrence left)
707
-						$forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn);
707
+						$forwardcount = ((((int)$this->recur["numoccur"]) - 1) * $everyn);
708 708
 					}
709 709
 
710 710
 					// Get month for yearly on D'th day of month M
711 711
 					if ($term == 0x0D /* yearly */) {
712
-						$selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg
712
+						$selmonth = floor(((int)$this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg
713 713
 					}
714 714
 
715
-					switch ((int) $this->recur["subtype"]) {
715
+					switch ((int)$this->recur["subtype"]) {
716 716
 						// on D day of every M month
717 717
 						case 2:
718 718
 							if (!isset($this->recur["monthday"])) {
@@ -725,11 +725,11 @@  discard block
 block discarded – undo
725 725
 							// Go the beginning of the month
726 726
 							$this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60;
727 727
 							// Go the the correct month day
728
-							$this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60;
728
+							$this->recur["start"] += (((int)$this->recur["monthday"]) - 1) * 24 * 60 * 60;
729 729
 
730 730
 							// If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence
731
-							if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) ||
732
-								($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) {
731
+							if (($term == 0x0C /* monthly */ && ((int)$this->recur["monthday"]) < $curmonthday) ||
732
+								($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int)$this->recur["monthday"]) < $curmonthday)))) {
733 733
 								if ($term == 0x0D /* yearly */) {
734 734
 									if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date
735 735
 										$count = $everyn - ($curmonth - $selmonth);
@@ -739,7 +739,7 @@  discard block
 block discarded – undo
739 739
 									}
740 740
 									else {
741 741
 										// Go to next occurrence while recurrence start date is greater than occurrence date but within same month
742
-										if (((int) $this->recur["monthday"]) < $curmonthday) {
742
+										if (((int)$this->recur["monthday"]) < $curmonthday) {
743 743
 											$count = $everyn;
744 744
 										}
745 745
 									}
@@ -765,33 +765,33 @@  discard block
 block discarded – undo
765 765
 							// of each month will overshoot in february (29 days). We compensate for that by checking
766 766
 							// if the day of the month we got is wrong, and then back up to the last day of the previous
767 767
 							// month.
768
-							if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
769
-								gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) {
770
-								$this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60;
768
+							if (((int)$this->recur["monthday"]) >= 28 && ((int)$this->recur["monthday"]) <= 31 &&
769
+								gmdate("j", ((int)$this->recur["start"])) < ((int)$this->recur["monthday"])) {
770
+								$this->recur["start"] -= gmdate("j", ((int)$this->recur["start"])) * 24 * 60 * 60;
771 771
 							}
772 772
 
773 773
 							// "start" is now the first occurrence
774 774
 
775 775
 							if ($term == 0x0C /* monthly */) {
776 776
 								// Calc first occ
777
-								$monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
777
+								$monthIndex = ((((12 % $everyn) * ((((int)gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int)gmdate("n", $this->recur["start"])) - 1)) % $everyn;
778 778
 
779 779
 								$firstocc = 0;
780 780
 								for ($i = 0; $i < $monthIndex; ++$i) {
781 781
 									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
782 782
 								}
783 783
 
784
-								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
784
+								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int)$this->recur["monthday"]);
785 785
 							}
786 786
 							else {
787 787
 								// Calc first occ
788 788
 								$firstocc = 0;
789
-								$monthIndex = (int) gmdate("n", $this->recur["start"]);
789
+								$monthIndex = (int)gmdate("n", $this->recur["start"]);
790 790
 								for ($i = 1; $i < $monthIndex; ++$i) {
791 791
 									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
792 792
 								}
793 793
 
794
-								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
794
+								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int)$this->recur["monthday"]);
795 795
 							}
796 796
 
797 797
 							break;
@@ -803,11 +803,11 @@  discard block
 block discarded – undo
803 803
 								return;
804 804
 							}
805 805
 
806
-							$weekdays = (int) $this->recur["weekdays"];
807
-							$nday = (int) $this->recur["nday"];
806
+							$weekdays = (int)$this->recur["weekdays"];
807
+							$nday = (int)$this->recur["nday"];
808 808
 
809 809
 							// Calc startdate
810
-							$monthbegindow = (int) $this->recur["start"];
810
+							$monthbegindow = (int)$this->recur["start"];
811 811
 
812 812
 							if ($nday == 5) {
813 813
 								// Set date on the last day of the last month
@@ -842,12 +842,12 @@  discard block
 block discarded – undo
842 842
 
843 843
 								$dayofweek = gmdate("w", $monthbegindow);
844 844
 								for ($i = 0; $i < 7; ++$i) {
845
-									if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
845
+									if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7))&$weekdays) {
846 846
 										$day = gmdate("j", $monthbegindow) - $i;
847 847
 
848 848
 										break;
849 849
 									}
850
-									if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
850
+									if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) {
851 851
 										$day = (($nday - 1) * 7) + ($i + 1);
852 852
 
853 853
 										break;
@@ -855,7 +855,7 @@  discard block
 block discarded – undo
855 855
 								}
856 856
 
857 857
 								// Goto the next X month
858
-								if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) {
858
+								if (isset($day) && ($day < gmdate("j", (int)$this->recur["start"]))) {
859 859
 									if ($nday == 5) {
860 860
 										$monthbegindow += 24 * 60 * 60;
861 861
 										if ($curmonth == 12) {
@@ -887,12 +887,12 @@  discard block
 block discarded – undo
887 887
 							// Set start on the right day
888 888
 							$dayofweek = gmdate("w", $monthbegindow);
889 889
 							for ($i = 0; $i < 7; ++$i) {
890
-								if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
890
+								if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7))&$weekdays) {
891 891
 									$day = $i;
892 892
 
893 893
 									break;
894 894
 								}
895
-								if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
895
+								if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) {
896 896
 									$day = ($nday - 1) * 7 + ($i + 1);
897 897
 
898 898
 									break;
@@ -909,7 +909,7 @@  discard block
 block discarded – undo
909 909
 
910 910
 							if ($term == 0x0C /* monthly */) {
911 911
 								// Calc first occ
912
-								$monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
912
+								$monthIndex = ((((12 % $everyn) * (((int)gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int)gmdate("n", $this->recur["start"])) - 1)) % $everyn;
913 913
 
914 914
 								for ($i = 0; $i < $monthIndex; ++$i) {
915 915
 									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
@@ -919,7 +919,7 @@  discard block
 block discarded – undo
919 919
 							}
920 920
 							else {
921 921
 								// Calc first occ
922
-								$monthIndex = (int) gmdate("n", $this->recur["start"]);
922
+								$monthIndex = (int)gmdate("n", $this->recur["start"]);
923 923
 
924 924
 								for ($i = 1; $i < $monthIndex; ++$i) {
925 925
 									$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
@@ -939,7 +939,7 @@  discard block
 block discarded – undo
939 939
 			}
940 940
 
941 941
 			// Terminate
942
-			$term = (int) $this->recur["term"];
942
+			$term = (int)$this->recur["term"];
943 943
 			$rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
944 944
 
945 945
 			switch ($term) {
@@ -954,7 +954,7 @@  discard block
 block discarded – undo
954 954
 						return;
955 955
 					}
956 956
 
957
-					$rdata .= pack("V", (int) $this->recur["numoccur"]);
957
+					$rdata .= pack("V", (int)$this->recur["numoccur"]);
958 958
 
959 959
 					break;
960 960
 				// Never ends
@@ -965,7 +965,7 @@  discard block
 block discarded – undo
965 965
 			}
966 966
 
967 967
 			// Strange little thing for the recurrence type "every workday"
968
-			if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
968
+			if (((int)$this->recur["type"]) == 0x0B && ((int)$this->recur["subtype"]) == 1) {
969 969
 				$rdata .= pack("V", 1);
970 970
 			}
971 971
 			else { // Other recurrences
@@ -1009,25 +1009,25 @@  discard block
 block discarded – undo
1009 1009
 			}
1010 1010
 
1011 1011
 			// Set start date
1012
-			$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
1012
+			$rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["start"]));
1013 1013
 
1014 1014
 			// Set enddate
1015 1015
 			switch ($term) {
1016 1016
 				// After the given enddate
1017 1017
 				case 0x21:
1018
-					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1018
+					$rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["end"]));
1019 1019
 
1020 1020
 					break;
1021 1021
 				// After a number of times
1022 1022
 				case 0x22:
1023 1023
 					// @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
1024
-					$occenddate = (int) $this->recur["start"];
1024
+					$occenddate = (int)$this->recur["start"];
1025 1025
 
1026
-					switch ((int) $this->recur["type"]) {
1026
+					switch ((int)$this->recur["type"]) {
1027 1027
 						case 0x0A: // daily
1028 1028
 							if ($this->recur["subtype"] == 1) {
1029 1029
 								// Daily every workday
1030
-								$restocc = (int) $this->recur["numoccur"];
1030
+								$restocc = (int)$this->recur["numoccur"];
1031 1031
 
1032 1032
 								// Get starting weekday
1033 1033
 								$nowtime = $this->gmtime($occenddate);
@@ -1049,7 +1049,7 @@  discard block
 block discarded – undo
1049 1049
 							}
1050 1050
 							else {
1051 1051
 								// -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
1052
-								$occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
1052
+								$occenddate += (((int)$this->recur["everyn"]) * 60 * (((int)$this->recur["numoccur"] - 1)));
1053 1053
 							}
1054 1054
 
1055 1055
 							break;
@@ -1068,11 +1068,11 @@  discard block
 block discarded – undo
1068 1068
 							for ($j = 1; $restocc > 0; ++$j) {
1069 1069
 								// Jump to the next week (which may be N weeks away) when going over the week boundary
1070 1070
 								if ((($dayofweek + $j) % 7) == $weekstart) {
1071
-									$occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1071
+									$occenddate += (((int)$this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1072 1072
 								}
1073 1073
 
1074 1074
 								// If this is a matching day, once less occurrence to process
1075
-								if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1075
+								if (((int)$this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1076 1076
 									--$restocc;
1077 1077
 								}
1078 1078
 
@@ -1084,11 +1084,11 @@  discard block
 block discarded – undo
1084 1084
 
1085 1085
 						case 0x0C: // monthly
1086 1086
 						case 0x0D: // yearly
1087
-							$curyear = gmdate("Y", (int) $this->recur["start"]);
1088
-							$curmonth = gmdate("n", (int) $this->recur["start"]);
1087
+							$curyear = gmdate("Y", (int)$this->recur["start"]);
1088
+							$curmonth = gmdate("n", (int)$this->recur["start"]);
1089 1089
 							// $forwardcount = months
1090 1090
 
1091
-							switch ((int) $this->recur["subtype"]) {
1091
+							switch ((int)$this->recur["subtype"]) {
1092 1092
 								case 2: // on D day of every M month
1093 1093
 									while ($forwardcount > 0) {
1094 1094
 										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
@@ -1104,8 +1104,8 @@  discard block
 block discarded – undo
1104 1104
 									}
1105 1105
 
1106 1106
 									// compensation between 28 and 31
1107
-									if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
1108
-										gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1107
+									if (((int)$this->recur["monthday"]) >= 28 && ((int)$this->recur["monthday"]) <= 31 &&
1108
+										gmdate("j", $occenddate) < ((int)$this->recur["monthday"])) {
1109 1109
 										if (gmdate("j", $occenddate) < 28) {
1110 1110
 											$occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1111 1111
 										}
@@ -1117,8 +1117,8 @@  discard block
 block discarded – undo
1117 1117
 									break;
1118 1118
 
1119 1119
 								case 3: // on Nth weekday of every M month
1120
-									$nday = (int) $this->recur["nday"]; // 1 tot 5
1121
-									$weekdays = (int) $this->recur["weekdays"];
1120
+									$nday = (int)$this->recur["nday"]; // 1 tot 5
1121
+									$weekdays = (int)$this->recur["weekdays"];
1122 1122
 
1123 1123
 									while ($forwardcount > 0) {
1124 1124
 										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
@@ -1144,12 +1144,12 @@  discard block
 block discarded – undo
1144 1144
 
1145 1145
 									$dayofweek = gmdate("w", $occenddate);
1146 1146
 									for ($i = 0; $i < 7; ++$i) {
1147
-										if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) {
1147
+										if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7))&$weekdays) {
1148 1148
 											$occenddate -= $i * 24 * 60 * 60;
1149 1149
 
1150 1150
 											break;
1151 1151
 										}
1152
-										if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) {
1152
+										if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) {
1153 1153
 											$occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60;
1154 1154
 
1155 1155
 											break;
@@ -1168,7 +1168,7 @@  discard block
 block discarded – undo
1168 1168
 
1169 1169
 					$this->recur["end"] = $occenddate;
1170 1170
 
1171
-					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1171
+					$rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["end"]));
1172 1172
 
1173 1173
 					break;
1174 1174
 				// Never ends
@@ -1181,12 +1181,12 @@  discard block
 block discarded – undo
1181 1181
 			}
1182 1182
 
1183 1183
 			// UTC date
1184
-			$utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1185
-			$utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1184
+			$utcstart = $this->toGMT($this->tz, (int)$this->recur["start"]);
1185
+			$utcend = $this->toGMT($this->tz, (int)$this->recur["end"]);
1186 1186
 
1187 1187
 			// utc date+time
1188
-			$utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart;
1189
-			$utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1188
+			$utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int)$this->recur["startocc"]) * 60) : $utcstart;
1189
+			$utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int)$this->recur["endocc"]) * 60) : $utcstart;
1190 1190
 
1191 1191
 			$propsToSet = [];
1192 1192
 			// update reminder time
@@ -1204,7 +1204,7 @@  discard block
 block discarded – undo
1204 1204
 
1205 1205
 				// recurrencetype
1206 1206
 				// Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1207
-				$propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9;
1207
+				$propsToSet[$this->proptags["recurrencetype"]] = ((int)$this->recur["type"]) - 0x9;
1208 1208
 
1209 1209
 				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1210 1210
 				$propsToSet[$this->proptags["side_effects"]] = 369;
@@ -1253,7 +1253,7 @@  discard block
 block discarded – undo
1253 1253
 
1254 1254
 			if (isset($this->recur["startocc"], $this->recur["endocc"])) {
1255 1255
 				// Set start and endtime in minutes
1256
-				$rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1256
+				$rdata .= pack("VV", (int)$this->recur["startocc"], (int)$this->recur["endocc"]);
1257 1257
 			}
1258 1258
 
1259 1259
 			// Detailed exception data
@@ -1308,7 +1308,7 @@  discard block
 block discarded – undo
1308 1308
 					$subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1309 1309
 					$length = strlen($subject);
1310 1310
 					$rdata .= pack("vv", $length + 1, $length);
1311
-					$rdata .= pack("a" . $length, $subject);
1311
+					$rdata .= pack("a".$length, $subject);
1312 1312
 				}
1313 1313
 
1314 1314
 				if (isset($changed_item["remind_before"])) {
@@ -1323,7 +1323,7 @@  discard block
 block discarded – undo
1323 1323
 					$location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1324 1324
 					$length = strlen($location);
1325 1325
 					$rdata .= pack("vv", $length + 1, $length);
1326
-					$rdata .= pack("a" . $length, $location);
1326
+					$rdata .= pack("a".$length, $location);
1327 1327
 				}
1328 1328
 
1329 1329
 				if (isset($changed_item["busystatus"])) {
@@ -1354,14 +1354,14 @@  discard block
 block discarded – undo
1354 1354
 					$subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1355 1355
 					$length = iconv_strlen($subject, "UCS-2LE");
1356 1356
 					$rdata .= pack("v", $length);
1357
-					$rdata .= pack("a" . $length * 2, $subject);
1357
+					$rdata .= pack("a".$length * 2, $subject);
1358 1358
 				}
1359 1359
 
1360 1360
 				if (isset($changed_item["location"])) {
1361 1361
 					$location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1362 1362
 					$length = iconv_strlen($location, "UCS-2LE");
1363 1363
 					$rdata .= pack("v", $length);
1364
-					$rdata .= pack("a" . $length * 2, $location);
1364
+					$rdata .= pack("a".$length * 2, $location);
1365 1365
 				}
1366 1366
 
1367 1367
 				if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
@@ -1918,7 +1918,7 @@  discard block
 block discarded – undo
1918 1918
 						}
1919 1919
 						elseif ($this->recur['regen']) {
1920 1920
 							$year_starttime = $this->gmtime($now);
1921
-							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1921
+							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year
1922 1922
 							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1923 1923
 
1924 1924
 							if ($now <= $dayend) {
Please login to merge, or discard this patch.
Braces   +35 added lines, -70 removed lines patch added patch discarded remove patch
@@ -53,8 +53,7 @@  discard block
 block discarded – undo
53 53
 
54 54
 			if (is_array($message)) {
55 55
 				$this->messageprops = $message;
56
-			}
57
-			else {
56
+			} else {
58 57
 				$this->message = $message;
59 58
 				$this->messageprops = mapi_getprops($this->message, $this->proptags);
60 59
 			}
@@ -218,8 +217,7 @@  discard block
 block discarded – undo
218 217
 
219 218
 					if ($ret["subtype"] == 3) {
220 219
 						$ret["weekdays"] = $data["monthday"];
221
-					}
222
-					else {
220
+					} else {
223 221
 						$ret["monthday"] = $data["monthday"];
224 222
 					}
225 223
 
@@ -247,8 +245,7 @@  discard block
 block discarded – undo
247 245
 
248 246
 					if ($ret["subtype"] == 3) {
249 247
 						$ret["weekdays"] = $data["monthday"];
250
-					}
251
-					else {
248
+					} else {
252 249
 						$ret["monthday"] = $data["monthday"];
253 250
 					}
254 251
 
@@ -592,8 +589,7 @@  discard block
 block discarded – undo
592 589
 					if ($this->recur["subtype"] == 1) {
593 590
 						// Daily every workday
594 591
 						$rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
595
-					}
596
-					else {
592
+					} else {
597 593
 						// Daily every N days (everyN in minutes)
598 594
 
599 595
 						$everyn = ((int) $this->recur["everyn"]) / 1440;
@@ -669,8 +665,7 @@  discard block
 block discarded – undo
669 665
 
670 666
 					if ($this->recur["regen"]) {
671 667
 						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
672
-					}
673
-					else {
668
+					} else {
674 669
 						$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
675 670
 					}
676 671
 
@@ -689,8 +684,7 @@  discard block
 block discarded – undo
689 684
 
690 685
 					if ($term == 0x0C /* monthly */) {
691 686
 						$everyn = (int) $this->recur["everyn"];
692
-					}
693
-					else {
687
+					} else {
694 688
 						$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
695 689
 					}
696 690
 
@@ -733,18 +727,15 @@  discard block
 block discarded – undo
733 727
 								if ($term == 0x0D /* yearly */) {
734 728
 									if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date
735 729
 										$count = $everyn - ($curmonth - $selmonth);
736
-									}
737
-									elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date
730
+									} elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date
738 731
 										$count = $selmonth - $curmonth;
739
-									}
740
-									else {
732
+									} else {
741 733
 										// Go to next occurrence while recurrence start date is greater than occurrence date but within same month
742 734
 										if (((int) $this->recur["monthday"]) < $curmonthday) {
743 735
 											$count = $everyn;
744 736
 										}
745 737
 									}
746
-								}
747
-								else {
738
+								} else {
748 739
 									$count = $everyn; // Monthly, go to next occurrence in 'everyn' months
749 740
 								}
750 741
 
@@ -782,8 +773,7 @@  discard block
 block discarded – undo
782 773
 								}
783 774
 
784 775
 								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
785
-							}
786
-							else {
776
+							} else {
787 777
 								// Calc first occ
788 778
 								$firstocc = 0;
789 779
 								$monthIndex = (int) gmdate("n", $this->recur["start"]);
@@ -812,8 +802,7 @@  discard block
 block discarded – undo
812 802
 							if ($nday == 5) {
813 803
 								// Set date on the last day of the last month
814 804
 								$monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
815
-							}
816
-							else {
805
+							} else {
817 806
 								// Set on the first day of the month
818 807
 								$monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
819 808
 							}
@@ -822,8 +811,7 @@  discard block
 block discarded – undo
822 811
 								// Set on right month
823 812
 								if ($selmonth < $curmonth) {
824 813
 									$tmp = 12 - $curmonth + $selmonth;
825
-								}
826
-								else {
814
+								} else {
827 815
 									$tmp = ($selmonth - $curmonth);
828 816
 								}
829 817
 
@@ -836,8 +824,7 @@  discard block
 block discarded – undo
836 824
 									}
837 825
 									++$curmonth;
838 826
 								}
839
-							}
840
-							else {
827
+							} else {
841 828
 								// Check or you exist in the right month
842 829
 
843 830
 								$dayofweek = gmdate("w", $monthbegindow);
@@ -900,8 +887,7 @@  discard block
 block discarded – undo
900 887
 							}
901 888
 							if ($nday == 5) {
902 889
 								$monthbegindow -= $day * 24 * 60 * 60;
903
-							}
904
-							else {
890
+							} else {
905 891
 								$monthbegindow += ($day - 1) * 24 * 60 * 60;
906 892
 							}
907 893
 
@@ -916,8 +902,7 @@  discard block
 block discarded – undo
916 902
 								}
917 903
 
918 904
 								$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
919
-							}
920
-							else {
905
+							} else {
921 906
 								// Calc first occ
922 907
 								$monthIndex = (int) gmdate("n", $this->recur["start"]);
923 908
 
@@ -967,8 +952,7 @@  discard block
 block discarded – undo
967 952
 			// Strange little thing for the recurrence type "every workday"
968 953
 			if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
969 954
 				$rdata .= pack("V", 1);
970
-			}
971
-			else { // Other recurrences
955
+			} else { // Other recurrences
972 956
 				$rdata .= pack("V", 0);
973 957
 			}
974 958
 
@@ -1046,8 +1030,7 @@  discard block
 block discarded – undo
1046 1030
 
1047 1031
 									$occenddate += 24 * 60 * 60;
1048 1032
 								}
1049
-							}
1050
-							else {
1033
+							} else {
1051 1034
 								// -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
1052 1035
 								$occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
1053 1036
 							}
@@ -1096,8 +1079,7 @@  discard block
 block discarded – undo
1096 1079
 										if ($curmonth >= 12) {
1097 1080
 											$curmonth = 1;
1098 1081
 											++$curyear;
1099
-										}
1100
-										else {
1082
+										} else {
1101 1083
 											++$curmonth;
1102 1084
 										}
1103 1085
 										--$forwardcount;
@@ -1108,8 +1090,7 @@  discard block
 block discarded – undo
1108 1090
 										gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1109 1091
 										if (gmdate("j", $occenddate) < 28) {
1110 1092
 											$occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1111
-										}
1112
-										else {
1093
+										} else {
1113 1094
 											$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1114 1095
 										}
1115 1096
 									}
@@ -1125,8 +1106,7 @@  discard block
 block discarded – undo
1125 1106
 										if ($curmonth >= 12) {
1126 1107
 											$curmonth = 1;
1127 1108
 											++$curyear;
1128
-										}
1129
-										else {
1109
+										} else {
1130 1110
 											++$curmonth;
1131 1111
 										}
1132 1112
 
@@ -1136,8 +1116,7 @@  discard block
 block discarded – undo
1136 1116
 									if ($nday == 5) {
1137 1117
 										// Set date on the last day of the last month
1138 1118
 										$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1139
-									}
1140
-									else {
1119
+									} else {
1141 1120
 										// Set date on the first day of the last month
1142 1121
 										$occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1143 1122
 									}
@@ -1208,8 +1187,7 @@  discard block
 block discarded – undo
1208 1187
 
1209 1188
 				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1210 1189
 				$propsToSet[$this->proptags["side_effects"]] = 369;
1211
-			}
1212
-			else {
1190
+			} else {
1213 1191
 				$propsToSet[$this->proptags["side_effects"]] = 3441;
1214 1192
 			}
1215 1193
 
@@ -1239,8 +1217,7 @@  discard block
 block discarded – undo
1239 1217
 
1240 1218
 				if ($occ) {
1241 1219
 					$propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60);
1242
-				}
1243
-				else {
1220
+				} else {
1244 1221
 					// Last reminder passed, no reminders any more.
1245 1222
 					$propsToSet[$this->proptags["reminder"]] = false;
1246 1223
 					$propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000;
@@ -1459,11 +1436,9 @@  discard block
 block discarded – undo
1459 1436
 		public function getMonthInSeconds($year, $month) {
1460 1437
 			if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1461 1438
 				$day = 31;
1462
-			}
1463
-			elseif (in_array($month, [4, 6, 9, 11])) {
1439
+			} elseif (in_array($month, [4, 6, 9, 11])) {
1464 1440
 				$day = 30;
1465
-			}
1466
-			else {
1441
+			} else {
1467 1442
 				$day = 28;
1468 1443
 				if ($this->isLeapYear($year) == 1) {
1469 1444
 					++$day;
@@ -1533,8 +1508,7 @@  discard block
 block discarded – undo
1533 1508
 				if ($date > $dststart && $date < $dstend) {
1534 1509
 					$dst = true;
1535 1510
 				}
1536
-			}
1537
-			else {
1511
+			} else {
1538 1512
 				// Southern hemisphere, eg DST is during Oct-Mar
1539 1513
 				if ($date < $dstend || $date > $dststart) {
1540 1514
 					$dst = true;
@@ -1720,8 +1694,7 @@  discard block
 block discarded – undo
1720 1694
 				// TODO use one isset
1721 1695
 				if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) {
1722 1696
 					$daystart = $this->dayStartOf($this->action['datecompleted']);
1723
-				}
1724
-				else {
1697
+				} else {
1725 1698
 					$daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
1726 1699
 				}
1727 1700
 
@@ -1729,8 +1702,7 @@  discard block
 block discarded – undo
1729 1702
 				// or the end of the recurrence, whichever comes first
1730 1703
 				if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1731 1704
 					$rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1732
-				}
1733
-				else {
1705
+				} else {
1734 1706
 					$rangeend = $end;
1735 1707
 				}
1736 1708
 
@@ -1750,8 +1722,7 @@  discard block
 block discarded – undo
1750 1722
 						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1751 1723
 							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1752 1724
 						}
1753
-					}
1754
-					else {
1725
+					} else {
1755 1726
 						// Every workday
1756 1727
 						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1757 1728
 							$nowtime = $this->gmtime($now);
@@ -1777,8 +1748,7 @@  discard block
 block discarded – undo
1777 1748
 					for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1778 1749
 						if ($this->recur['regen']) {
1779 1750
 							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1780
-						}
1781
-						else {
1751
+						} else {
1782 1752
 							// Loop through the whole following week to the first occurrence of the week, add each day that is specified
1783 1753
 							for ($wday = 0; $wday < 7; ++$wday) {
1784 1754
 								$daynow = $now + $wday * 60 * 60 * 24;
@@ -1813,8 +1783,7 @@  discard block
 block discarded – undo
1813 1783
 							if ($daynow <= $dayend) {
1814 1784
 								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1815 1785
 							}
1816
-						}
1817
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1786
+						} elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1818 1787
 							// Sanitize input
1819 1788
 							if ($this->recur["weekdays"] == 0) {
1820 1789
 								$this->recur["weekdays"] = 1;
@@ -1841,8 +1810,7 @@  discard block
 block discarded – undo
1841 1810
 								}
1842 1811
 								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1843 1812
 								$daynow = $now + $firstday * 60 * 60 * 24;
1844
-							}
1845
-							else {
1813
+							} else {
1846 1814
 								// Find last day in the month ($now is the firstday of the month)
1847 1815
 								$NumDaysInMonth = $this->daysInMonth($now, 1);
1848 1816
 								$daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
@@ -1860,8 +1828,7 @@  discard block
 block discarded – undo
1860 1828
 							if ($daynow <= $dayend && $daynow >= $daystart) {
1861 1829
 								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1862 1830
 							}
1863
-						}
1864
-						elseif ($this->recur['regen']) {
1831
+						} elseif ($this->recur['regen']) {
1865 1832
 							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1866 1833
 							$now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1867 1834
 
@@ -1890,8 +1857,7 @@  discard block
 block discarded – undo
1890 1857
 							}    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
1891 1858
 							$daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1892 1859
 							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1893
-						}
1894
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1860
+						} elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1895 1861
 							// Go the correct month
1896 1862
 							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1897 1863
 
@@ -1915,8 +1881,7 @@  discard block
 block discarded – undo
1915 1881
 							}
1916 1882
 
1917 1883
 							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1918
-						}
1919
-						elseif ($this->recur['regen']) {
1884
+						} elseif ($this->recur['regen']) {
1920 1885
 							$year_starttime = $this->gmtime($now);
1921 1886
 							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
1922 1887
 							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
Please login to merge, or discard this patch.
mapi/class.baseexception.php 2 patches
Indentation   +125 added lines, -125 removed lines patch added patch discarded remove patch
@@ -17,129 +17,129 @@
 block discarded – undo
17 17
  * getTraceAsString() - formated string of trace
18 18
  */
19 19
 class BaseException extends Exception {
20
-	/**
21
-	 * Base name of the file, so we don't have to use static path of the file.
22
-	 */
23
-	private $baseFile;
24
-
25
-	/**
26
-	 * Flag to check if exception is already handled or not.
27
-	 */
28
-	public $isHandled = false;
29
-
30
-	/**
31
-	 * The exception message to show at client side.
32
-	 */
33
-	public $displayMessage;
34
-
35
-	/**
36
-	 * Flag for allow to exception details message or not.
37
-	 */
38
-	public $allowToShowDetailsMessage = false;
39
-
40
-	/**
41
-	 * The exception title to show as a message box title at client side.
42
-	 */
43
-	public $title;
44
-
45
-	/**
46
-	 * Construct the exception.
47
-	 *
48
-	 * @param string    $errorMessage
49
-	 * @param int       $code
50
-	 * @param Exception $previous
51
-	 * @param string    $displayMessage
52
-	 */
53
-	public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) {
54
-		// assign display message
55
-		$this->displayMessage = $displayMessage;
56
-
57
-		parent::__construct($errorMessage, (int) $code, $previous);
58
-	}
59
-
60
-	/**
61
-	 * @return string returns file name and line number combined where exception occurred
62
-	 */
63
-	public function getFileLine() {
64
-		return $this->getBaseFile() . ':' . $this->getLine();
65
-	}
66
-
67
-	/**
68
-	 * @return string returns message that should be sent to client to display
69
-	 */
70
-	public function getDisplayMessage() {
71
-		if (!is_null($this->displayMessage)) {
72
-			return $this->displayMessage;
73
-		}
74
-
75
-		return $this->getMessage();
76
-	}
77
-
78
-	/**
79
-	 * Function sets display message of an exception that will be sent to the client side
80
-	 * to show it to user.
81
-	 *
82
-	 * @param string $message display message
83
-	 */
84
-	public function setDisplayMessage($message) {
85
-		$this->displayMessage = $message;
86
-	}
87
-
88
-	/**
89
-	 * Function sets title of an exception that will be sent to the client side
90
-	 * to show it to user.
91
-	 *
92
-	 * @param string $title title of an exception
93
-	 */
94
-	public function setTitle($title) {
95
-		$this->title = $title;
96
-	}
97
-
98
-	/**
99
-	 * @return string returns title that should be sent to client to display as a message box
100
-	 *                title
101
-	 */
102
-	public function getTitle() {
103
-		return $this->title;
104
-	}
105
-
106
-	/**
107
-	 * Function sets a flag in exception class to indicate that exception is already handled
108
-	 * so if it is caught again in the top level of function stack then we have to silently
109
-	 * ignore it.
110
-	 */
111
-	public function setHandled() {
112
-		$this->isHandled = true;
113
-	}
114
-
115
-	/**
116
-	 * @return string returns base path of the file where exception occurred
117
-	 */
118
-	public function getBaseFile() {
119
-		if (is_null($this->baseFile)) {
120
-			$this->baseFile = basename(parent::getFile());
121
-		}
122
-
123
-		return $this->baseFile;
124
-	}
125
-
126
-	/**
127
-	 * Name of the class of exception.
128
-	 *
129
-	 * @return string
130
-	 */
131
-	public function getName() {
132
-		return get_class($this);
133
-	}
134
-
135
-	/**
136
-	 * It will return details error message if allowToShowDetailsMessage is set.
137
-	 *
138
-	 * @return string returns details error message
139
-	 */
140
-	public function getDetailsMessage() {
141
-		return $this->allowToShowDetailsMessage ? $this->__toString() : '';
142
-	}
143
-
144
-	// @TODO getTrace and getTraceAsString
20
+    /**
21
+     * Base name of the file, so we don't have to use static path of the file.
22
+     */
23
+    private $baseFile;
24
+
25
+    /**
26
+     * Flag to check if exception is already handled or not.
27
+     */
28
+    public $isHandled = false;
29
+
30
+    /**
31
+     * The exception message to show at client side.
32
+     */
33
+    public $displayMessage;
34
+
35
+    /**
36
+     * Flag for allow to exception details message or not.
37
+     */
38
+    public $allowToShowDetailsMessage = false;
39
+
40
+    /**
41
+     * The exception title to show as a message box title at client side.
42
+     */
43
+    public $title;
44
+
45
+    /**
46
+     * Construct the exception.
47
+     *
48
+     * @param string    $errorMessage
49
+     * @param int       $code
50
+     * @param Exception $previous
51
+     * @param string    $displayMessage
52
+     */
53
+    public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) {
54
+        // assign display message
55
+        $this->displayMessage = $displayMessage;
56
+
57
+        parent::__construct($errorMessage, (int) $code, $previous);
58
+    }
59
+
60
+    /**
61
+     * @return string returns file name and line number combined where exception occurred
62
+     */
63
+    public function getFileLine() {
64
+        return $this->getBaseFile() . ':' . $this->getLine();
65
+    }
66
+
67
+    /**
68
+     * @return string returns message that should be sent to client to display
69
+     */
70
+    public function getDisplayMessage() {
71
+        if (!is_null($this->displayMessage)) {
72
+            return $this->displayMessage;
73
+        }
74
+
75
+        return $this->getMessage();
76
+    }
77
+
78
+    /**
79
+     * Function sets display message of an exception that will be sent to the client side
80
+     * to show it to user.
81
+     *
82
+     * @param string $message display message
83
+     */
84
+    public function setDisplayMessage($message) {
85
+        $this->displayMessage = $message;
86
+    }
87
+
88
+    /**
89
+     * Function sets title of an exception that will be sent to the client side
90
+     * to show it to user.
91
+     *
92
+     * @param string $title title of an exception
93
+     */
94
+    public function setTitle($title) {
95
+        $this->title = $title;
96
+    }
97
+
98
+    /**
99
+     * @return string returns title that should be sent to client to display as a message box
100
+     *                title
101
+     */
102
+    public function getTitle() {
103
+        return $this->title;
104
+    }
105
+
106
+    /**
107
+     * Function sets a flag in exception class to indicate that exception is already handled
108
+     * so if it is caught again in the top level of function stack then we have to silently
109
+     * ignore it.
110
+     */
111
+    public function setHandled() {
112
+        $this->isHandled = true;
113
+    }
114
+
115
+    /**
116
+     * @return string returns base path of the file where exception occurred
117
+     */
118
+    public function getBaseFile() {
119
+        if (is_null($this->baseFile)) {
120
+            $this->baseFile = basename(parent::getFile());
121
+        }
122
+
123
+        return $this->baseFile;
124
+    }
125
+
126
+    /**
127
+     * Name of the class of exception.
128
+     *
129
+     * @return string
130
+     */
131
+    public function getName() {
132
+        return get_class($this);
133
+    }
134
+
135
+    /**
136
+     * It will return details error message if allowToShowDetailsMessage is set.
137
+     *
138
+     * @return string returns details error message
139
+     */
140
+    public function getDetailsMessage() {
141
+        return $this->allowToShowDetailsMessage ? $this->__toString() : '';
142
+    }
143
+
144
+    // @TODO getTrace and getTraceAsString
145 145
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -54,14 +54,14 @@
 block discarded – undo
54 54
 		// assign display message
55 55
 		$this->displayMessage = $displayMessage;
56 56
 
57
-		parent::__construct($errorMessage, (int) $code, $previous);
57
+		parent::__construct($errorMessage, (int)$code, $previous);
58 58
 	}
59 59
 
60 60
 	/**
61 61
 	 * @return string returns file name and line number combined where exception occurred
62 62
 	 */
63 63
 	public function getFileLine() {
64
-		return $this->getBaseFile() . ':' . $this->getLine();
64
+		return $this->getBaseFile().':'.$this->getLine();
65 65
 	}
66 66
 
67 67
 	/**
Please login to merge, or discard this patch.