Test Setup Failed
Branch master (463395)
by Mike
02:40
created
mapi/mapitags.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -903,5 +903,5 @@
 block discarded – undo
903 903
 // ElementIDs for persist data of PR_IPM_OL2007_ENTRYIDS / PR_ADDITIONAL_REN_ENTRYIDS_EX
904 904
 define('RSF_ELID_HEADER', 0x0002); // 4 bytes Indicates that the ElementData field contains a 4-byte header value equal to 0x00000000.
905 905
 define('RSF_ELID_ENTRYID', 0x0001); // variable Indicates that the ElementData field contains the entry ID of the special folder
906
-																// that is of the type indicated by the value of the PersistID field of the PersistData structure.
906
+                                                                // that is of the type indicated by the value of the PersistID field of the PersistData structure.
907 907
 define('ELEMENT_SENTINEL', 0x0000); // 0 bytes Indicates that the PersistElement structure is the last one contained in the DataElements field of the PersistData structure.
Please login to merge, or discard this patch.
mapi/class.taskrequest.php 4 patches
Indentation   +851 added lines, -851 removed lines patch added patch discarded remove patch
@@ -5,7 +5,7 @@  discard block
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020 grommunio GmbH
6 6
  */
7 7
 
8
-	/*
8
+    /*
9 9
 	* In general
10 10
 	*
11 11
 	* This class never actually modifies a task item unless we receive a task request update. This means
@@ -17,63 +17,63 @@  discard block
 block discarded – undo
17 17
 	* task request is sent via sendTaskRequest.
18 18
 	*/
19 19
 
20
-	/* The TaskMode value is only used for the IPM.TaskRequest items. It must 0 (tdmtNothing) on IPM.Task items.
20
+    /* The TaskMode value is only used for the IPM.TaskRequest items. It must 0 (tdmtNothing) on IPM.Task items.
21 21
 	*
22 22
 	* It is used to indicate the type of change that is being carried in the IPM.TaskRequest item (although this
23 23
 	* information seems redundant due to that information already being available in PR_MESSAGE_CLASS).
24 24
 	*/
25
-	define('tdmtNothing', 0);			// Value in IPM.Task items
26
-	define('tdmtTaskReq', 1);			// Assigner -> Assignee
27
-	define('tdmtTaskAcc', 2);			// Assignee -> Assigner
28
-	define('tdmtTaskDec', 3);			// Assignee -> Assigner
29
-	define('tdmtTaskUpd', 4);			// Assignee -> Assigner
30
-	define('tdmtTaskSELF', 5);			// Assigner -> Assigner (?)
31
-
32
-	/* The TaskHistory is used to show the last action on the task on both the assigner and the assignee's side.
25
+    define('tdmtNothing', 0);			// Value in IPM.Task items
26
+    define('tdmtTaskReq', 1);			// Assigner -> Assignee
27
+    define('tdmtTaskAcc', 2);			// Assignee -> Assigner
28
+    define('tdmtTaskDec', 3);			// Assignee -> Assigner
29
+    define('tdmtTaskUpd', 4);			// Assignee -> Assigner
30
+    define('tdmtTaskSELF', 5);			// Assigner -> Assigner (?)
31
+
32
+    /* The TaskHistory is used to show the last action on the task on both the assigner and the assignee's side.
33 33
 	*
34 34
 	* It is used in combination with 'AssignedTime' and 'tasklastdelegate' or 'tasklastuser' to show the information
35 35
 	* at the top of the task request in the format 'Accepted by <user> on 01-01-2010 11:00'.
36 36
 	*/
37
-	define('thNone', 0);
38
-	define('thAccepted', 1);			// Set by assignee
39
-	define('thDeclined', 2);			// Set by assignee
40
-	define('thUpdated', 3);				// Set by assignee
41
-	define('thDueDateChanged', 4);
42
-	define('thAssigned', 5);			// Set by assigner
43
-
44
-	/* The TaskState value is used to differentiate the version of a task in the assigner's folder and the version in the
37
+    define('thNone', 0);
38
+    define('thAccepted', 1);			// Set by assignee
39
+    define('thDeclined', 2);			// Set by assignee
40
+    define('thUpdated', 3);				// Set by assignee
41
+    define('thDueDateChanged', 4);
42
+    define('thAssigned', 5);			// Set by assigner
43
+
44
+    /* The TaskState value is used to differentiate the version of a task in the assigner's folder and the version in the
45 45
 	* assignee's folder. The buttons shown depend on this and the 'taskaccepted' boolean (for the assignee)
46 46
 	*/
47
-	define('tdsNOM', 0);		// Got a response to a deleted task, and re-created the task for the assigner
48
-	define('tdsOWNNEW', 1);		// Not assigned
49
-	define('tdsOWN', 2);		// Assignee version
50
-	define('tdsACC', 3);		// Assigner version
51
-	define('tdsDEC', 4);		// Assigner version, but assignee declined
47
+    define('tdsNOM', 0);		// Got a response to a deleted task, and re-created the task for the assigner
48
+    define('tdsOWNNEW', 1);		// Not assigned
49
+    define('tdsOWN', 2);		// Assignee version
50
+    define('tdsACC', 3);		// Assigner version
51
+    define('tdsDEC', 4);		// Assigner version, but assignee declined
52 52
 
53
-	/* The delegationstate is used for the assigner to indicate state
53
+    /* The delegationstate is used for the assigner to indicate state
54 54
 	*/
55
-	define('olTaskNotDelegated', 0);
56
-	define('olTaskDelegationUnknown', 1); // After sending req
57
-	define('olTaskDelegationAccepted', 2); // After receiving accept
58
-	define('olTaskDelegationDeclined', 3); // After receiving decline
55
+    define('olTaskNotDelegated', 0);
56
+    define('olTaskDelegationUnknown', 1); // After sending req
57
+    define('olTaskDelegationAccepted', 2); // After receiving accept
58
+    define('olTaskDelegationDeclined', 3); // After receiving decline
59 59
 
60
-	/* The task ownership indicates the role of the current user relative to the task.
60
+    /* The task ownership indicates the role of the current user relative to the task.
61 61
 	*/
62
-	define('olNewTask', 0);
63
-	define('olDelegatedTask', 1);	// Task has been assigned
64
-	define('olOwnTask', 2);			// Task owned
62
+    define('olNewTask', 0);
63
+    define('olDelegatedTask', 1);	// Task has been assigned
64
+    define('olOwnTask', 2);			// Task owned
65 65
 
66
-	/* taskmultrecips indicates whether the task request sent or received has multiple assignees or not.
66
+    /* taskmultrecips indicates whether the task request sent or received has multiple assignees or not.
67 67
 	*/
68
-	define('tmrNone', 0);
69
-	define('tmrSent', 1);		// Task has been sent to multiple assignee
70
-	define('tmrReceived', 2);	// Task Request received has multiple assignee
68
+    define('tmrNone', 0);
69
+    define('tmrSent', 1);		// Task has been sent to multiple assignee
70
+    define('tmrReceived', 2);	// Task Request received has multiple assignee
71 71
 
72
-	class TaskRequest {
73
-		// All recipient properties
74
-		public $recipprops = [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, PR_SEARCH_KEY];
72
+    class TaskRequest {
73
+        // All recipient properties
74
+        public $recipprops = [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, PR_SEARCH_KEY];
75 75
 
76
-		/*
76
+        /*
77 77
 		 * Constructs a TaskRequest object for the specified message. This can be either the task request
78 78
 		 * message itself (in the inbox) or the task in the tasks folder, depending on the action to be performed.
79 79
 		 *
@@ -84,307 +84,307 @@  discard block
 block discarded – undo
84 84
 		 * @param $message message MAPI Message to which the task request refers to (can be an email or a task)
85 85
 		 * @param $session session MAPI Session which is used to open tasks folders for delegated task requests or responses
86 86
 		 */
87
-		public function __construct($store, $message, $session) {
88
-			$this->store = $store;
89
-			$this->message = $message;
90
-			$this->session = $session;
91
-
92
-			$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
93
-			$properties["updatecount"] = "PT_LONG:PSETID_Task:0x8112";
94
-			$properties["taskstate"] = "PT_LONG:PSETID_Task:0x8113";
95
-			$properties["taskmultrecips"] = "PT_LONG:PSETID_Task:0x8120";
96
-			$properties["taskupdates"] = "PT_BOOLEAN:PSETID_Task:0x811b";
97
-			$properties["tasksoc"] = "PT_BOOLEAN:PSETID_Task:0x8119";
98
-			$properties["taskhistory"] = "PT_LONG:PSETID_Task:0x811a";
99
-			$properties["taskmode"] = "PT_LONG:PSETID_Common:0x8518";
100
-			$properties["taskglobalobjid"] = "PT_BINARY:PSETID_Common:0x8519";
101
-			$properties["complete"] = "PT_BOOLEAN:PSETID_Common:0x811c";
102
-			$properties["assignedtime"] = "PT_SYSTIME:PSETID_Task:0x8115";
103
-			$properties["taskfcreator"] = "PT_BOOLEAN:PSETID_Task:0x0x811e";
104
-			$properties["tasklastuser"] = "PT_STRING8:PSETID_Task:0x8122";
105
-			$properties["tasklastdelegate"] = "PT_STRING8:PSETID_Task:0x8125";
106
-			$properties["taskaccepted"] = "PT_BOOLEAN:PSETID_Task:0x8108";
107
-			$properties["delegationstate"] = "PT_LONG:PSETID_Task:0x812a";
108
-			$properties["ownership"] = "PT_LONG:PSETID_Task:0x8129";
109
-
110
-			$properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
111
-			$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
112
-			$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
113
-			$properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
114
-			$properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
115
-			$properties["status"] = "PT_LONG:PSETID_Task:0x8101";
116
-			$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
117
-			$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
118
-			$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
119
-			$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
120
-			$properties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
121
-			$properties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
122
-			$properties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
123
-
124
-			$this->props = getPropIdsFromStrings($store, $properties);
125
-		}
126
-
127
-		// General functions
128
-
129
-		/* Return TRUE if the item is a task request message
87
+        public function __construct($store, $message, $session) {
88
+            $this->store = $store;
89
+            $this->message = $message;
90
+            $this->session = $session;
91
+
92
+            $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
93
+            $properties["updatecount"] = "PT_LONG:PSETID_Task:0x8112";
94
+            $properties["taskstate"] = "PT_LONG:PSETID_Task:0x8113";
95
+            $properties["taskmultrecips"] = "PT_LONG:PSETID_Task:0x8120";
96
+            $properties["taskupdates"] = "PT_BOOLEAN:PSETID_Task:0x811b";
97
+            $properties["tasksoc"] = "PT_BOOLEAN:PSETID_Task:0x8119";
98
+            $properties["taskhistory"] = "PT_LONG:PSETID_Task:0x811a";
99
+            $properties["taskmode"] = "PT_LONG:PSETID_Common:0x8518";
100
+            $properties["taskglobalobjid"] = "PT_BINARY:PSETID_Common:0x8519";
101
+            $properties["complete"] = "PT_BOOLEAN:PSETID_Common:0x811c";
102
+            $properties["assignedtime"] = "PT_SYSTIME:PSETID_Task:0x8115";
103
+            $properties["taskfcreator"] = "PT_BOOLEAN:PSETID_Task:0x0x811e";
104
+            $properties["tasklastuser"] = "PT_STRING8:PSETID_Task:0x8122";
105
+            $properties["tasklastdelegate"] = "PT_STRING8:PSETID_Task:0x8125";
106
+            $properties["taskaccepted"] = "PT_BOOLEAN:PSETID_Task:0x8108";
107
+            $properties["delegationstate"] = "PT_LONG:PSETID_Task:0x812a";
108
+            $properties["ownership"] = "PT_LONG:PSETID_Task:0x8129";
109
+
110
+            $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
111
+            $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
112
+            $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
113
+            $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
114
+            $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
115
+            $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
116
+            $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
117
+            $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
118
+            $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
119
+            $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
120
+            $properties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
121
+            $properties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
122
+            $properties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
123
+
124
+            $this->props = getPropIdsFromStrings($store, $properties);
125
+        }
126
+
127
+        // General functions
128
+
129
+        /* Return TRUE if the item is a task request message
130 130
 		 */
131
-		public function isTaskRequest() {
132
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
133
-			if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.TaskRequest") {
134
-				return true;
135
-			}
136
-		}
137
-
138
-		/* Return TRUE if the item is a task response message
131
+        public function isTaskRequest() {
132
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
133
+            if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.TaskRequest") {
134
+                return true;
135
+            }
136
+        }
137
+
138
+        /* Return TRUE if the item is a task response message
139 139
 		 */
140
-		public function isTaskRequestResponse() {
141
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
142
-			if (isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.TaskRequest.") === 0) {
143
-				return true;
144
-			}
145
-		}
146
-
147
-		/*
140
+        public function isTaskRequestResponse() {
141
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
142
+            if (isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.TaskRequest.") === 0) {
143
+                return true;
144
+            }
145
+        }
146
+
147
+        /*
148 148
 		 * Gets the task associated with an IPM.TaskRequest message
149 149
 		 *
150 150
 		 * If the task does not exist yet, it is created, using the attachment object in the
151 151
 		 * task request item.
152 152
 		 */
153
-		public function getAssociatedTask($create) {
154
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['taskglobalobjid']]);
153
+        public function getAssociatedTask($create) {
154
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->props['taskglobalobjid']]);
155 155
 
156
-			if ($props[PR_MESSAGE_CLASS] == "IPM.Task") {
157
-				return $this->message;
158
-			} // Message itself is task, so return that
156
+            if ($props[PR_MESSAGE_CLASS] == "IPM.Task") {
157
+                return $this->message;
158
+            } // Message itself is task, so return that
159 159
 
160
-			$tfolder = $this->getDefaultTasksFolder();
161
-			$globalobjid = $props[$this->props['taskglobalobjid']];
160
+            $tfolder = $this->getDefaultTasksFolder();
161
+            $globalobjid = $props[$this->props['taskglobalobjid']];
162 162
 
163
-			// Find the task by looking for the taskglobalobjid
164
-			$restriction = [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid]];
165
-			$contents = mapi_folder_getcontentstable($tfolder);
166
-			$rows = mapi_table_queryallrows($contents, [PR_ENTRYID], $restriction);
167
-			if (!empty($rows)) {
168
-				// If there are multiple, just use the first
169
-				$entryid = $rows[0][PR_ENTRYID];
163
+            // Find the task by looking for the taskglobalobjid
164
+            $restriction = [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid]];
165
+            $contents = mapi_folder_getcontentstable($tfolder);
166
+            $rows = mapi_table_queryallrows($contents, [PR_ENTRYID], $restriction);
167
+            if (!empty($rows)) {
168
+                // If there are multiple, just use the first
169
+                $entryid = $rows[0][PR_ENTRYID];
170 170
 
171
-				$store = $this->getTaskFolderStore();
171
+                $store = $this->getTaskFolderStore();
172 172
 
173
-				return mapi_msgstore_openentry($store, $entryid);
174
-			}
175
-			// None found, create one if possible
176
-			if (!$create) {
177
-				return false;
178
-			}
173
+                return mapi_msgstore_openentry($store, $entryid);
174
+            }
175
+            // None found, create one if possible
176
+            if (!$create) {
177
+                return false;
178
+            }
179 179
 
180
-			$task = mapi_folder_createmessage($tfolder);
180
+            $task = mapi_folder_createmessage($tfolder);
181 181
 
182
-			$sub = $this->getEmbeddedTask($this->message);
183
-			mapi_copyto($sub, [], [], $task);
182
+            $sub = $this->getEmbeddedTask($this->message);
183
+            mapi_copyto($sub, [], [], $task);
184 184
 
185
-			// Copy sender information from the e-mail
186
-			$senderprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
187
-			mapi_setprops($task, $senderprops);
185
+            // Copy sender information from the e-mail
186
+            $senderprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
187
+            mapi_setprops($task, $senderprops);
188 188
 
189
-			$senderprops = mapi_getprops($this->message, [PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENDER_ENTRYID, PR_SENDER_ADDRTYPE, PR_SENDER_SEARCH_KEY]);
190
-			mapi_setprops($task, $senderprops);
189
+            $senderprops = mapi_getprops($this->message, [PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENDER_ENTRYID, PR_SENDER_ADDRTYPE, PR_SENDER_SEARCH_KEY]);
190
+            mapi_setprops($task, $senderprops);
191 191
 
192
-			return $task;
193
-		}
192
+            return $task;
193
+        }
194 194
 
195
-		// Organizer functions (called by the organizer)
195
+        // Organizer functions (called by the organizer)
196 196
 
197
-		/* Processes a task request response, which can be any of the following:
197
+        /* Processes a task request response, which can be any of the following:
198 198
 		 * - Task accept (task history is marked as accepted)
199 199
 		 * - Task decline (task history is marked as declined)
200 200
 		 * - Task update (updates completion %, etc)
201 201
 		 */
202
-		public function processTaskResponse() {
203
-			$messageprops = mapi_getprops($this->message, [PR_PROCESSED]);
204
-			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED]) {
205
-				return true;
206
-			}
207
-
208
-			// Get the task for this response
209
-			$task = $this->getAssociatedTask(false);
210
-
211
-			if (!$task) {
212
-				// Got a response for a task that has been deleted, create a new one and mark it as such
213
-				$task = $this->getAssociatedTask(true);
214
-
215
-				// tdsNOM indicates a task request that had gone missing
216
-				mapi_setprops($task, [$this->props['taskstate'] => tdsNOM]);
217
-			}
218
-
219
-			// Get the embedded task information and copy it into our task
220
-			$sub = $this->getEmbeddedTask($this->message);
221
-			mapi_copyto($sub, [], [$this->props['taskstate'], $this->props['taskhistory'], $this->props['taskmode'], $this->props['taskfcreator']], $task);
222
-
223
-			$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
224
-
225
-			// Set correct taskmode and taskhistory depending on response type
226
-			switch ($props[PR_MESSAGE_CLASS]) {
227
-				case 'IPM.TaskRequest.Accept':
228
-					$taskhistory = thAccepted;
229
-					$taskstate = tdsACC;
230
-					$delegationstate = olTaskDelegationAccepted;
231
-					break;
232
-
233
-				case 'IPM.TaskRequest.Decline':
234
-					$taskhistory = thDeclined;
235
-					$taskstate = tdsDEC;
236
-					$delegationstate = olTaskDelegationDeclined;
237
-					break;
238
-
239
-				case 'IPM.TaskRequest.Update':
240
-					$taskhistory = thUpdated;
241
-					$taskstate = tdsACC; // Doesn't actually change anything
242
-					$delegationstate = olTaskDelegationAccepted;
243
-					break;
244
-			}
245
-
246
-			// Update taskstate (what the task looks like) and task history (last action done by the assignee)
247
-			mapi_setprops($task, [$this->props['taskhistory'] => $taskhistory, $this->props['taskstate'] => $taskstate, $this->props['delegationstate'] => $delegationstate, $this->props['ownership'] => olDelegatedTask]);
248
-
249
-			mapi_setprops($this->message, [PR_PROCESSED => true]);
250
-			mapi_savechanges($task);
251
-
252
-			return true;
253
-		}
254
-
255
-		/* Create a new message in the current user's outbox and submit it
202
+        public function processTaskResponse() {
203
+            $messageprops = mapi_getprops($this->message, [PR_PROCESSED]);
204
+            if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED]) {
205
+                return true;
206
+            }
207
+
208
+            // Get the task for this response
209
+            $task = $this->getAssociatedTask(false);
210
+
211
+            if (!$task) {
212
+                // Got a response for a task that has been deleted, create a new one and mark it as such
213
+                $task = $this->getAssociatedTask(true);
214
+
215
+                // tdsNOM indicates a task request that had gone missing
216
+                mapi_setprops($task, [$this->props['taskstate'] => tdsNOM]);
217
+            }
218
+
219
+            // Get the embedded task information and copy it into our task
220
+            $sub = $this->getEmbeddedTask($this->message);
221
+            mapi_copyto($sub, [], [$this->props['taskstate'], $this->props['taskhistory'], $this->props['taskmode'], $this->props['taskfcreator']], $task);
222
+
223
+            $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
224
+
225
+            // Set correct taskmode and taskhistory depending on response type
226
+            switch ($props[PR_MESSAGE_CLASS]) {
227
+                case 'IPM.TaskRequest.Accept':
228
+                    $taskhistory = thAccepted;
229
+                    $taskstate = tdsACC;
230
+                    $delegationstate = olTaskDelegationAccepted;
231
+                    break;
232
+
233
+                case 'IPM.TaskRequest.Decline':
234
+                    $taskhistory = thDeclined;
235
+                    $taskstate = tdsDEC;
236
+                    $delegationstate = olTaskDelegationDeclined;
237
+                    break;
238
+
239
+                case 'IPM.TaskRequest.Update':
240
+                    $taskhistory = thUpdated;
241
+                    $taskstate = tdsACC; // Doesn't actually change anything
242
+                    $delegationstate = olTaskDelegationAccepted;
243
+                    break;
244
+            }
245
+
246
+            // Update taskstate (what the task looks like) and task history (last action done by the assignee)
247
+            mapi_setprops($task, [$this->props['taskhistory'] => $taskhistory, $this->props['taskstate'] => $taskstate, $this->props['delegationstate'] => $delegationstate, $this->props['ownership'] => olDelegatedTask]);
248
+
249
+            mapi_setprops($this->message, [PR_PROCESSED => true]);
250
+            mapi_savechanges($task);
251
+
252
+            return true;
253
+        }
254
+
255
+        /* Create a new message in the current user's outbox and submit it
256 256
 		 *
257 257
 		 * Takes the task passed in the constructor as the task to be sent; recipient should
258 258
 		 * be pre-existing. The task request will be sent to all recipients.
259 259
 		 */
260
-		public function sendTaskRequest($prefix) {
261
-			// Generate a TaskGlobalObjectId
262
-			$taskid = $this->createTGOID();
263
-			$messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
264
-
265
-			// Set properties on Task Request
266
-			mapi_setprops($this->message, [
267
-				$this->props['taskglobalobjid'] => $taskid, /* our new taskglobalobjid */
268
-				$this->props['taskstate'] => tdsACC, 		/* state for our outgoing request */
269
-				$this->props['taskmode'] => tdmtNothing, 	/* we're not sending a change */
270
-				$this->props['updatecount'] => 2,			/* version 2 (no idea) */
271
-				$this->props['delegationstate'] => olTaskDelegationUnknown, /* no reply yet */
272
-				$this->props['ownership'] => olDelegatedTask, /* Task has been assigned */
273
-				$this->props['taskhistory'] => thAssigned,	/* Task has been assigned */
274
-				PR_ICON_INDEX => 1283,						/* Task request icon */
275
-			]);
276
-			$this->setLastUser();
277
-			$this->setOwnerForAssignor();
278
-			mapi_savechanges($this->message);
279
-
280
-			// Create outgoing task request message
281
-			$outgoing = $this->createOutgoingMessage();
282
-			// No need to copy attachments as task will be attached as embedded message.
283
-			mapi_copyto($this->message, [], [PR_MESSAGE_ATTACHMENTS], $outgoing);
284
-
285
-			// Make it a task request, and put it in sent items after it is sent
286
-			mapi_setprops($outgoing, [
287
-				PR_MESSAGE_CLASS => "IPM.TaskRequest", 		/* class is task request */
288
-				$this->props['taskstate'] => tdsOWNNEW, 	/* for the recipient the task is new */
289
-				$this->props['taskmode'] => tdmtTaskReq,	/* for the recipient, it is a request */
290
-				$this->props['updatecount'] => 1,			/* version 2 is in the attachment */
291
-				PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
292
-				PR_ICON_INDEX => 0xFFFFFFFF,				/* show assigned icon */
293
-			]);
294
-
295
-			// Set Body
296
-			$body = $this->getBody();
297
-			$stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
298
-			mapi_stream_setsize($stream, strlen($body));
299
-			mapi_stream_write($stream, $body);
300
-			mapi_stream_commit($stream);
301
-
302
-			$attach = mapi_message_createattach($outgoing);
303
-			mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT]]);
304
-
305
-			$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
306
-
307
-			mapi_copyto($this->message, [], [], $sub);
308
-			mapi_savechanges($sub);
309
-
310
-			mapi_savechanges($attach);
311
-
312
-			mapi_savechanges($outgoing);
313
-			mapi_message_submitmessage($outgoing);
314
-
315
-			return true;
316
-		}
317
-
318
-		// Assignee functions (called by the assignee)
319
-
320
-		/* Update task version counter
260
+        public function sendTaskRequest($prefix) {
261
+            // Generate a TaskGlobalObjectId
262
+            $taskid = $this->createTGOID();
263
+            $messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
264
+
265
+            // Set properties on Task Request
266
+            mapi_setprops($this->message, [
267
+                $this->props['taskglobalobjid'] => $taskid, /* our new taskglobalobjid */
268
+                $this->props['taskstate'] => tdsACC, 		/* state for our outgoing request */
269
+                $this->props['taskmode'] => tdmtNothing, 	/* we're not sending a change */
270
+                $this->props['updatecount'] => 2,			/* version 2 (no idea) */
271
+                $this->props['delegationstate'] => olTaskDelegationUnknown, /* no reply yet */
272
+                $this->props['ownership'] => olDelegatedTask, /* Task has been assigned */
273
+                $this->props['taskhistory'] => thAssigned,	/* Task has been assigned */
274
+                PR_ICON_INDEX => 1283,						/* Task request icon */
275
+            ]);
276
+            $this->setLastUser();
277
+            $this->setOwnerForAssignor();
278
+            mapi_savechanges($this->message);
279
+
280
+            // Create outgoing task request message
281
+            $outgoing = $this->createOutgoingMessage();
282
+            // No need to copy attachments as task will be attached as embedded message.
283
+            mapi_copyto($this->message, [], [PR_MESSAGE_ATTACHMENTS], $outgoing);
284
+
285
+            // Make it a task request, and put it in sent items after it is sent
286
+            mapi_setprops($outgoing, [
287
+                PR_MESSAGE_CLASS => "IPM.TaskRequest", 		/* class is task request */
288
+                $this->props['taskstate'] => tdsOWNNEW, 	/* for the recipient the task is new */
289
+                $this->props['taskmode'] => tdmtTaskReq,	/* for the recipient, it is a request */
290
+                $this->props['updatecount'] => 1,			/* version 2 is in the attachment */
291
+                PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
292
+                PR_ICON_INDEX => 0xFFFFFFFF,				/* show assigned icon */
293
+            ]);
294
+
295
+            // Set Body
296
+            $body = $this->getBody();
297
+            $stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
298
+            mapi_stream_setsize($stream, strlen($body));
299
+            mapi_stream_write($stream, $body);
300
+            mapi_stream_commit($stream);
301
+
302
+            $attach = mapi_message_createattach($outgoing);
303
+            mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT]]);
304
+
305
+            $sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
306
+
307
+            mapi_copyto($this->message, [], [], $sub);
308
+            mapi_savechanges($sub);
309
+
310
+            mapi_savechanges($attach);
311
+
312
+            mapi_savechanges($outgoing);
313
+            mapi_message_submitmessage($outgoing);
314
+
315
+            return true;
316
+        }
317
+
318
+        // Assignee functions (called by the assignee)
319
+
320
+        /* Update task version counter
321 321
 		 *
322 322
 		 * Must be called before each update to increase counter
323 323
 		 */
324
-		public function updateTaskRequest() {
325
-			$messageprops = mapi_getprops($this->message, [$this->props['updatecount']]);
326
-			if (isset($messageprops)) {
327
-				++$messageprops[$this->props['updatecount']];
328
-			}
329
-			else {
330
-				$messageprops[$this->props['updatecount']] = 1;
331
-			}
332
-
333
-			mapi_setprops($this->message, $messageprops);
334
-		}
335
-
336
-		/* Process a task request
324
+        public function updateTaskRequest() {
325
+            $messageprops = mapi_getprops($this->message, [$this->props['updatecount']]);
326
+            if (isset($messageprops)) {
327
+                ++$messageprops[$this->props['updatecount']];
328
+            }
329
+            else {
330
+                $messageprops[$this->props['updatecount']] = 1;
331
+            }
332
+
333
+            mapi_setprops($this->message, $messageprops);
334
+        }
335
+
336
+        /* Process a task request
337 337
 		 *
338 338
 		 * Message passed should be an IPM.TaskRequest message. The task request is then processed to create
339 339
 		 * the task in the tasks folder if needed.
340 340
 		 */
341
-		public function processTaskRequest() {
342
-			if (!$this->isTaskRequest()) {
343
-				return false;
344
-			}
341
+        public function processTaskRequest() {
342
+            if (!$this->isTaskRequest()) {
343
+                return false;
344
+            }
345 345
 
346
-			$messageprops = mapi_getprops($this->message, [PR_PROCESSED]);
347
-			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED]) {
348
-				return true;
349
-			}
346
+            $messageprops = mapi_getprops($this->message, [PR_PROCESSED]);
347
+            if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED]) {
348
+                return true;
349
+            }
350 350
 
351
-			$task = $this->getAssociatedTask(true);
352
-			$taskProps = mapi_getprops($task, [$this->props['taskmultrecips']]);
351
+            $task = $this->getAssociatedTask(true);
352
+            $taskProps = mapi_getprops($task, [$this->props['taskmultrecips']]);
353 353
 
354
-			// Set the task state to say that we're the attendee receiving the message, that we have not yet responded and that this message represents no change
355
-			$taskProps[$this->props["taskstate"]] = tdsOWN;
356
-			$taskProps[$this->props["taskhistory"]] = thAssigned;
357
-			$taskProps[$this->props["taskmode"]] = tdmtNothing;
358
-			$taskProps[$this->props["taskaccepted"]] = false;
359
-			$taskProps[$this->props["taskfcreator"]] = false;
360
-			$taskProps[$this->props["ownership"]] = olOwnTask;
361
-			$taskProps[$this->props["delegationstate"]] = olTaskNotDelegated;
362
-			$taskProps[PR_ICON_INDEX] = 1282;
354
+            // Set the task state to say that we're the attendee receiving the message, that we have not yet responded and that this message represents no change
355
+            $taskProps[$this->props["taskstate"]] = tdsOWN;
356
+            $taskProps[$this->props["taskhistory"]] = thAssigned;
357
+            $taskProps[$this->props["taskmode"]] = tdmtNothing;
358
+            $taskProps[$this->props["taskaccepted"]] = false;
359
+            $taskProps[$this->props["taskfcreator"]] = false;
360
+            $taskProps[$this->props["ownership"]] = olOwnTask;
361
+            $taskProps[$this->props["delegationstate"]] = olTaskNotDelegated;
362
+            $taskProps[PR_ICON_INDEX] = 1282;
363 363
 
364
-			// This task was assigned to multiple recips, so set this user as owner
365
-			if (isset($taskProps[$this->props['taskmultrecips']]) && $taskProps[$this->props['taskmultrecips']] == tmrSent) {
366
-				$loginUserData = $this->retrieveUserData();
364
+            // This task was assigned to multiple recips, so set this user as owner
365
+            if (isset($taskProps[$this->props['taskmultrecips']]) && $taskProps[$this->props['taskmultrecips']] == tmrSent) {
366
+                $loginUserData = $this->retrieveUserData();
367 367
 
368
-				if ($loginUserData) {
369
-					$taskProps[$this->props['owner']] = $loginUserData[PR_DISPLAY_NAME];
370
-					$taskProps[$this->props['taskmultrecips']] = tmrReceived;
371
-				}
372
-			}
373
-			mapi_setprops($task, $taskProps);
368
+                if ($loginUserData) {
369
+                    $taskProps[$this->props['owner']] = $loginUserData[PR_DISPLAY_NAME];
370
+                    $taskProps[$this->props['taskmultrecips']] = tmrReceived;
371
+                }
372
+            }
373
+            mapi_setprops($task, $taskProps);
374 374
 
375
-			$this->setAssignorInRecipients($task);
375
+            $this->setAssignorInRecipients($task);
376 376
 
377
-			mapi_savechanges($task);
377
+            mapi_savechanges($task);
378 378
 
379
-			$taskprops = mapi_getprops($task, [PR_ENTRYID]);
379
+            $taskprops = mapi_getprops($task, [PR_ENTRYID]);
380 380
 
381
-			mapi_setprops($this->message, [PR_PROCESSED => true]);
382
-			mapi_savechanges($this->message);
381
+            mapi_setprops($this->message, [PR_PROCESSED => true]);
382
+            mapi_savechanges($this->message);
383 383
 
384
-			return $taskprops[PR_ENTRYID];
385
-		}
384
+            return $taskprops[PR_ENTRYID];
385
+        }
386 386
 
387
-		/* Accept a task request and send the response.
387
+        /* Accept a task request and send the response.
388 388
 		 *
389 389
 		 * Message passed should be an IPM.Task (eg the task from getAssociatedTask())
390 390
 		 *
@@ -393,28 +393,28 @@  discard block
 block discarded – undo
393 393
 		 *
394 394
 		 * @return entryid EntryID of the accepted task
395 395
 		 */
396
-		public function doAccept($prefix) {
397
-			$messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
396
+        public function doAccept($prefix) {
397
+            $messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
398 398
 
399
-			if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
400
-				return false;
401
-			} // Can only accept assignee task
399
+            if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
400
+                return false;
401
+            } // Can only accept assignee task
402 402
 
403
-			$this->setLastUser();
404
-			$this->updateTaskRequest();
403
+            $this->setLastUser();
404
+            $this->updateTaskRequest();
405 405
 
406
-			// Set as accepted
407
-			mapi_setprops($this->message, [$this->props['taskhistory'] => thAccepted, $this->props['assignedtime'] => time(), $this->props['taskaccepted'] => true,  $this->props['delegationstate'] => olTaskNotDelegated]);
406
+            // Set as accepted
407
+            mapi_setprops($this->message, [$this->props['taskhistory'] => thAccepted, $this->props['assignedtime'] => time(), $this->props['taskaccepted'] => true,  $this->props['delegationstate'] => olTaskNotDelegated]);
408 408
 
409
-			mapi_savechanges($this->message);
409
+            mapi_savechanges($this->message);
410 410
 
411
-			$this->sendResponse(tdmtTaskAcc, $prefix);
411
+            $this->sendResponse(tdmtTaskAcc, $prefix);
412 412
 
413
-			// @TODO: delete received task request from Inbox
414
-			return $this->deleteReceivedTR();
415
-		}
413
+            // @TODO: delete received task request from Inbox
414
+            return $this->deleteReceivedTR();
415
+        }
416 416
 
417
-		/* Decline a task request and send the response.
417
+        /* Decline a task request and send the response.
418 418
 		 *
419 419
 		 * Passed message must be a task request message, ie isTaskRequest() must return TRUE.
420 420
 		 *
@@ -422,590 +422,590 @@  discard block
 block discarded – undo
422 422
 		 *
423 423
 		 * @return boolean TRUE on success, FALSE on failure
424 424
 		 */
425
-		public function doDecline($prefix) {
426
-			$messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
425
+        public function doDecline($prefix) {
426
+            $messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
427 427
 
428
-			if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
429
-				return false;
430
-			} // Can only decline assignee task
428
+            if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
429
+                return false;
430
+            } // Can only decline assignee task
431 431
 
432
-			$this->setLastUser();
433
-			$this->updateTaskRequest();
432
+            $this->setLastUser();
433
+            $this->updateTaskRequest();
434 434
 
435
-			// Set as declined
436
-			mapi_setprops($this->message, [$this->props['taskhistory'] => thDeclined,  $this->props['delegationstate'] => olTaskDelegationDeclined]);
435
+            // Set as declined
436
+            mapi_setprops($this->message, [$this->props['taskhistory'] => thDeclined,  $this->props['delegationstate'] => olTaskDelegationDeclined]);
437 437
 
438
-			mapi_savechanges($this->message);
438
+            mapi_savechanges($this->message);
439 439
 
440
-			$this->sendResponse(tdmtTaskDec, $prefix);
440
+            $this->sendResponse(tdmtTaskDec, $prefix);
441 441
 
442
-			return $this->deleteReceivedTR();
443
-		}
442
+            return $this->deleteReceivedTR();
443
+        }
444 444
 
445
-		/* Send an update of the task if requested, and send the Status-On-Completion report if complete and requested
445
+        /* Send an update of the task if requested, and send the Status-On-Completion report if complete and requested
446 446
 		 *
447 447
 		 * If no updates were requested from the organizer, this function does nothing.
448 448
 		 *
449 449
 		 * @return boolean TRUE if the update succeeded, FALSE otherwise.
450 450
 		 */
451
-		public function doUpdate($prefix, $prefixComplete) {
452
-			$messageprops = mapi_getprops($this->message, [$this->props['taskstate'], PR_SUBJECT]);
451
+        public function doUpdate($prefix, $prefixComplete) {
452
+            $messageprops = mapi_getprops($this->message, [$this->props['taskstate'], PR_SUBJECT]);
453 453
 
454
-			if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
455
-				return false;
456
-			} // Can only update assignee task
454
+            if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
455
+                return false;
456
+            } // Can only update assignee task
457 457
 
458
-			$this->setLastUser();
459
-			$this->updateTaskRequest();
458
+            $this->setLastUser();
459
+            $this->updateTaskRequest();
460 460
 
461
-			// Set as updated
462
-			mapi_setprops($this->message, [$this->props['taskhistory'] => thUpdated]);
461
+            // Set as updated
462
+            mapi_setprops($this->message, [$this->props['taskhistory'] => thUpdated]);
463 463
 
464
-			mapi_savechanges($this->message);
464
+            mapi_savechanges($this->message);
465 465
 
466
-			$props = mapi_getprops($this->message, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['recurring'], $this->props['complete']]);
467
-			if ($props[$this->props['taskupdates']] && !(isset($props[$this->props['recurring']]) && $props[$this->props['recurring']])) {
468
-				$this->sendResponse(tdmtTaskUpd, $prefix);
469
-			}
466
+            $props = mapi_getprops($this->message, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['recurring'], $this->props['complete']]);
467
+            if ($props[$this->props['taskupdates']] && !(isset($props[$this->props['recurring']]) && $props[$this->props['recurring']])) {
468
+                $this->sendResponse(tdmtTaskUpd, $prefix);
469
+            }
470 470
 
471
-			if ($props[$this->props['tasksoc']] && $props[$this->props['complete']]) {
472
-				$outgoing = $this->createOutgoingMessage();
471
+            if ($props[$this->props['tasksoc']] && $props[$this->props['complete']]) {
472
+                $outgoing = $this->createOutgoingMessage();
473 473
 
474
-				mapi_setprops($outgoing, [PR_SUBJECT => $prefixComplete . $messageprops[PR_SUBJECT]]);
474
+                mapi_setprops($outgoing, [PR_SUBJECT => $prefixComplete . $messageprops[PR_SUBJECT]]);
475 475
 
476
-				$this->setRecipientsForResponse($outgoing, tdmtTaskUpd, true);
477
-				$body = $this->getBody();
478
-				$stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
479
-				mapi_stream_setsize($stream, strlen($body));
480
-				mapi_stream_write($stream, $body);
481
-				mapi_stream_commit($stream);
476
+                $this->setRecipientsForResponse($outgoing, tdmtTaskUpd, true);
477
+                $body = $this->getBody();
478
+                $stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
479
+                mapi_stream_setsize($stream, strlen($body));
480
+                mapi_stream_write($stream, $body);
481
+                mapi_stream_commit($stream);
482 482
 
483
-				mapi_savechanges($outgoing);
484
-				mapi_message_submitmessage($outgoing);
485
-			}
486
-		}
483
+                mapi_savechanges($outgoing);
484
+                mapi_message_submitmessage($outgoing);
485
+            }
486
+        }
487 487
 
488
-		// Internal functions
488
+        // Internal functions
489 489
 
490
-		/* Get the store associated with the task
490
+        /* Get the store associated with the task
491 491
 		 *
492 492
 		 * Normally this will just open the store that the processed message is in. However, if the message is opened
493 493
 		 * by a delegate, this function opens the store that the message was delegated from.
494 494
 		 */
495
-		public function getTaskFolderStore() {
496
-			$ownerentryid = false;
497
-
498
-			$rcvdprops = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
499
-			if (isset($rcvdprops[PR_RCVD_REPRESENTING_ENTRYID])) {
500
-				$ownerentryid = $rcvdprops;
501
-			}
502
-			if (!$ownerentryid) {
503
-				return $this->store;
504
-			}
505
-			$ab = mapi_openaddressbook($session);
506
-			if (!$ab) {
507
-				return false;
508
-			}
509
-
510
-			$mailuser = mapi_ab_openentry($ab, $ownerentryid);
511
-			if (!$mailuser) {
512
-				return false;
513
-			}
514
-			$mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
515
-			if (!isset($mailuserprops[PR_EMAIL_ADDRESS])) {
516
-				return false;
517
-			}
518
-
519
-			$storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
520
-
521
-			return mapi_openmsgstore($this->session, $storeid);
522
-		}
523
-
524
-		/* Open the default task folder for the current user, or the specified user if passed
495
+        public function getTaskFolderStore() {
496
+            $ownerentryid = false;
497
+
498
+            $rcvdprops = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]);
499
+            if (isset($rcvdprops[PR_RCVD_REPRESENTING_ENTRYID])) {
500
+                $ownerentryid = $rcvdprops;
501
+            }
502
+            if (!$ownerentryid) {
503
+                return $this->store;
504
+            }
505
+            $ab = mapi_openaddressbook($session);
506
+            if (!$ab) {
507
+                return false;
508
+            }
509
+
510
+            $mailuser = mapi_ab_openentry($ab, $ownerentryid);
511
+            if (!$mailuser) {
512
+                return false;
513
+            }
514
+            $mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]);
515
+            if (!isset($mailuserprops[PR_EMAIL_ADDRESS])) {
516
+                return false;
517
+            }
518
+
519
+            $storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
520
+
521
+            return mapi_openmsgstore($this->session, $storeid);
522
+        }
523
+
524
+        /* Open the default task folder for the current user, or the specified user if passed
525 525
 		 *
526 526
 		 * @param $ownerentryid (Optional)EntryID of user for which we are opening the task folder
527 527
 		 */
528
-		public function getDefaultTasksFolder() {
529
-			$store = $this->getTaskFolderStore();
530
-
531
-			$inbox = mapi_msgstore_getreceivefolder($store);
532
-			$inboxprops = mapi_getprops($inbox, [PR_IPM_TASK_ENTRYID]);
533
-			if (!isset($inboxprops[PR_IPM_TASK_ENTRYID])) {
534
-				return false;
535
-			}
536
-
537
-			return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_TASK_ENTRYID]);
538
-		}
539
-
540
-		public function getSentReprProps($store) {
541
-			$storeprops = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
542
-			if (!isset($storeprops[PR_MAILBOX_OWNER_ENTRYID])) {
543
-				return false;
544
-			}
545
-
546
-			$ab = mapi_openaddressbook($this->session);
547
-			$mailuser = mapi_ab_openentry($ab, $storeprops[PR_MAILBOX_OWNER_ENTRYID]);
548
-			$mailuserprops = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_ENTRYID]);
549
-
550
-			$props = [];
551
-			$props[PR_SENT_REPRESENTING_ADDRTYPE] = $mailuserprops[PR_ADDRTYPE];
552
-			$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $mailuserprops[PR_EMAIL_ADDRESS];
553
-			$props[PR_SENT_REPRESENTING_NAME] = $mailuserprops[PR_DISPLAY_NAME];
554
-			$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $mailuserprops[PR_SEARCH_KEY];
555
-			$props[PR_SENT_REPRESENTING_ENTRYID] = $mailuserprops[PR_ENTRYID];
556
-
557
-			return $props;
558
-		}
559
-
560
-		/*
528
+        public function getDefaultTasksFolder() {
529
+            $store = $this->getTaskFolderStore();
530
+
531
+            $inbox = mapi_msgstore_getreceivefolder($store);
532
+            $inboxprops = mapi_getprops($inbox, [PR_IPM_TASK_ENTRYID]);
533
+            if (!isset($inboxprops[PR_IPM_TASK_ENTRYID])) {
534
+                return false;
535
+            }
536
+
537
+            return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_TASK_ENTRYID]);
538
+        }
539
+
540
+        public function getSentReprProps($store) {
541
+            $storeprops = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
542
+            if (!isset($storeprops[PR_MAILBOX_OWNER_ENTRYID])) {
543
+                return false;
544
+            }
545
+
546
+            $ab = mapi_openaddressbook($this->session);
547
+            $mailuser = mapi_ab_openentry($ab, $storeprops[PR_MAILBOX_OWNER_ENTRYID]);
548
+            $mailuserprops = mapi_getprops($mailuser, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_ENTRYID]);
549
+
550
+            $props = [];
551
+            $props[PR_SENT_REPRESENTING_ADDRTYPE] = $mailuserprops[PR_ADDRTYPE];
552
+            $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $mailuserprops[PR_EMAIL_ADDRESS];
553
+            $props[PR_SENT_REPRESENTING_NAME] = $mailuserprops[PR_DISPLAY_NAME];
554
+            $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $mailuserprops[PR_SEARCH_KEY];
555
+            $props[PR_SENT_REPRESENTING_ENTRYID] = $mailuserprops[PR_ENTRYID];
556
+
557
+            return $props;
558
+        }
559
+
560
+        /*
561 561
 		 * Creates an outgoing message based on the passed message - will set delegate information
562 562
 		 * and sentmail folder
563 563
 		 */
564
-		public function createOutgoingMessage() {
565
-			// Open our default store for this user (that's the only store we can submit in)
566
-			$store = $this->getDefaultStore();
567
-			$storeprops = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID]);
564
+        public function createOutgoingMessage() {
565
+            // Open our default store for this user (that's the only store we can submit in)
566
+            $store = $this->getDefaultStore();
567
+            $storeprops = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID]);
568 568
 
569
-			$outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
570
-			if (!$outbox) {
571
-				return false;
572
-			}
569
+            $outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
570
+            if (!$outbox) {
571
+                return false;
572
+            }
573 573
 
574
-			$outgoing = mapi_folder_createmessage($outbox);
575
-			if (!$outgoing) {
576
-				return false;
577
-			}
574
+            $outgoing = mapi_folder_createmessage($outbox);
575
+            if (!$outgoing) {
576
+                return false;
577
+            }
578 578
 
579
-			// Set SENT_REPRESENTING in case we're sending as a delegate
580
-			$ownerstore = $this->getTaskFolderStore();
581
-			$sentreprprops = $this->getSentReprProps($ownerstore);
582
-			mapi_setprops($outgoing, $sentreprprops);
579
+            // Set SENT_REPRESENTING in case we're sending as a delegate
580
+            $ownerstore = $this->getTaskFolderStore();
581
+            $sentreprprops = $this->getSentReprProps($ownerstore);
582
+            mapi_setprops($outgoing, $sentreprprops);
583 583
 
584
-			mapi_setprops($outgoing, [PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID]]);
584
+            mapi_setprops($outgoing, [PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID]]);
585 585
 
586
-			return $outgoing;
587
-		}
586
+            return $outgoing;
587
+        }
588 588
 
589
-		/*
589
+        /*
590 590
 		 * Send a response message (from assignee back to organizer).
591 591
 		 *
592 592
 		 * @param $type int Type of response (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd);
593 593
 		 * @return boolean TRUE on success
594 594
 		 */
595
-		public function sendResponse($type, $prefix) {
596
-			// Create a message in our outbox
597
-			$outgoing = $this->createOutgoingMessage();
598
-
599
-			$messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
600
-
601
-			$attach = mapi_message_createattach($outgoing);
602
-			mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT], PR_ATTACHMENT_HIDDEN => true]);
603
-			$sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
604
-
605
-			mapi_copyto($this->message, [], [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY], $outgoing);
606
-			mapi_copyto($this->message, [], [], $sub);
607
-
608
-			if (!$this->setRecipientsForResponse($outgoing, $type)) {
609
-				return false;
610
-			}
611
-
612
-			switch ($type) {
613
-				case tdmtTaskAcc:
614
-					$messageclass = "IPM.TaskRequest.Accept";
615
-					break;
616
-
617
-				case tdmtTaskDec:
618
-					$messageclass = "IPM.TaskRequest.Decline";
619
-					break;
620
-
621
-				case tdmtTaskUpd:
622
-					$messageclass = "IPM.TaskRequest.Update";
623
-					break;
624
-			}
625
-
626
-			mapi_savechanges($sub);
627
-			mapi_savechanges($attach);
628
-
629
-			// Set Body
630
-			$body = $this->getBody();
631
-			$stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
632
-			mapi_stream_setsize($stream, strlen($body));
633
-			mapi_stream_write($stream, $body);
634
-			mapi_stream_commit($stream);
635
-
636
-			// Set subject, taskmode, message class, icon index, response time
637
-			mapi_setprops($outgoing, [PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
638
-				$this->props['taskmode'] => $type,
639
-				PR_MESSAGE_CLASS => $messageclass,
640
-				PR_ICON_INDEX => 0xFFFFFFFF,
641
-				$this->props['assignedtime'] => time(), ]);
642
-
643
-			mapi_savechanges($outgoing);
644
-			mapi_message_submitmessage($outgoing);
645
-
646
-			return true;
647
-		}
648
-
649
-		public function getDefaultStore() {
650
-			$table = mapi_getmsgstorestable($this->session);
651
-			$rows = mapi_table_queryallrows($table, [PR_DEFAULT_STORE, PR_ENTRYID]);
652
-			foreach ($rows as $row) {
653
-				if ($row[PR_DEFAULT_STORE]) {
654
-					return mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
655
-				}
656
-			}
657
-
658
-			return false;
659
-		}
660
-
661
-		/* Creates a new TaskGlobalObjId
595
+        public function sendResponse($type, $prefix) {
596
+            // Create a message in our outbox
597
+            $outgoing = $this->createOutgoingMessage();
598
+
599
+            $messageprops = mapi_getprops($this->message, [PR_SUBJECT]);
600
+
601
+            $attach = mapi_message_createattach($outgoing);
602
+            mapi_setprops($attach, [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT], PR_ATTACHMENT_HIDDEN => true]);
603
+            $sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
604
+
605
+            mapi_copyto($this->message, [], [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY], $outgoing);
606
+            mapi_copyto($this->message, [], [], $sub);
607
+
608
+            if (!$this->setRecipientsForResponse($outgoing, $type)) {
609
+                return false;
610
+            }
611
+
612
+            switch ($type) {
613
+                case tdmtTaskAcc:
614
+                    $messageclass = "IPM.TaskRequest.Accept";
615
+                    break;
616
+
617
+                case tdmtTaskDec:
618
+                    $messageclass = "IPM.TaskRequest.Decline";
619
+                    break;
620
+
621
+                case tdmtTaskUpd:
622
+                    $messageclass = "IPM.TaskRequest.Update";
623
+                    break;
624
+            }
625
+
626
+            mapi_savechanges($sub);
627
+            mapi_savechanges($attach);
628
+
629
+            // Set Body
630
+            $body = $this->getBody();
631
+            $stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
632
+            mapi_stream_setsize($stream, strlen($body));
633
+            mapi_stream_write($stream, $body);
634
+            mapi_stream_commit($stream);
635
+
636
+            // Set subject, taskmode, message class, icon index, response time
637
+            mapi_setprops($outgoing, [PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
638
+                $this->props['taskmode'] => $type,
639
+                PR_MESSAGE_CLASS => $messageclass,
640
+                PR_ICON_INDEX => 0xFFFFFFFF,
641
+                $this->props['assignedtime'] => time(), ]);
642
+
643
+            mapi_savechanges($outgoing);
644
+            mapi_message_submitmessage($outgoing);
645
+
646
+            return true;
647
+        }
648
+
649
+        public function getDefaultStore() {
650
+            $table = mapi_getmsgstorestable($this->session);
651
+            $rows = mapi_table_queryallrows($table, [PR_DEFAULT_STORE, PR_ENTRYID]);
652
+            foreach ($rows as $row) {
653
+                if ($row[PR_DEFAULT_STORE]) {
654
+                    return mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
655
+                }
656
+            }
657
+
658
+            return false;
659
+        }
660
+
661
+        /* Creates a new TaskGlobalObjId
662 662
 		 *
663 663
 		 * Just 16 bytes of random data
664 664
 		 */
665
-		public function createTGOID() {
666
-			$goid = "";
667
-			for ($i = 0; $i < 16; ++$i) {
668
-				$goid .= chr(rand(0, 255));
669
-			}
670
-
671
-			return $goid;
672
-		}
673
-
674
-		public function getEmbeddedTask($message) {
675
-			$table = mapi_message_getattachmenttable($message);
676
-			$rows = mapi_table_queryallrows($table, [PR_ATTACH_NUM]);
677
-
678
-			// Assume only one attachment
679
-			if (empty($rows)) {
680
-				return false;
681
-			}
682
-
683
-			$attach = mapi_message_openattach($message, $rows[0][PR_ATTACH_NUM]);
684
-
685
-			return mapi_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, 0);
686
-		}
687
-
688
-		public function setLastUser() {
689
-			$delegatestore = $this->getDefaultStore();
690
-			$taskstore = $this->getTaskFolderStore();
691
-
692
-			$delegateprops = mapi_getprops($delegatestore, [PR_MAILBOX_OWNER_NAME]);
693
-			$taskprops = mapi_getprops($taskstore, [PR_MAILBOX_OWNER_NAME]);
694
-
695
-			// The owner of the task
696
-			$username = $delegateprops[PR_MAILBOX_OWNER_NAME];
697
-			// This is me (the one calling the script)
698
-			$delegate = $taskprops[PR_MAILBOX_OWNER_NAME];
699
-
700
-			mapi_setprops($this->message, [$this->props["tasklastuser"] => $username, $this->props["tasklastdelegate"] => $delegate, $this->props['assignedtime'] => time()]);
701
-		}
702
-
703
-		/** Assignee becomes the owner when a user/assignor assigns any task to someone. Also there can be more than one assignee.
704
-		 * This function sets assignee as owner in the assignor's copy of task.
705
-		 */
706
-		public function setOwnerForAssignor() {
707
-			$recipTable = mapi_message_getrecipienttable($this->message);
708
-			$recips = mapi_table_queryallrows($recipTable, [PR_DISPLAY_NAME]);
709
-
710
-			if (!empty($recips)) {
711
-				$owner = [];
712
-				foreach ($recips as $value) {
713
-					$owner[] = $value[PR_DISPLAY_NAME];
714
-				}
715
-
716
-				$props = [$this->props['owner'] => implode("; ", $owner)];
717
-				mapi_setprops($this->message, $props);
718
-			}
719
-		}
720
-
721
-		/** Sets assignor as recipients in assignee's copy of task.
722
-		 *
723
-		 * If assignor has requested task updates then the assignor is added as recipient type MAPI_CC.
724
-		 *
725
-		 * Also if assignor has request SOC then the assignor is also add as recipient type MAPI_BCC
726
-		 *
727
-		 * @param $task message MAPI message which is the assignee's copy of the task
728
-		 */
729
-		public function setAssignorInRecipients($task) {
730
-			$recipTable = mapi_message_getrecipienttable($task);
731
-
732
-			// Delete all MAPI_TO recipients
733
-			$recips = mapi_table_queryallrows($recipTable, [PR_ROWID], [RES_PROPERTY,
734
-				[RELOP => RELOP_EQ,
735
-					ULPROPTAG => PR_RECIPIENT_TYPE,
736
-					VALUE => MAPI_TO,
737
-				], ]);
738
-			foreach ($recips as $recip) {
739
-				mapi_message_modifyrecipients($task, MODRECIP_REMOVE, [$recip]);
740
-			}
741
-
742
-			$recips = [];
743
-			$taskReqProps = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE]);
744
-			$associatedTaskProps = mapi_getprops($task, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['taskmultrecips']]);
745
-
746
-			// Build assignor info
747
-			$assignor = [PR_ENTRYID => $taskReqProps[PR_SENT_REPRESENTING_ENTRYID],
748
-				PR_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
749
-				PR_EMAIL_ADDRESS => $taskReqProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
750
-				PR_RECIPIENT_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
751
-				PR_ADDRTYPE => empty($taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE],
752
-				PR_RECIPIENT_FLAGS => recipSendable,
753
-			];
754
-
755
-			// Assignor has requested task updates, so set him/her as MAPI_CC in recipienttable.
756
-			if ((isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['taskupdates']]) &&
757
-				!(isset($associatedTaskProps[$this->props['taskmultrecips']]) && $associatedTaskProps[$this->props['taskmultrecips']] == tmrReceived)) {
758
-				$assignor[PR_RECIPIENT_TYPE] = MAPI_CC;
759
-				$recips[] = $assignor;
760
-			}
761
-
762
-			// Assignor wants to receive an email report when task is mark as 'Complete', so in recipients as MAPI_BCC
763
-			if (isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['tasksoc']]) {
764
-				$assignor[PR_RECIPIENT_TYPE] = MAPI_BCC;
765
-				$recips[] = $assignor;
766
-			}
767
-
768
-			if (!empty($recips)) {
769
-				mapi_message_modifyrecipients($task, MODRECIP_ADD, $recips);
770
-			}
771
-		}
772
-
773
-		/** Returns user information who has task request.
774
-		 */
775
-		public function retrieveUserData() {
776
-			// get user entryid
777
-			$storeProps = mapi_getprops($this->store, [PR_USER_ENTRYID]);
778
-			if (!$storeProps[PR_USER_ENTRYID]) {
779
-				return false;
780
-			}
781
-
782
-			$ab = mapi_openaddressbook($this->session);
783
-			// open the user entry
784
-			$user = mapi_ab_openentry($ab, $storeProps[PR_USER_ENTRYID]);
785
-			if (!$user) {
786
-				return false;
787
-			}
788
-
789
-			// receive userdata
790
-			$userProps = mapi_getprops($user, [PR_DISPLAY_NAME]);
791
-			if (!$userProps[PR_DISPLAY_NAME]) {
792
-				return false;
793
-			}
794
-
795
-			return $userProps;
796
-		}
797
-
798
-		/** Deletes incoming task request from Inbox.
799
-		 *
800
-		 * @returns array returns PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the deleted task request
801
-		 */
802
-		public function deleteReceivedTR() {
803
-			$store = $this->getTaskFolderStore();
804
-			$inbox = mapi_msgstore_getreceivefolder($store);
805
-
806
-			$storeProps = mapi_getprops($store, [PR_IPM_WASTEBASKET_ENTRYID]);
807
-			$props = mapi_getprops($this->message, [$this->props['taskglobalobjid']]);
808
-			$globalobjid = $props[$this->props['taskglobalobjid']];
809
-
810
-			// Find the task by looking for the taskglobalobjid
811
-			$restriction = [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid]];
812
-
813
-			$contents = mapi_folder_getcontentstable($inbox);
814
-
815
-			$rows = mapi_table_queryallrows($contents, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID], $restriction);
816
-
817
-			$taskrequest = false;
818
-			if (!empty($rows)) {
819
-				// If there are multiple, just use the first
820
-				$entryid = $rows[0][PR_ENTRYID];
821
-				$wastebasket = mapi_msgstore_openentry($store, $storeProps[PR_IPM_WASTEBASKET_ENTRYID]);
822
-				mapi_folder_copymessages($inbox, [$entryid], $wastebasket, MESSAGE_MOVE);
823
-
824
-				return [PR_ENTRYID => $entryid, PR_PARENT_ENTRYID => $rows[0][PR_PARENT_ENTRYID], PR_STORE_ENTRYID => $rows[0][PR_STORE_ENTRYID]];
825
-			}
826
-
827
-			return false;
828
-		}
829
-
830
-		/** Converts already sent task request to normal task.
831
-		 */
832
-		public function createUnassignedCopy() {
833
-			mapi_deleteprops($this->message, [$this->props['taskglobalobjid']]);
834
-			mapi_setprops($this->message, [$this->props['updatecount'] => 1]);
835
-
836
-			// Remove all recipents
837
-			$this->deleteAllRecipients($this->message);
838
-		}
839
-
840
-		/** Sets recipients for the outgoing message according to type of the response.
841
-		 *
842
-		 * If it is a task update, then only recipient type MAPI_CC are taken from the task message.
843
-		 *
844
-		 * If it is accept/decline response, then PR_SENT_REPRESENTATING_XXXX are taken as recipient.
845
-		 *
846
-		 *@param $outgoing MAPI_message outgoing mapi message
847
-		 *@param $responseType String response type
848
-		 *@param $sendSOC Boolean true if sending complete response else false
849
-		 */
850
-		public function setRecipientsForResponse($outgoing, $responseType, $sendSOC = false) {
851
-			// Clear recipients from outgoing msg
852
-			$this->deleteAllRecipients($outgoing);
853
-
854
-			// If it is a task update then get MAPI_CC recipients which are assignors who has asked for task update.
855
-			if ($responseType == tdmtTaskUpd) {
856
-				$recipTable = mapi_message_getrecipienttable($this->message);
857
-				$recips = mapi_table_queryallrows($recipTable, $this->recipprops, [RES_PROPERTY,
858
-					[RELOP => RELOP_EQ,
859
-						ULPROPTAG => PR_RECIPIENT_TYPE,
860
-						VALUE => ($sendSOC ? MAPI_BCC : MAPI_CC),
861
-					],
862
-				]);
863
-
864
-				// No recipients found, return error
865
-				if (empty($recips)) {
866
-					return false;
867
-				}
868
-
869
-				foreach ($recips as $recip) {
870
-					$recip[PR_RECIPIENT_TYPE] = MAPI_TO;	// Change recipient type to MAPI_TO
871
-					mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
872
-				}
873
-
874
-				return true;
875
-			}
876
-
877
-			$orgprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SUBJECT]);
878
-			$recip = [PR_DISPLAY_NAME => $orgprops[PR_SENT_REPRESENTING_NAME], PR_EMAIL_ADDRESS => $orgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS], PR_ADDRTYPE => $orgprops[PR_SENT_REPRESENTING_ADDRTYPE], PR_ENTRYID => $orgprops[PR_SENT_REPRESENTING_ENTRYID], PR_RECIPIENT_TYPE => MAPI_TO];
879
-
880
-			mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
881
-
882
-			return true;
883
-		}
884
-
885
-		/** Adds task details to message body and returns body.
886
-		 *
887
-		 *@return string constructed body with task details
888
-		 */
889
-		public function getBody() {
890
-			// @TODO: Fix translations
891
-
892
-			$msgProps = mapi_getprops($this->message);
893
-			$body = "";
894
-
895
-			if (isset($msgProps[PR_SUBJECT])) {
896
-				$body .= "\n" . dgettext("kopano", "Subject") . ":\t" . $msgProps[PR_SUBJECT];
897
-			}
898
-			if (isset($msgProps[$this->props['startdate']])) {
899
-				$body .= "\n" . dgettext("kopano", "Start Date") . ":\t" . strftime(dgettext("kopano", "%A, %B %d, %Y"), $msgProps[$this->props['startdate']]);
900
-			}
901
-			if (isset($msgProps[$this->props['duedate']])) {
902
-				$body .= "\n" . dgettext("kopano", "Due Date") . ":\t" . strftime(dgettext("kopano", "%A, %B %d, %Y"), $msgProps[$this->props['duedate']]);
903
-			}
904
-			$body .= "\n";
905
-
906
-			if (isset($msgProps[$this->props['status']])) {
907
-				$body .= "\n" . dgettext("kopano", "Status") . ":\t";
908
-				if ($msgProps[$this->props['status']] == 0) {
909
-					$body .= dgettext("kopano", "Not Started");
910
-				}
911
-				elseif ($msgProps[$this->props['status']] == 1) {
912
-					$body .= dgettext("kopano", "In Progress");
913
-				}
914
-				elseif ($msgProps[$this->props['status']] == 2) {
915
-					$body .= dgettext("kopano", "Complete");
916
-				}
917
-				elseif ($msgProps[$this->props['status']] == 3) {
918
-					$body .= dgettext("kopano", "Wait for other person");
919
-				}
920
-				elseif ($msgProps[$this->props['status']] == 4) {
921
-					$body .= dgettext("kopano", "Deferred");
922
-				}
923
-			}
924
-
925
-			if (isset($msgProps[$this->props['percent_complete']])) {
926
-				$body .= "\n" . dgettext("kopano", "Percent Complete") . ":\t" . ($msgProps[$this->props['percent_complete']] * 100) . '%';
927
-
928
-				if ($msgProps[$this->props['percent_complete']] == 1 && isset($msgProps[$this->props['datecompleted']])) {
929
-					$body .= "\n" . dgettext("kopano", "Date Completed") . ":\t" . strftime("%A, %B %d, %Y", $msgProps[$this->props['datecompleted']]);
930
-				}
931
-			}
932
-			$body .= "\n";
933
-
934
-			if (isset($msgProps[$this->props['totalwork']])) {
935
-				$body .= "\n" . dgettext("kopano", "Total Work") . ":\t" . ($msgProps[$this->props['totalwork']] / 60) . " " . dgettext("kopano", "hours");
936
-			}
937
-			if (isset($msgProps[$this->props['actualwork']])) {
938
-				$body .= "\n" . dgettext("kopano", "Actual Work") . ":\t" . ($msgProps[$this->props['actualwork']] / 60) . " " . dgettext("kopano", "hours");
939
-			}
940
-			$body .= "\n";
941
-
942
-			if (isset($msgProps[$this->props['owner']])) {
943
-				$body .= "\n" . dgettext("kopano", "Owner") . ":\t" . $msgProps[$this->props['owner']];
944
-			}
945
-			$body .= "\n";
946
-
947
-			if (isset($msgProps[$this->props['categories']]) && !empty($msgProps[$this->props['categories']])) {
948
-				$body .= "\nCategories:\t" . implode(', ', $msgProps[$this->props['categories']]);
949
-			}
950
-			if (isset($msgProps[$this->props['companies']]) && !empty($msgProps[$this->props['companies']])) {
951
-				$body .= "\nCompany:\t" . implode(', ', $msgProps[$this->props['companies']]);
952
-			}
953
-			if (isset($msgProps[$this->props['billinginformation']])) {
954
-				$body .= "\n" . dgettext("kopano", "Billing Information") . ":\t" . $msgProps[$this->props['billinginformation']];
955
-			}
956
-			if (isset($msgProps[$this->props['mileage']])) {
957
-				$body .= "\n" . dgettext("kopano", "Mileage") . ":\t" . $msgProps[$this->props['mileage']];
958
-			}
959
-			$body .= "\n";
960
-
961
-			$content = mapi_message_openproperty($this->message, PR_BODY);
962
-			$body .= "\n" . trim($content, "\0");
963
-
964
-			return $body;
965
-		}
966
-
967
-		/** Reclaims ownership of a decline task.
968
-		 *
969
-		 * Deletes taskrequest properties and recipients from the task message.
970
-		 */
971
-		public function reclaimownership() {
972
-			// Delete task request properties
973
-			mapi_deleteprops($this->message, [$this->props['taskglobalobjid'],
974
-				$this->props['tasklastuser'],
975
-				$this->props['tasklastdelegate'], ]);
976
-
977
-			mapi_setprops($this->message, [$this->props['updatecount'] => 2,
978
-				$this->props['taskfcreator'] => true, ]);
979
-
980
-			// Delete recipients
981
-			$this->deleteAllRecipients($this->message);
982
-		}
983
-
984
-		/** Deletes all recipients from given message object.
985
-		 *
986
-		 *@param $message MAPI message from which recipients are to be removed
987
-		 */
988
-		public function deleteAllRecipients($message) {
989
-			$recipTable = mapi_message_getrecipienttable($message);
990
-			$recipRows = mapi_table_queryallrows($recipTable, [PR_ROWID]);
991
-
992
-			foreach ($recipRows as $recipient) {
993
-				mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
994
-			}
995
-		}
996
-
997
-		public function sendCompleteUpdate($prefix, $action, $prefixComplete) {
998
-			$messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
999
-
1000
-			if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
1001
-				return false;
1002
-			} // Can only decline assignee task
1003
-
1004
-			mapi_setprops($this->message, [$this->props['complete'] => true,
1005
-				$this->props['datecompleted'] => $action["dateCompleted"],
1006
-				$this->props['status'] => 2,
1007
-				$this->props['percent_complete'] => 1, ]);
1008
-
1009
-			$this->doUpdate($prefix, $prefixComplete);
1010
-		}
1011
-	}
665
+        public function createTGOID() {
666
+            $goid = "";
667
+            for ($i = 0; $i < 16; ++$i) {
668
+                $goid .= chr(rand(0, 255));
669
+            }
670
+
671
+            return $goid;
672
+        }
673
+
674
+        public function getEmbeddedTask($message) {
675
+            $table = mapi_message_getattachmenttable($message);
676
+            $rows = mapi_table_queryallrows($table, [PR_ATTACH_NUM]);
677
+
678
+            // Assume only one attachment
679
+            if (empty($rows)) {
680
+                return false;
681
+            }
682
+
683
+            $attach = mapi_message_openattach($message, $rows[0][PR_ATTACH_NUM]);
684
+
685
+            return mapi_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, 0);
686
+        }
687
+
688
+        public function setLastUser() {
689
+            $delegatestore = $this->getDefaultStore();
690
+            $taskstore = $this->getTaskFolderStore();
691
+
692
+            $delegateprops = mapi_getprops($delegatestore, [PR_MAILBOX_OWNER_NAME]);
693
+            $taskprops = mapi_getprops($taskstore, [PR_MAILBOX_OWNER_NAME]);
694
+
695
+            // The owner of the task
696
+            $username = $delegateprops[PR_MAILBOX_OWNER_NAME];
697
+            // This is me (the one calling the script)
698
+            $delegate = $taskprops[PR_MAILBOX_OWNER_NAME];
699
+
700
+            mapi_setprops($this->message, [$this->props["tasklastuser"] => $username, $this->props["tasklastdelegate"] => $delegate, $this->props['assignedtime'] => time()]);
701
+        }
702
+
703
+        /** Assignee becomes the owner when a user/assignor assigns any task to someone. Also there can be more than one assignee.
704
+         * This function sets assignee as owner in the assignor's copy of task.
705
+         */
706
+        public function setOwnerForAssignor() {
707
+            $recipTable = mapi_message_getrecipienttable($this->message);
708
+            $recips = mapi_table_queryallrows($recipTable, [PR_DISPLAY_NAME]);
709
+
710
+            if (!empty($recips)) {
711
+                $owner = [];
712
+                foreach ($recips as $value) {
713
+                    $owner[] = $value[PR_DISPLAY_NAME];
714
+                }
715
+
716
+                $props = [$this->props['owner'] => implode("; ", $owner)];
717
+                mapi_setprops($this->message, $props);
718
+            }
719
+        }
720
+
721
+        /** Sets assignor as recipients in assignee's copy of task.
722
+         *
723
+         * If assignor has requested task updates then the assignor is added as recipient type MAPI_CC.
724
+         *
725
+         * Also if assignor has request SOC then the assignor is also add as recipient type MAPI_BCC
726
+         *
727
+         * @param $task message MAPI message which is the assignee's copy of the task
728
+         */
729
+        public function setAssignorInRecipients($task) {
730
+            $recipTable = mapi_message_getrecipienttable($task);
731
+
732
+            // Delete all MAPI_TO recipients
733
+            $recips = mapi_table_queryallrows($recipTable, [PR_ROWID], [RES_PROPERTY,
734
+                [RELOP => RELOP_EQ,
735
+                    ULPROPTAG => PR_RECIPIENT_TYPE,
736
+                    VALUE => MAPI_TO,
737
+                ], ]);
738
+            foreach ($recips as $recip) {
739
+                mapi_message_modifyrecipients($task, MODRECIP_REMOVE, [$recip]);
740
+            }
741
+
742
+            $recips = [];
743
+            $taskReqProps = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE]);
744
+            $associatedTaskProps = mapi_getprops($task, [$this->props['taskupdates'], $this->props['tasksoc'], $this->props['taskmultrecips']]);
745
+
746
+            // Build assignor info
747
+            $assignor = [PR_ENTRYID => $taskReqProps[PR_SENT_REPRESENTING_ENTRYID],
748
+                PR_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
749
+                PR_EMAIL_ADDRESS => $taskReqProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
750
+                PR_RECIPIENT_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
751
+                PR_ADDRTYPE => empty($taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE],
752
+                PR_RECIPIENT_FLAGS => recipSendable,
753
+            ];
754
+
755
+            // Assignor has requested task updates, so set him/her as MAPI_CC in recipienttable.
756
+            if ((isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['taskupdates']]) &&
757
+                !(isset($associatedTaskProps[$this->props['taskmultrecips']]) && $associatedTaskProps[$this->props['taskmultrecips']] == tmrReceived)) {
758
+                $assignor[PR_RECIPIENT_TYPE] = MAPI_CC;
759
+                $recips[] = $assignor;
760
+            }
761
+
762
+            // Assignor wants to receive an email report when task is mark as 'Complete', so in recipients as MAPI_BCC
763
+            if (isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['tasksoc']]) {
764
+                $assignor[PR_RECIPIENT_TYPE] = MAPI_BCC;
765
+                $recips[] = $assignor;
766
+            }
767
+
768
+            if (!empty($recips)) {
769
+                mapi_message_modifyrecipients($task, MODRECIP_ADD, $recips);
770
+            }
771
+        }
772
+
773
+        /** Returns user information who has task request.
774
+         */
775
+        public function retrieveUserData() {
776
+            // get user entryid
777
+            $storeProps = mapi_getprops($this->store, [PR_USER_ENTRYID]);
778
+            if (!$storeProps[PR_USER_ENTRYID]) {
779
+                return false;
780
+            }
781
+
782
+            $ab = mapi_openaddressbook($this->session);
783
+            // open the user entry
784
+            $user = mapi_ab_openentry($ab, $storeProps[PR_USER_ENTRYID]);
785
+            if (!$user) {
786
+                return false;
787
+            }
788
+
789
+            // receive userdata
790
+            $userProps = mapi_getprops($user, [PR_DISPLAY_NAME]);
791
+            if (!$userProps[PR_DISPLAY_NAME]) {
792
+                return false;
793
+            }
794
+
795
+            return $userProps;
796
+        }
797
+
798
+        /** Deletes incoming task request from Inbox.
799
+         *
800
+         * @returns array returns PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the deleted task request
801
+         */
802
+        public function deleteReceivedTR() {
803
+            $store = $this->getTaskFolderStore();
804
+            $inbox = mapi_msgstore_getreceivefolder($store);
805
+
806
+            $storeProps = mapi_getprops($store, [PR_IPM_WASTEBASKET_ENTRYID]);
807
+            $props = mapi_getprops($this->message, [$this->props['taskglobalobjid']]);
808
+            $globalobjid = $props[$this->props['taskglobalobjid']];
809
+
810
+            // Find the task by looking for the taskglobalobjid
811
+            $restriction = [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid]];
812
+
813
+            $contents = mapi_folder_getcontentstable($inbox);
814
+
815
+            $rows = mapi_table_queryallrows($contents, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID], $restriction);
816
+
817
+            $taskrequest = false;
818
+            if (!empty($rows)) {
819
+                // If there are multiple, just use the first
820
+                $entryid = $rows[0][PR_ENTRYID];
821
+                $wastebasket = mapi_msgstore_openentry($store, $storeProps[PR_IPM_WASTEBASKET_ENTRYID]);
822
+                mapi_folder_copymessages($inbox, [$entryid], $wastebasket, MESSAGE_MOVE);
823
+
824
+                return [PR_ENTRYID => $entryid, PR_PARENT_ENTRYID => $rows[0][PR_PARENT_ENTRYID], PR_STORE_ENTRYID => $rows[0][PR_STORE_ENTRYID]];
825
+            }
826
+
827
+            return false;
828
+        }
829
+
830
+        /** Converts already sent task request to normal task.
831
+         */
832
+        public function createUnassignedCopy() {
833
+            mapi_deleteprops($this->message, [$this->props['taskglobalobjid']]);
834
+            mapi_setprops($this->message, [$this->props['updatecount'] => 1]);
835
+
836
+            // Remove all recipents
837
+            $this->deleteAllRecipients($this->message);
838
+        }
839
+
840
+        /** Sets recipients for the outgoing message according to type of the response.
841
+         *
842
+         * If it is a task update, then only recipient type MAPI_CC are taken from the task message.
843
+         *
844
+         * If it is accept/decline response, then PR_SENT_REPRESENTATING_XXXX are taken as recipient.
845
+         *
846
+         *@param $outgoing MAPI_message outgoing mapi message
847
+         *@param $responseType String response type
848
+         *@param $sendSOC Boolean true if sending complete response else false
849
+         */
850
+        public function setRecipientsForResponse($outgoing, $responseType, $sendSOC = false) {
851
+            // Clear recipients from outgoing msg
852
+            $this->deleteAllRecipients($outgoing);
853
+
854
+            // If it is a task update then get MAPI_CC recipients which are assignors who has asked for task update.
855
+            if ($responseType == tdmtTaskUpd) {
856
+                $recipTable = mapi_message_getrecipienttable($this->message);
857
+                $recips = mapi_table_queryallrows($recipTable, $this->recipprops, [RES_PROPERTY,
858
+                    [RELOP => RELOP_EQ,
859
+                        ULPROPTAG => PR_RECIPIENT_TYPE,
860
+                        VALUE => ($sendSOC ? MAPI_BCC : MAPI_CC),
861
+                    ],
862
+                ]);
863
+
864
+                // No recipients found, return error
865
+                if (empty($recips)) {
866
+                    return false;
867
+                }
868
+
869
+                foreach ($recips as $recip) {
870
+                    $recip[PR_RECIPIENT_TYPE] = MAPI_TO;	// Change recipient type to MAPI_TO
871
+                    mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
872
+                }
873
+
874
+                return true;
875
+            }
876
+
877
+            $orgprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SUBJECT]);
878
+            $recip = [PR_DISPLAY_NAME => $orgprops[PR_SENT_REPRESENTING_NAME], PR_EMAIL_ADDRESS => $orgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS], PR_ADDRTYPE => $orgprops[PR_SENT_REPRESENTING_ADDRTYPE], PR_ENTRYID => $orgprops[PR_SENT_REPRESENTING_ENTRYID], PR_RECIPIENT_TYPE => MAPI_TO];
879
+
880
+            mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
881
+
882
+            return true;
883
+        }
884
+
885
+        /** Adds task details to message body and returns body.
886
+         *
887
+         *@return string constructed body with task details
888
+         */
889
+        public function getBody() {
890
+            // @TODO: Fix translations
891
+
892
+            $msgProps = mapi_getprops($this->message);
893
+            $body = "";
894
+
895
+            if (isset($msgProps[PR_SUBJECT])) {
896
+                $body .= "\n" . dgettext("kopano", "Subject") . ":\t" . $msgProps[PR_SUBJECT];
897
+            }
898
+            if (isset($msgProps[$this->props['startdate']])) {
899
+                $body .= "\n" . dgettext("kopano", "Start Date") . ":\t" . strftime(dgettext("kopano", "%A, %B %d, %Y"), $msgProps[$this->props['startdate']]);
900
+            }
901
+            if (isset($msgProps[$this->props['duedate']])) {
902
+                $body .= "\n" . dgettext("kopano", "Due Date") . ":\t" . strftime(dgettext("kopano", "%A, %B %d, %Y"), $msgProps[$this->props['duedate']]);
903
+            }
904
+            $body .= "\n";
905
+
906
+            if (isset($msgProps[$this->props['status']])) {
907
+                $body .= "\n" . dgettext("kopano", "Status") . ":\t";
908
+                if ($msgProps[$this->props['status']] == 0) {
909
+                    $body .= dgettext("kopano", "Not Started");
910
+                }
911
+                elseif ($msgProps[$this->props['status']] == 1) {
912
+                    $body .= dgettext("kopano", "In Progress");
913
+                }
914
+                elseif ($msgProps[$this->props['status']] == 2) {
915
+                    $body .= dgettext("kopano", "Complete");
916
+                }
917
+                elseif ($msgProps[$this->props['status']] == 3) {
918
+                    $body .= dgettext("kopano", "Wait for other person");
919
+                }
920
+                elseif ($msgProps[$this->props['status']] == 4) {
921
+                    $body .= dgettext("kopano", "Deferred");
922
+                }
923
+            }
924
+
925
+            if (isset($msgProps[$this->props['percent_complete']])) {
926
+                $body .= "\n" . dgettext("kopano", "Percent Complete") . ":\t" . ($msgProps[$this->props['percent_complete']] * 100) . '%';
927
+
928
+                if ($msgProps[$this->props['percent_complete']] == 1 && isset($msgProps[$this->props['datecompleted']])) {
929
+                    $body .= "\n" . dgettext("kopano", "Date Completed") . ":\t" . strftime("%A, %B %d, %Y", $msgProps[$this->props['datecompleted']]);
930
+                }
931
+            }
932
+            $body .= "\n";
933
+
934
+            if (isset($msgProps[$this->props['totalwork']])) {
935
+                $body .= "\n" . dgettext("kopano", "Total Work") . ":\t" . ($msgProps[$this->props['totalwork']] / 60) . " " . dgettext("kopano", "hours");
936
+            }
937
+            if (isset($msgProps[$this->props['actualwork']])) {
938
+                $body .= "\n" . dgettext("kopano", "Actual Work") . ":\t" . ($msgProps[$this->props['actualwork']] / 60) . " " . dgettext("kopano", "hours");
939
+            }
940
+            $body .= "\n";
941
+
942
+            if (isset($msgProps[$this->props['owner']])) {
943
+                $body .= "\n" . dgettext("kopano", "Owner") . ":\t" . $msgProps[$this->props['owner']];
944
+            }
945
+            $body .= "\n";
946
+
947
+            if (isset($msgProps[$this->props['categories']]) && !empty($msgProps[$this->props['categories']])) {
948
+                $body .= "\nCategories:\t" . implode(', ', $msgProps[$this->props['categories']]);
949
+            }
950
+            if (isset($msgProps[$this->props['companies']]) && !empty($msgProps[$this->props['companies']])) {
951
+                $body .= "\nCompany:\t" . implode(', ', $msgProps[$this->props['companies']]);
952
+            }
953
+            if (isset($msgProps[$this->props['billinginformation']])) {
954
+                $body .= "\n" . dgettext("kopano", "Billing Information") . ":\t" . $msgProps[$this->props['billinginformation']];
955
+            }
956
+            if (isset($msgProps[$this->props['mileage']])) {
957
+                $body .= "\n" . dgettext("kopano", "Mileage") . ":\t" . $msgProps[$this->props['mileage']];
958
+            }
959
+            $body .= "\n";
960
+
961
+            $content = mapi_message_openproperty($this->message, PR_BODY);
962
+            $body .= "\n" . trim($content, "\0");
963
+
964
+            return $body;
965
+        }
966
+
967
+        /** Reclaims ownership of a decline task.
968
+         *
969
+         * Deletes taskrequest properties and recipients from the task message.
970
+         */
971
+        public function reclaimownership() {
972
+            // Delete task request properties
973
+            mapi_deleteprops($this->message, [$this->props['taskglobalobjid'],
974
+                $this->props['tasklastuser'],
975
+                $this->props['tasklastdelegate'], ]);
976
+
977
+            mapi_setprops($this->message, [$this->props['updatecount'] => 2,
978
+                $this->props['taskfcreator'] => true, ]);
979
+
980
+            // Delete recipients
981
+            $this->deleteAllRecipients($this->message);
982
+        }
983
+
984
+        /** Deletes all recipients from given message object.
985
+         *
986
+         *@param $message MAPI message from which recipients are to be removed
987
+         */
988
+        public function deleteAllRecipients($message) {
989
+            $recipTable = mapi_message_getrecipienttable($message);
990
+            $recipRows = mapi_table_queryallrows($recipTable, [PR_ROWID]);
991
+
992
+            foreach ($recipRows as $recipient) {
993
+                mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
994
+            }
995
+        }
996
+
997
+        public function sendCompleteUpdate($prefix, $action, $prefixComplete) {
998
+            $messageprops = mapi_getprops($this->message, [$this->props['taskstate']]);
999
+
1000
+            if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) {
1001
+                return false;
1002
+            } // Can only decline assignee task
1003
+
1004
+            mapi_setprops($this->message, [$this->props['complete'] => true,
1005
+                $this->props['datecompleted'] => $action["dateCompleted"],
1006
+                $this->props['status'] => 2,
1007
+                $this->props['percent_complete'] => 1, ]);
1008
+
1009
+            $this->doUpdate($prefix, $prefixComplete);
1010
+        }
1011
+    }
Please login to merge, or discard this patch.
Switch Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -224,23 +224,23 @@  discard block
 block discarded – undo
224 224
 
225 225
 			// Set correct taskmode and taskhistory depending on response type
226 226
 			switch ($props[PR_MESSAGE_CLASS]) {
227
-				case 'IPM.TaskRequest.Accept':
228
-					$taskhistory = thAccepted;
229
-					$taskstate = tdsACC;
230
-					$delegationstate = olTaskDelegationAccepted;
231
-					break;
227
+			case 'IPM.TaskRequest.Accept':
228
+				$taskhistory = thAccepted;
229
+				$taskstate = tdsACC;
230
+				$delegationstate = olTaskDelegationAccepted;
231
+				break;
232 232
 
233
-				case 'IPM.TaskRequest.Decline':
234
-					$taskhistory = thDeclined;
235
-					$taskstate = tdsDEC;
236
-					$delegationstate = olTaskDelegationDeclined;
237
-					break;
233
+			case 'IPM.TaskRequest.Decline':
234
+				$taskhistory = thDeclined;
235
+				$taskstate = tdsDEC;
236
+				$delegationstate = olTaskDelegationDeclined;
237
+				break;
238 238
 
239
-				case 'IPM.TaskRequest.Update':
240
-					$taskhistory = thUpdated;
241
-					$taskstate = tdsACC; // Doesn't actually change anything
242
-					$delegationstate = olTaskDelegationAccepted;
243
-					break;
239
+			case 'IPM.TaskRequest.Update':
240
+				$taskhistory = thUpdated;
241
+				$taskstate = tdsACC; // Doesn't actually change anything
242
+				$delegationstate = olTaskDelegationAccepted;
243
+				break;
244 244
 			}
245 245
 
246 246
 			// Update taskstate (what the task looks like) and task history (last action done by the assignee)
@@ -610,17 +610,17 @@  discard block
 block discarded – undo
610 610
 			}
611 611
 
612 612
 			switch ($type) {
613
-				case tdmtTaskAcc:
614
-					$messageclass = "IPM.TaskRequest.Accept";
615
-					break;
613
+			case tdmtTaskAcc:
614
+				$messageclass = "IPM.TaskRequest.Accept";
615
+				break;
616 616
 
617
-				case tdmtTaskDec:
618
-					$messageclass = "IPM.TaskRequest.Decline";
619
-					break;
617
+			case tdmtTaskDec:
618
+				$messageclass = "IPM.TaskRequest.Decline";
619
+				break;
620 620
 
621
-				case tdmtTaskUpd:
622
-					$messageclass = "IPM.TaskRequest.Update";
623
-					break;
621
+			case tdmtTaskUpd:
622
+				$messageclass = "IPM.TaskRequest.Update";
623
+				break;
624 624
 			}
625 625
 
626 626
 			mapi_savechanges($sub);
Please login to merge, or discard this patch.
Spacing   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -22,12 +22,12 @@  discard block
 block discarded – undo
22 22
 	* It is used to indicate the type of change that is being carried in the IPM.TaskRequest item (although this
23 23
 	* information seems redundant due to that information already being available in PR_MESSAGE_CLASS).
24 24
 	*/
25
-	define('tdmtNothing', 0);			// Value in IPM.Task items
26
-	define('tdmtTaskReq', 1);			// Assigner -> Assignee
27
-	define('tdmtTaskAcc', 2);			// Assignee -> Assigner
28
-	define('tdmtTaskDec', 3);			// Assignee -> Assigner
29
-	define('tdmtTaskUpd', 4);			// Assignee -> Assigner
30
-	define('tdmtTaskSELF', 5);			// Assigner -> Assigner (?)
25
+	define('tdmtNothing', 0); // Value in IPM.Task items
26
+	define('tdmtTaskReq', 1); // Assigner -> Assignee
27
+	define('tdmtTaskAcc', 2); // Assignee -> Assigner
28
+	define('tdmtTaskDec', 3); // Assignee -> Assigner
29
+	define('tdmtTaskUpd', 4); // Assignee -> Assigner
30
+	define('tdmtTaskSELF', 5); // Assigner -> Assigner (?)
31 31
 
32 32
 	/* The TaskHistory is used to show the last action on the task on both the assigner and the assignee's side.
33 33
 	*
@@ -35,20 +35,20 @@  discard block
 block discarded – undo
35 35
 	* at the top of the task request in the format 'Accepted by <user> on 01-01-2010 11:00'.
36 36
 	*/
37 37
 	define('thNone', 0);
38
-	define('thAccepted', 1);			// Set by assignee
39
-	define('thDeclined', 2);			// Set by assignee
40
-	define('thUpdated', 3);				// Set by assignee
38
+	define('thAccepted', 1); // Set by assignee
39
+	define('thDeclined', 2); // Set by assignee
40
+	define('thUpdated', 3); // Set by assignee
41 41
 	define('thDueDateChanged', 4);
42
-	define('thAssigned', 5);			// Set by assigner
42
+	define('thAssigned', 5); // Set by assigner
43 43
 
44 44
 	/* The TaskState value is used to differentiate the version of a task in the assigner's folder and the version in the
45 45
 	* assignee's folder. The buttons shown depend on this and the 'taskaccepted' boolean (for the assignee)
46 46
 	*/
47
-	define('tdsNOM', 0);		// Got a response to a deleted task, and re-created the task for the assigner
48
-	define('tdsOWNNEW', 1);		// Not assigned
49
-	define('tdsOWN', 2);		// Assignee version
50
-	define('tdsACC', 3);		// Assigner version
51
-	define('tdsDEC', 4);		// Assigner version, but assignee declined
47
+	define('tdsNOM', 0); // Got a response to a deleted task, and re-created the task for the assigner
48
+	define('tdsOWNNEW', 1); // Not assigned
49
+	define('tdsOWN', 2); // Assignee version
50
+	define('tdsACC', 3); // Assigner version
51
+	define('tdsDEC', 4); // Assigner version, but assignee declined
52 52
 
53 53
 	/* The delegationstate is used for the assigner to indicate state
54 54
 	*/
@@ -60,14 +60,14 @@  discard block
 block discarded – undo
60 60
 	/* The task ownership indicates the role of the current user relative to the task.
61 61
 	*/
62 62
 	define('olNewTask', 0);
63
-	define('olDelegatedTask', 1);	// Task has been assigned
64
-	define('olOwnTask', 2);			// Task owned
63
+	define('olDelegatedTask', 1); // Task has been assigned
64
+	define('olOwnTask', 2); // Task owned
65 65
 
66 66
 	/* taskmultrecips indicates whether the task request sent or received has multiple assignees or not.
67 67
 	*/
68 68
 	define('tmrNone', 0);
69
-	define('tmrSent', 1);		// Task has been sent to multiple assignee
70
-	define('tmrReceived', 2);	// Task Request received has multiple assignee
69
+	define('tmrSent', 1); // Task has been sent to multiple assignee
70
+	define('tmrReceived', 2); // Task Request received has multiple assignee
71 71
 
72 72
 	class TaskRequest {
73 73
 		// All recipient properties
@@ -265,13 +265,13 @@  discard block
 block discarded – undo
265 265
 			// Set properties on Task Request
266 266
 			mapi_setprops($this->message, [
267 267
 				$this->props['taskglobalobjid'] => $taskid, /* our new taskglobalobjid */
268
-				$this->props['taskstate'] => tdsACC, 		/* state for our outgoing request */
269
-				$this->props['taskmode'] => tdmtNothing, 	/* we're not sending a change */
270
-				$this->props['updatecount'] => 2,			/* version 2 (no idea) */
268
+				$this->props['taskstate'] => tdsACC, /* state for our outgoing request */
269
+				$this->props['taskmode'] => tdmtNothing, /* we're not sending a change */
270
+				$this->props['updatecount'] => 2, /* version 2 (no idea) */
271 271
 				$this->props['delegationstate'] => olTaskDelegationUnknown, /* no reply yet */
272 272
 				$this->props['ownership'] => olDelegatedTask, /* Task has been assigned */
273
-				$this->props['taskhistory'] => thAssigned,	/* Task has been assigned */
274
-				PR_ICON_INDEX => 1283,						/* Task request icon */
273
+				$this->props['taskhistory'] => thAssigned, /* Task has been assigned */
274
+				PR_ICON_INDEX => 1283, /* Task request icon */
275 275
 			]);
276 276
 			$this->setLastUser();
277 277
 			$this->setOwnerForAssignor();
@@ -284,12 +284,12 @@  discard block
 block discarded – undo
284 284
 
285 285
 			// Make it a task request, and put it in sent items after it is sent
286 286
 			mapi_setprops($outgoing, [
287
-				PR_MESSAGE_CLASS => "IPM.TaskRequest", 		/* class is task request */
288
-				$this->props['taskstate'] => tdsOWNNEW, 	/* for the recipient the task is new */
289
-				$this->props['taskmode'] => tdmtTaskReq,	/* for the recipient, it is a request */
290
-				$this->props['updatecount'] => 1,			/* version 2 is in the attachment */
287
+				PR_MESSAGE_CLASS => "IPM.TaskRequest", /* class is task request */
288
+				$this->props['taskstate'] => tdsOWNNEW, /* for the recipient the task is new */
289
+				$this->props['taskmode'] => tdmtTaskReq, /* for the recipient, it is a request */
290
+				$this->props['updatecount'] => 1, /* version 2 is in the attachment */
291 291
 				PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
292
-				PR_ICON_INDEX => 0xFFFFFFFF,				/* show assigned icon */
292
+				PR_ICON_INDEX => 0xFFFFFFFF, /* show assigned icon */
293 293
 			]);
294 294
 
295 295
 			// Set Body
@@ -404,7 +404,7 @@  discard block
 block discarded – undo
404 404
 			$this->updateTaskRequest();
405 405
 
406 406
 			// Set as accepted
407
-			mapi_setprops($this->message, [$this->props['taskhistory'] => thAccepted, $this->props['assignedtime'] => time(), $this->props['taskaccepted'] => true,  $this->props['delegationstate'] => olTaskNotDelegated]);
407
+			mapi_setprops($this->message, [$this->props['taskhistory'] => thAccepted, $this->props['assignedtime'] => time(), $this->props['taskaccepted'] => true, $this->props['delegationstate'] => olTaskNotDelegated]);
408 408
 
409 409
 			mapi_savechanges($this->message);
410 410
 
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
 			$this->updateTaskRequest();
434 434
 
435 435
 			// Set as declined
436
-			mapi_setprops($this->message, [$this->props['taskhistory'] => thDeclined,  $this->props['delegationstate'] => olTaskDelegationDeclined]);
436
+			mapi_setprops($this->message, [$this->props['taskhistory'] => thDeclined, $this->props['delegationstate'] => olTaskDelegationDeclined]);
437 437
 
438 438
 			mapi_savechanges($this->message);
439 439
 
@@ -867,7 +867,7 @@  discard block
 block discarded – undo
867 867
 				}
868 868
 
869 869
 				foreach ($recips as $recip) {
870
-					$recip[PR_RECIPIENT_TYPE] = MAPI_TO;	// Change recipient type to MAPI_TO
870
+					$recip[PR_RECIPIENT_TYPE] = MAPI_TO; // Change recipient type to MAPI_TO
871 871
 					mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, [$recip]);
872 872
 				}
873 873
 
Please login to merge, or discard this patch.
Braces   +5 added lines, -10 removed lines patch added patch discarded remove patch
@@ -325,8 +325,7 @@  discard block
 block discarded – undo
325 325
 			$messageprops = mapi_getprops($this->message, [$this->props['updatecount']]);
326 326
 			if (isset($messageprops)) {
327 327
 				++$messageprops[$this->props['updatecount']];
328
-			}
329
-			else {
328
+			} else {
330 329
 				$messageprops[$this->props['updatecount']] = 1;
331 330
 			}
332 331
 
@@ -907,17 +906,13 @@  discard block
 block discarded – undo
907 906
 				$body .= "\n" . dgettext("kopano", "Status") . ":\t";
908 907
 				if ($msgProps[$this->props['status']] == 0) {
909 908
 					$body .= dgettext("kopano", "Not Started");
910
-				}
911
-				elseif ($msgProps[$this->props['status']] == 1) {
909
+				} elseif ($msgProps[$this->props['status']] == 1) {
912 910
 					$body .= dgettext("kopano", "In Progress");
913
-				}
914
-				elseif ($msgProps[$this->props['status']] == 2) {
911
+				} elseif ($msgProps[$this->props['status']] == 2) {
915 912
 					$body .= dgettext("kopano", "Complete");
916
-				}
917
-				elseif ($msgProps[$this->props['status']] == 3) {
913
+				} elseif ($msgProps[$this->props['status']] == 3) {
918 914
 					$body .= dgettext("kopano", "Wait for other person");
919
-				}
920
-				elseif ($msgProps[$this->props['status']] == 4) {
915
+				} elseif ($msgProps[$this->props['status']] == 4) {
921 916
 					$body .= dgettext("kopano", "Deferred");
922 917
 				}
923 918
 			}
Please login to merge, or discard this patch.
mapi/class.baserecurrence.php 3 patches
Indentation   +1880 added lines, -1880 removed lines patch added patch discarded remove patch
@@ -5,1885 +5,1885 @@
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020 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
-		 * @param resource $store      MAPI Message Store Object
46
-		 * @param resource $message    the MAPI (appointment) message
47
-		 * @param array    $properties the list of MAPI properties the message has
48
-		 */
49
-		public function __construct($store, $message) {
50
-			$this->store = $store;
51
-
52
-			if (is_array($message)) {
53
-				$this->messageprops = $message;
54
-			}
55
-			else {
56
-				$this->message = $message;
57
-				$this->messageprops = mapi_getprops($this->message, $this->proptags);
58
-			}
59
-
60
-			if (isset($this->messageprops[$this->proptags["recurring_data"]])) {
61
-				// There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
62
-				if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
63
-					$this->getFullRecurrenceBlob();
64
-				}
65
-
66
-				$this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
67
-			}
68
-			if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) {
69
-				$this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
70
-			}
71
-		}
72
-
73
-		public function getRecurrence() {
74
-			return $this->recur;
75
-		}
76
-
77
-		public function getFullRecurrenceBlob() {
78
-			$message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
79
-
80
-			$recurrBlob = '';
81
-			$stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
82
-			$stat = mapi_stream_stat($stream);
83
-
84
-			for ($i = 0; $i < $stat['cb']; $i += 1024) {
85
-				$recurrBlob .= mapi_stream_read($stream, 1024);
86
-			}
87
-			if (!empty($recurrBlob)) {
88
-				$this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
89
-			}
90
-		}
91
-
92
-		/**
93
-		 * Function for parsing the Recurrence value of a Calendar item.
94
-		 *
95
-		 * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
96
-		 * data to this function
97
-		 *
98
-		 * Returns a structure containing the data:
99
-		 *
100
-		 * type		- type of recurrence: day=10, week=11, month=12, year=13
101
-		 * subtype	- type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (i.e. 2nd Tuesday and Wednesday)
102
-		 * start	- Unix timestamp of first occurrence
103
-		 * end		- Unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
104
-		 * numoccur     - occurrences (may be very large when there is no end data)
105
-		 *
106
-		 * then, for each type:
107
-		 *
108
-		 * Daily:
109
-		 *  everyn	- every [everyn] days in minutes
110
-		 *  regen	- regenerating event (like tasks)
111
-		 *
112
-		 * Weekly:
113
-		 *  everyn	- every [everyn] weeks in weeks
114
-		 *  regen	- regenerating event (like tasks)
115
-		 *  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
116
-		 *
117
-		 * Monthly:
118
-		 *  everyn	- every [everyn] months
119
-		 *  regen	- regenerating event (like tasks)
120
-		 *
121
-		 *  subtype 2:
122
-		 *	  monthday - on day [monthday] of the month
123
-		 *
124
-		 *  subtype 3:
125
-		 *	  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
126
-		 *   nday	- on [nday]'th [weekdays] of the month
127
-		 *
128
-		 * Yearly:
129
-		 *  everyn	- every [everyn] months (12, 24, 36, ...)
130
-		 *  month	- in month [month] (although the month is encoded in minutes since the startning of the year ........)
131
-		 *  regen	- regenerating event (like tasks)
132
-		 *
133
-		 *  subtype 2:
134
-		 *   monthday - on day [monthday] of the month
135
-		 *
136
-		 *  subtype 3:
137
-		 *   weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
138
-		 *	  nday	- on [nday]'th [weekdays] of the month [month]
139
-		 *
140
-		 * @param string $rdata Binary string
141
-		 *
142
-		 * @return array recurrence data
143
-		 */
144
-		public function parseRecurrence($rdata) {
145
-			if (strlen($rdata) < 10) {
146
-				return;
147
-			}
148
-
149
-			$ret["changed_occurrences"] = [];
150
-			$ret["deleted_occurrences"] = [];
151
-
152
-			$data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
153
-
154
-			$ret["type"] = $data["rtype"];
155
-			$ret["subtype"] = $data["rtype2"];
156
-			$rdata = substr($rdata, 10);
157
-
158
-			switch ($data["rtype"]) {
159
-			case 0x0A:
160
-				// Daily
161
-				if (strlen($rdata) < 12) {
162
-					return $ret;
163
-				}
164
-				$data = unpack("Vunknown/Veveryn/Vregen", $rdata);
165
-				$ret["everyn"] = $data["everyn"];
166
-				$ret["regen"] = $data["regen"];
167
-
168
-				switch ($ret["subtype"]) {
169
-				case 0:
170
-					$rdata = substr($rdata, 12);
171
-					break;
172
-
173
-				case 1:
174
-					$rdata = substr($rdata, 16);
175
-					break;
176
-				}
177
-				break;
178
-
179
-			case 0x0B:
180
-				// Weekly
181
-				if (strlen($rdata) < 16) {
182
-					return $ret;
183
-				}
184
-
185
-				$data = unpack("Vconst1/Veveryn/Vregen", $rdata);
186
-				$rdata = substr($rdata, 12);
187
-
188
-				$ret["everyn"] = $data["everyn"];
189
-				$ret["regen"] = $data["regen"];
190
-				$ret["weekdays"] = 0;
191
-
192
-				if ($data["regen"] == 0) {
193
-					$data = unpack("Vweekdays", $rdata);
194
-					$rdata = substr($rdata, 4);
195
-					$ret["weekdays"] = $data["weekdays"];
196
-				}
197
-				break;
198
-
199
-			case 0x0C:
200
-				// Monthly
201
-				if (strlen($rdata) < 16) {
202
-					return $ret;
203
-				}
204
-
205
-				$data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
206
-				$ret["everyn"] = $data["everyn"];
207
-				$ret["regen"] = $data["regen"];
208
-				if ($ret["subtype"] == 3) {
209
-					$ret["weekdays"] = $data["monthday"];
210
-				}
211
-				else {
212
-					$ret["monthday"] = $data["monthday"];
213
-				}
214
-
215
-				$rdata = substr($rdata, 16);
216
-				if ($ret["subtype"] == 3) {
217
-					$data = unpack("Vnday", $rdata);
218
-					$ret["nday"] = $data["nday"];
219
-					$rdata = substr($rdata, 4);
220
-				}
221
-				break;
222
-
223
-			case 0x0D:
224
-				// Yearly
225
-				if (strlen($rdata) < 16) {
226
-					return $ret;
227
-				}
228
-
229
-				$data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
230
-				$ret["month"] = $data["month"];
231
-				$ret["everyn"] = $data["everyn"];
232
-				$ret["regen"] = $data["regen"];
233
-				if ($ret["subtype"] == 3) {
234
-					$ret["weekdays"] = $data["monthday"];
235
-				}
236
-				else {
237
-					$ret["monthday"] = $data["monthday"];
238
-				}
239
-
240
-				$rdata = substr($rdata, 16);
241
-				if ($ret["subtype"] == 3) {
242
-					$data = unpack("Vnday", $rdata);
243
-					$ret["nday"] = $data["nday"];
244
-					$rdata = substr($rdata, 4);
245
-				}
246
-				break;
247
-			}
248
-
249
-			if (strlen($rdata) < 16) {
250
-				return $ret;
251
-			}
252
-
253
-			$data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
254
-
255
-			$rdata = substr($rdata, 16);
256
-
257
-			$ret["term"] = $data["term"];
258
-			$ret["numoccur"] = $data["numoccur"];
259
-			$ret["numexcept"] = $data["numexcept"];
260
-
261
-			// exc_base_dates are *all* the base dates that have been either deleted or modified
262
-			$exc_base_dates = [];
263
-			for ($i = 0; $i < $ret["numexcept"]; ++$i) {
264
-				if (strlen($rdata) < 4) {
265
-					// We shouldn't arrive here, because that implies
266
-					// numexcept does not match the amount of data
267
-					// which is available for the exceptions.
268
-					return $ret;
269
-				}
270
-				$data = unpack("Vbasedate", $rdata);
271
-				$rdata = substr($rdata, 4);
272
-				$exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
273
-			}
274
-
275
-			if (strlen($rdata) < 4) {
276
-				return $ret;
277
-			}
278
-			$data = unpack("Vnumexceptmod", $rdata);
279
-			$rdata = substr($rdata, 4);
280
-
281
-			$ret["numexceptmod"] = $data["numexceptmod"];
282
-
283
-			// exc_changed are the base dates of *modified* occurrences. exactly what is modified
284
-			// is in the attachments *and* in the data further down this function.
285
-			$exc_changed = [];
286
-			for ($i = 0; $i < $ret["numexceptmod"]; ++$i) {
287
-				if (strlen($rdata) < 4) {
288
-					// We shouldn't arrive here, because that implies
289
-					// numexceptmod does not match the amount of data
290
-					// which is available for the exceptions.
291
-					return $ret;
292
-				}
293
-				$data = unpack("Vstartdate", $rdata);
294
-				$rdata = substr($rdata, 4);
295
-				$exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
296
-			}
297
-
298
-			if (strlen($rdata) < 8) {
299
-				return $ret;
300
-			}
301
-			$data = unpack("Vstart/Vend", $rdata);
302
-			$rdata = substr($rdata, 8);
303
-
304
-			$ret["start"] = $this->recurDataToUnixData($data["start"]);
305
-			$ret["end"] = $this->recurDataToUnixData($data["end"]);
306
-
307
-			// this is where task recurrence stop
308
-			if (strlen($rdata) < 16) {
309
-				return $ret;
310
-			}
311
-			$data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
312
-			$rdata = substr($rdata, 16);
313
-
314
-			$ret["startocc"] = $data["startmin"];
315
-			$ret["endocc"] = $data["endmin"];
316
-			$readerversion = $data["readerversion"];
317
-			$writerversion = $data["writerversion"];
318
-
319
-			$data = unpack("vnumber", $rdata);
320
-			$rdata = substr($rdata, 2);
321
-
322
-			$nexceptions = $data["number"];
323
-			$exc_changed_details = [];
324
-
325
-			// Parse n modified exceptions
326
-			for ($i = 0; $i < $nexceptions; ++$i) {
327
-				$item = [];
328
-
329
-				// Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
330
-				$data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
331
-				$rdata = substr($rdata, 12);
332
-
333
-				// Convert recurtimestamp to Unix timestamp
334
-				$startdate = $this->recurDataToUnixData($data["startdate"]);
335
-				$enddate = $this->recurDataToUnixData($data["enddate"]);
336
-				$basedate = $this->recurDataToUnixData($data["basedate"]);
337
-
338
-				// Set the right properties
339
-				$item["basedate"] = $this->dayStartOf($basedate);
340
-				$item["start"] = $startdate;
341
-				$item["end"] = $enddate;
342
-
343
-				$data = unpack("vbitmask", $rdata);
344
-				$rdata = substr($rdata, 2);
345
-				$item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
346
-
347
-				// Bitmask to verify what properties are changed
348
-				$bitmask = $data["bitmask"];
349
-
350
-				// ARO_SUBJECT: 0x0001
351
-				// Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
352
-				if (($bitmask & (1 << 0))) {
353
-					$data = unpack("vnull_length/vlength", $rdata);
354
-					$rdata = substr($rdata, 4);
355
-
356
-					$length = $data["length"];
357
-					$item["subject"] = ""; // Normalized subject
358
-					for ($j = 0; $j < $length && strlen($rdata); ++$j) {
359
-						$data = unpack("Cchar", $rdata);
360
-						$rdata = substr($rdata, 1);
361
-
362
-						$item["subject"] .= chr($data["char"]);
363
-					}
364
-				}
365
-
366
-				// ARO_MEETINGTYPE: 0x0002
367
-				if (($bitmask & (1 << 1))) {
368
-					$rdata = substr($rdata, 4);
369
-				}
370
-				// Attendees modified: no data here (only in attachment)
371
-
372
-				// ARO_REMINDERDELTA: 0x0004
373
-				// Look for field: ReminderDelta (4b)
374
-				if (($bitmask & (1 << 2))) {
375
-					$data = unpack("Vremind_before", $rdata);
376
-					$rdata = substr($rdata, 4);
377
-
378
-					$item["remind_before"] = $data["remind_before"];
379
-				}
380
-
381
-				// ARO_REMINDER: 0x0008
382
-				// Look field: ReminderSet (4b)
383
-				if (($bitmask & (1 << 3))) {
384
-					$data = unpack("Vreminder_set", $rdata);
385
-					$rdata = substr($rdata, 4);
386
-
387
-					$item["reminder_set"] = $data["reminder_set"];
388
-				}
389
-
390
-				// ARO_LOCATION: 0x0010
391
-				// Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
392
-				// Similar to ARO_SUBJECT above.
393
-				if (($bitmask & (1 << 4))) {
394
-					$data = unpack("vnull_length/vlength", $rdata);
395
-					$rdata = substr($rdata, 4);
396
-
397
-					$item["location"] = "";
398
-
399
-					$length = $data["length"];
400
-					$data = substr($rdata, 0, $length);
401
-					$rdata = substr($rdata, $length);
402
-
403
-					$item["location"] .= $data;
404
-				}
405
-
406
-				// ARO_BUSYSTATUS: 0x0020
407
-				// Look for field: BusyStatus (4b)
408
-				if (($bitmask & (1 << 5))) {
409
-					$data = unpack("Vbusystatus", $rdata);
410
-					$rdata = substr($rdata, 4);
411
-
412
-					$item["busystatus"] = $data["busystatus"];
413
-				}
414
-
415
-				// ARO_ATTACHMENT: 0x0040
416
-				if (($bitmask & (1 << 6))) {
417
-					// no data: RESERVED
418
-					$rdata = substr($rdata, 4);
419
-				}
420
-
421
-				// ARO_SUBTYPE: 0x0080
422
-				// Look for field: SubType (4b). Determines whether it is an allday event.
423
-				if (($bitmask & (1 << 7))) {
424
-					$data = unpack("Vallday", $rdata);
425
-					$rdata = substr($rdata, 4);
426
-
427
-					$item["alldayevent"] = $data["allday"];
428
-				}
429
-
430
-				// ARO_APPTCOLOR: 0x0100
431
-				// Look for field: AppointmentColor (4b)
432
-				if (($bitmask & (1 << 8))) {
433
-					$data = unpack("Vlabel", $rdata);
434
-					$rdata = substr($rdata, 4);
435
-
436
-					$item["label"] = $data["label"];
437
-				}
438
-
439
-				// ARO_EXCEPTIONAL_BODY: 0x0200
440
-				if (($bitmask & (1 << 9)))
441
-					/* nothing */; // Notes or Attachments modified: no data here (only in attachment)
442
-
443
-				array_push($exc_changed_details, $item);
444
-			}
445
-
446
-			/**
447
-			 * We now have $exc_changed, $exc_base_dates and $exc_changed_details
448
-			 * We will ignore $exc_changed, as this information is available in $exc_changed_details
449
-			 * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
450
-			 * has been deleted.
451
-			 */
452
-
453
-			// Find deleted occurrences
454
-			$deleted_occurrences = [];
455
-
456
-			foreach ($exc_base_dates as $base_date) {
457
-				$found = false;
458
-
459
-				foreach ($exc_changed_details as $details) {
460
-					if ($details["basedate"] == $base_date) {
461
-						$found = true;
462
-
463
-						break;
464
-					}
465
-				}
466
-				if (!$found) {
467
-					// item was not in exc_changed_details, so it must be deleted
468
-					$deleted_occurrences[] = $base_date;
469
-				}
470
-			}
471
-
472
-			$ret["deleted_occurrences"] = $deleted_occurrences;
473
-			$ret["changed_occurrences"] = $exc_changed_details;
474
-
475
-			// enough data for normal exception (no extended data)
476
-			if (strlen($rdata) < 16) {
477
-				return $ret;
478
-			}
479
-
480
-			$data = unpack("Vreservedsize", $rdata);
481
-			$rdata = substr($rdata, 4 + $data["reservedsize"]);
482
-
483
-			for ($i = 0; $i < $nexceptions; ++$i) {
484
-				// subject and location in UCS-2 to UTF-8
485
-				if ($writerversion >= 0x3009) {
486
-					$data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
487
-					$rdata = substr($rdata, 4 + $data["size"]);
488
-				}
489
-
490
-				$data = unpack("Vreservedsize", $rdata);
491
-				$rdata = substr($rdata, 4 + $data["reservedsize"]);
492
-
493
-				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
494
-				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
495
-					$data = unpack("Vstart/Vend/Vorig", $rdata);
496
-					$rdata = substr($rdata, 4 * 3);
497
-
498
-					$exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
499
-					$exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
500
-					$exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
501
-				}
502
-
503
-				// ARO_SUBJECT
504
-				if ($exc_changed_details[$i]["bitmask"] & 0x01) {
505
-					// decode UCS-2 string to UTF-8
506
-					$data = unpack("vlength", $rdata);
507
-					$rdata = substr($rdata, 2);
508
-					$length = $data["length"];
509
-					$data = substr($rdata, 0, $length * 2);
510
-					$rdata = substr($rdata, $length * 2);
511
-					$subject = iconv("UCS-2LE", "UTF-8", $data);
512
-					// replace subject with unicode subject
513
-					$exc_changed_details[$i]["subject"] = $subject;
514
-				}
515
-
516
-				// ARO_LOCATION
517
-				if ($exc_changed_details[$i]["bitmask"] & 0x10) {
518
-					// decode UCS-2 string to UTF-8
519
-					$data = unpack("vlength", $rdata);
520
-					$rdata = substr($rdata, 2);
521
-					$length = $data["length"];
522
-					$data = substr($rdata, 0, $length * 2);
523
-					$rdata = substr($rdata, $length * 2);
524
-					$location = iconv("UCS-2LE", "UTF-8", $data);
525
-					// replace subject with unicode subject
526
-					$exc_changed_details[$i]["location"] = $location;
527
-				}
528
-
529
-				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
530
-				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
531
-					$data = unpack("Vreservedsize", $rdata);
532
-					$rdata = substr($rdata, 4 + $data["reservedsize"]);
533
-				}
534
-			}
535
-
536
-			// update with extended data
537
-			$ret["changed_occurrences"] = $exc_changed_details;
538
-
539
-			return $ret;
540
-		}
541
-
542
-		/**
543
-		 * Saves the recurrence data to the recurrence property.
544
-		 *
545
-		 * @param array $properties the recurrence data
546
-		 *
547
-		 * @return string binary string
548
-		 */
549
-		public function saveRecurrence() {
550
-			// Only save if a message was passed
551
-			if (!isset($this->message)) {
552
-				return;
553
-			}
554
-
555
-			// Abort if no recurrence was set
556
-			if (!isset($this->recur["type"]) && !isset($this->recur["subtype"])) {
557
-				return;
558
-			}
559
-			if (!isset($this->recur["start"]) && !isset($this->recur["end"])) {
560
-				return;
561
-			}
562
-			if (!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) {
563
-				return;
564
-			}
565
-
566
-			$rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
567
-
568
-			$weekstart = 1; // monday
569
-			$forwardcount = 0;
570
-			$restocc = 0;
571
-			$dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
572
-
573
-			$term = (int) $this->recur["type"];
574
-
575
-			switch ($term) {
576
-			case 0x0A:
577
-				// Daily
578
-				if (!isset($this->recur["everyn"])) {
579
-					return;
580
-				}
581
-
582
-				if ($this->recur["subtype"] == 1) {
583
-					// Daily every workday
584
-					$rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
585
-				}
586
-				else {
587
-					// Daily every N days (everyN in minutes)
588
-					$everyn = ((int) $this->recur["everyn"]) / 1440;
589
-					// Calc first occ
590
-					$firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
591
-					$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
592
-				}
593
-				break;
594
-
595
-			case 0x0B:
596
-				// Weekly
597
-				if (!isset($this->recur["everyn"])) {
598
-					return;
599
-				}
600
-				if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
601
-					return;
602
-				}
603
-
604
-				// No need to calculate startdate if sliding flag was set.
605
-				if (!$this->recur['regen']) {
606
-					// Calculate start date of recurrence
607
-
608
-					// Find the first day that matches one of the weekdays selected
609
-					$daycount = 0;
610
-					$dayskip = -1;
611
-					for ($j = 0; $j < 7; ++$j) {
612
-						if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
613
-							if ($dayskip == -1) {
614
-								$dayskip = $j;
615
-							}
616
-							++$daycount;
617
-						}
618
-					}
619
-
620
-					// $dayskip is the number of days to skip from the startdate until the first occurrence
621
-					// $daycount is the number of days per week that an occurrence occurs
622
-					$weekskip = 0;
623
-					if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) {
624
-						$weekskip = 1;
625
-					}
626
-
627
-					// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
628
-					// remaining occurrences based on the start of the recurrence.
629
-					if (((int) $this->recur["term"]) == 0x22) {
630
-						// $weekskip is the amount of weeks to skip from the startdate before the first occurrence
631
-						// $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
632
-						// is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
633
-						// (eg when numoccur = 2, and daycount = 1)
634
-						$forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount);
635
-
636
-						// $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
637
-						// for the occurrence on the first day
638
-						$restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
639
-
640
-						// $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
641
-						$forwardcount *= (int) $this->recur["everyn"];
642
-					}
643
-
644
-					// The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
645
-					$this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
646
-				}
647
-
648
-				// Calc first occ
649
-				$firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60);
650
-				$firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
651
-
652
-				if ($this->recur["regen"]) {
653
-					$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
654
-				}
655
-				else {
656
-					$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
657
-				}
658
-				break;
659
-
660
-			case 0x0C:
661
-				// Monthly
662
-			case 0x0D:
663
-				// Yearly
664
-				if (!isset($this->recur["everyn"])) {
665
-					return;
666
-				}
667
-				if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) {
668
-					return;
669
-				}
670
-				if ($term == 0x0C /* monthly */) {
671
-					$everyn = (int) $this->recur["everyn"];
672
-				}
673
-				else {
674
-					$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
675
-				}
676
-
677
-				// Get montday/month/year of original start
678
-				$curmonthday = gmdate("j", (int) $this->recur["start"]);
679
-				$curyear = gmdate("Y", (int) $this->recur["start"]);
680
-				$curmonth = gmdate("n", (int) $this->recur["start"]);
681
-
682
-				// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
683
-				// remaining occurrences based on the start of the recurrence.
684
-				if (((int) $this->recur["term"]) == 0x22) {
685
-					// $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
686
-					// one to make sure there are always at least one occurrence left)
687
-					$forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn);
688
-				}
689
-
690
-				// Get month for yearly on D'th day of month M
691
-				if ($term == 0x0D /* yearly */) {
692
-					$selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1;
693
-				} // 1=jan, 2=feb, eg
694
-
695
-				switch ((int) $this->recur["subtype"]) {
696
-				// on D day of every M month
697
-				case 2:
698
-					if (!isset($this->recur["monthday"])) {
699
-						return;
700
-					}
701
-					// Recalc startdate
702
-					// Set on the right begin day
703
-					// Go the beginning of the month
704
-					$this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60;
705
-					// Go the the correct month day
706
-					$this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60;
707
-
708
-					// If the previous calculation gave us a start date *before* the original start date, then we need to skip to the next occurrence
709
-					if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) ||
710
-						($term == 0x0D /* yearly */ && ($selmonth < $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) {
711
-						if ($term == 0x0D /* yearly */) {
712
-							$count = ($everyn - ($curmonth - $selmonth));
713
-						} // Yearly, go to next occurrence in 'everyn' months minus difference in first occurrence and original date
714
-						else {
715
-							$count = $everyn;
716
-						} // Monthly, go to next occurrence in 'everyn' months
717
-
718
-						// Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
719
-						for ($i = 0; $i < $count; ++$i) {
720
-							$this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
721
-							if ($curmonth == 12) {
722
-								++$curyear;
723
-								$curmonth = 0;
724
-							}
725
-							++$curmonth;
726
-						}
727
-					}
728
-
729
-					// "start" is now pointing to the first occurrence, except that it will overshoot if the
730
-					// month in which it occurs has less days than specified as the day of the month. So 31st
731
-					// of each month will overshoot in february (29 days). We compensate for that by checking
732
-					// if the day of the month we got is wrong, and then back up to the last day of the previous
733
-					// month.
734
-					if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
735
-						gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) {
736
-						$this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60;
737
-					}
738
-
739
-					// "start" is now the first occurrence
740
-					if ($term == 0x0C /* monthly */) {
741
-						// Calc first occ
742
-						$monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
743
-
744
-						$firstocc = 0;
745
-						for ($i = 0; $i < $monthIndex; ++$i) {
746
-							$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
747
-						}
748
-						$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
749
-					}
750
-					else {
751
-						// Calc first occ
752
-						$firstocc = 0;
753
-						$monthIndex = (int) gmdate("n", $this->recur["start"]);
754
-						for ($i = 1; $i < $monthIndex; ++$i) {
755
-							$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
756
-						}
757
-						$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
758
-					}
759
-					break;
760
-
761
-				case 3:
762
-					// monthly: on Nth weekday of every M month
763
-					// yearly: on Nth weekday of M month
764
-					if (!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) {
765
-						return;
766
-					}
767
-
768
-					$weekdays = (int) $this->recur["weekdays"];
769
-					$nday = (int) $this->recur["nday"];
770
-
771
-					// Calc startdate
772
-					$monthbegindow = (int) $this->recur["start"];
773
-
774
-					if ($nday == 5) {
775
-						// Set date on the last day of the last month
776
-						$monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
777
-					}
778
-					else {
779
-						// Set on the first day of the month
780
-						$monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
781
-					}
782
-
783
-					if ($term == 0x0D /* yearly */) {
784
-						// Set on right month
785
-						if ($selmonth < $curmonth) {
786
-							$tmp = 12 - $curmonth + $selmonth;
787
-						}
788
-						else {
789
-							$tmp = ($selmonth - $curmonth);
790
-						}
791
-
792
-						for ($i = 0; $i < $tmp; ++$i) {
793
-							$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
794
-							if ($curmonth == 12) {
795
-								++$curyear;
796
-								$curmonth = 0;
797
-							}
798
-							++$curmonth;
799
-						}
800
-					}
801
-					else {
802
-						// Check or you exist in the right month
803
-						for ($i = 0; $i < 7; ++$i) {
804
-							if ($nday == 5 && (1 << ((gmdate("w", $monthbegindow) - $i) % 7)) & $weekdays) {
805
-								$day = gmdate("j", $monthbegindow) - $i;
806
-
807
-								break;
808
-							}
809
-							if ($nday != 5 && (1 << ((gmdate("w", $monthbegindow) + $i) % 7)) & $weekdays) {
810
-								$day = (($nday - 1) * 7) + ($i + 1);
811
-
812
-								break;
813
-							}
814
-						}
815
-
816
-						// Goto the next X month
817
-						if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) {
818
-							if ($nday == 5) {
819
-								$monthbegindow += 24 * 60 * 60;
820
-								if ($curmonth == 12) {
821
-									++$curyear;
822
-									$curmonth = 0;
823
-								}
824
-								++$curmonth;
825
-							}
826
-
827
-							for ($i = 0; $i < $everyn; ++$i) {
828
-								$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
829
-								if ($curmonth == 12) {
830
-									++$curyear;
831
-									$curmonth = 0;
832
-								}
833
-								++$curmonth;
834
-							}
835
-							if ($nday == 5) {
836
-								$monthbegindow -= 24 * 60 * 60;
837
-							}
838
-						}
839
-					}
840
-
841
-					// FIXME: weekstart?
842
-
843
-					$day = 0;
844
-					// Set start on the right day
845
-					for ($i = 0; $i < 7; ++$i) {
846
-						if ($nday == 5 && (1 << ((gmdate("w", $monthbegindow) - $i) % 7)) & $weekdays) {
847
-							$day = $i;
848
-
849
-							break;
850
-						}
851
-						if ($nday != 5 && (1 << ((gmdate("w", $monthbegindow) + $i) % 7)) & $weekdays) {
852
-							$day = ($nday - 1) * 7 + ($i + 1);
853
-
854
-							break;
855
-						}
856
-					}
857
-					if ($nday == 5) {
858
-						$monthbegindow -= $day * 24 * 60 * 60;
859
-					}
860
-					else {
861
-						$monthbegindow += ($day - 1) * 24 * 60 * 60;
862
-					}
863
-
864
-					$firstocc = 0;
865
-					if ($term == 0x0C /* monthly */) {
866
-						// Calc first occ
867
-						$monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
868
-						for ($i = 0; $i < $monthIndex; ++$i) {
869
-							$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
870
-						}
871
-						$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
872
-						break;
873
-					}
874
-					// Calc first occ
875
-					$monthIndex = (int) gmdate("n", $this->recur["start"]);
876
-					for ($i = 1; $i < $monthIndex; ++$i) {
877
-						$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
878
-					}
879
-					$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
880
-					break;
881
-				}
882
-				break;
883
-			}
884
-
885
-			if (!isset($this->recur["term"])) {
886
-				return;
887
-			}
888
-
889
-			// Terminate
890
-			$term = (int) $this->recur["term"];
891
-			$rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
892
-
893
-			switch ($term) {
894
-			// After the given enddate
895
-			case 0x21:
896
-				$rdata .= pack("V", 10);
897
-				break;
898
-			// After a number of times
899
-			case 0x22:
900
-				if (!isset($this->recur["numoccur"])) {
901
-					return;
902
-				}
903
-				$rdata .= pack("V", (int) $this->recur["numoccur"]);
904
-				break;
905
-			// Never ends
906
-			case 0x23:
907
-				$rdata .= pack("V", 0);
908
-				break;
909
-			}
910
-
911
-			// Strange little thing for the recurrence type "every workday"
912
-			if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
913
-				$rdata .= pack("V", 1);
914
-			}
915
-			else { // Other recurrences
916
-				$rdata .= pack("V", 0);
917
-			}
918
-
919
-			// Exception data
920
-
921
-			// Get all exceptions
922
-			$deleted_items = $this->recur["deleted_occurrences"];
923
-			$changed_items = $this->recur["changed_occurrences"];
924
-
925
-			// Merge deleted and changed items into one list
926
-			$items = $deleted_items;
927
-
928
-			foreach ($changed_items as $changed_item) {
929
-				array_push($items, $changed_item["basedate"]);
930
-			}
931
-
932
-			sort($items);
933
-
934
-			// Add the merged list in to the rdata
935
-			$rdata .= pack("V", count($items));
936
-			foreach ($items as $item) {
937
-				$rdata .= pack("V", $this->unixDataToRecurData($item));
938
-			}
939
-
940
-			// Loop through the changed exceptions (not deleted)
941
-			$rdata .= pack("V", count($changed_items));
942
-			$items = [];
943
-
944
-			foreach ($changed_items as $changed_item) {
945
-				$items[] = $this->dayStartOf($changed_item["start"]);
946
-			}
947
-
948
-			sort($items);
949
-
950
-			// Add the changed items list int the rdata
951
-			foreach ($items as $item) {
952
-				$rdata .= pack("V", $this->unixDataToRecurData($item));
953
-			}
954
-
955
-			// Set start date
956
-			$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
957
-
958
-			// Set enddate
959
-			switch ($term) {
960
-			// After the given enddate
961
-			case 0x21:
962
-				$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
963
-				break;
964
-			// After a number of times
965
-			case 0x22:
966
-				// @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
967
-				$occenddate = (int) $this->recur["start"];
968
-
969
-				switch ((int) $this->recur["type"]) {
970
-				case 0x0A: // daily
971
-					if ($this->recur["subtype"] != 1) {
972
-						// -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
973
-						$occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
974
-						break;
975
-					}
976
-					// Daily every workday
977
-					$restocc = (int) $this->recur["numoccur"];
978
-					// Get starting weekday
979
-					$nowtime = $this->gmtime($occenddate);
980
-					$j = $nowtime["tm_wday"];
981
-
982
-					while (1) {
983
-						if (($j % 7) > 0 && ($j % 7) < 6) {
984
-							--$restocc;
985
-						}
986
-						++$j;
987
-						if ($restocc <= 0) {
988
-							break;
989
-						}
990
-						$occenddate += 24 * 60 * 60;
991
-					}
992
-					break;
993
-
994
-				case 0x0B: // weekly
995
-					// Needed values
996
-					// $forwardcount - number of weeks we can skip forward
997
-					// $restocc - number of remaining occurrences after the week skip
998
-
999
-					// Add the weeks till the last item
1000
-					$occenddate += ($forwardcount * 7 * 24 * 60 * 60);
1001
-					$dayofweek = gmdate("w", $occenddate);
1002
-
1003
-					// Loop through the last occurrences until we have had them all
1004
-					for ($j = 1; $restocc > 0; ++$j) {
1005
-						// Jump to the next week (which may be N weeks away) when going over the week boundary
1006
-						if ((($dayofweek + $j) % 7) == $weekstart) {
1007
-							$occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1008
-						}
1009
-
1010
-						// If this is a matching day, once less occurrence to process
1011
-						if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1012
-							--$restocc;
1013
-						}
1014
-
1015
-						// Next day
1016
-						$occenddate += 24 * 60 * 60;
1017
-					}
1018
-					break;
1019
-
1020
-				case 0x0C: // monthly
1021
-				case 0x0D: // yearly
1022
-					$curyear = gmdate("Y", (int) $this->recur["start"]);
1023
-					$curmonth = gmdate("n", (int) $this->recur["start"]);
1024
-					// $forwardcount = months
1025
-
1026
-					switch ((int) $this->recur["subtype"]) {
1027
-					case 2: // on D day of every M month
1028
-						while ($forwardcount > 0) {
1029
-							$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1030
-							if ($curmonth >= 12) {
1031
-								$curmonth = 1;
1032
-								++$curyear;
1033
-							}
1034
-							else {
1035
-								++$curmonth;
1036
-							}
1037
-							--$forwardcount;
1038
-						}
1039
-
1040
-						// compensation between 28 and 31
1041
-						if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
1042
-							gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1043
-							if (gmdate("j", $occenddate) < 28) {
1044
-								$occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1045
-							}
1046
-							else {
1047
-								$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1048
-							}
1049
-						}
1050
-						break;
1051
-
1052
-					case 3: // on Nth weekday of every M month
1053
-						$nday = (int) $this->recur["nday"]; // 1 tot 5
1054
-						$weekdays = (int) $this->recur["weekdays"];
1055
-						while ($forwardcount > 0) {
1056
-							$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1057
-							if ($curmonth >= 12) {
1058
-								$curmonth = 1;
1059
-								++$curyear;
1060
-							}
1061
-							else {
1062
-								++$curmonth;
1063
-							}
1064
-							--$forwardcount;
1065
-						}
1066
-						if ($nday == 5) {
1067
-							// Set date on the last day of the last month
1068
-							$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1069
-						}
1070
-						else {
1071
-							// Set date on the first day of the last month
1072
-							$occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1073
-						}
1074
-
1075
-						for ($i = 0; $i < 7; ++$i) {
1076
-							if ($nday == 5 && (1 << ((gmdate("w", $occenddate) - $i) % 7)) & $weekdays) {
1077
-								$occenddate -= $i * 24 * 60 * 60;
1078
-
1079
-								break;
1080
-							}
1081
-							if ($nday != 5 && (1 << ((gmdate("w", $occenddate) + $i) % 7)) & $weekdays) {
1082
-								$occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60;
1083
-
1084
-								break;
1085
-							}
1086
-						}
1087
-						break; // case 3:
1088
-					}
1089
-					break;
1090
-				}
1091
-
1092
-				if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) {
1093
-					$occenddate = PHP_INT_MAX;
1094
-				}
1095
-
1096
-				$this->recur["end"] = $occenddate;
1097
-				$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1098
-				break;
1099
-			// Never ends
1100
-			case 0x23:
1101
-			default:
1102
-				$this->recur["end"] = 0x7FFFFFFF; // max date -> 2038
1103
-				$rdata .= pack("V", 0x5AE980DF);
1104
-				break;
1105
-			}
1106
-
1107
-			// UTC date
1108
-			$utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1109
-			$utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1110
-
1111
-			// utc date+time
1112
-			$utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart;
1113
-			$utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1114
-
1115
-			// update reminder time
1116
-			mapi_setprops($this->message, [$this->proptags["reminder_time"] => $utcfirstoccstartdatetime]);
1117
-
1118
-			// update first occurrence date
1119
-			mapi_setprops($this->message, [$this->proptags["startdate"] => $utcfirstoccstartdatetime]);
1120
-			mapi_setprops($this->message, [$this->proptags["duedate"] => $utcfirstoccenddatetime]);
1121
-			mapi_setprops($this->message, [$this->proptags["commonstart"] => $utcfirstoccstartdatetime]);
1122
-			mapi_setprops($this->message, [$this->proptags["commonend"] => $utcfirstoccenddatetime]);
1123
-
1124
-			// Set Outlook properties, if it is an appointment
1125
-			if (isset($this->recur["message_class"]) && $this->recur["message_class"] == "IPM.Appointment") {
1126
-				// update real begin and real end date
1127
-				mapi_setprops($this->message, [$this->proptags["startdate_recurring"] => $utcstart]);
1128
-				mapi_setprops($this->message, [$this->proptags["enddate_recurring"] => $utcend]);
1129
-
1130
-				// recurrencetype
1131
-				// Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1132
-				mapi_setprops($this->message, [$this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9]);
1133
-
1134
-				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1135
-				mapi_setprops($this->message, [$this->proptags["side_effects"] => 369]);
1136
-			}
1137
-			else {
1138
-				mapi_setprops($this->message, [$this->proptags["side_effects"] => 3441]);
1139
-			}
1140
-
1141
-			// FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
1142
-			// Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
1143
-			// to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to
1144
-			// the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
1145
-			// with the reminder flag set.
1146
-			$reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]);
1147
-			if (isset($reminderprops[$this->proptags["reminder_minutes"]])) {
1148
-				$occ = false;
1149
-				$occurrences = $this->getItems(time(), 0x7FF00000, 3, true);
1150
-
1151
-				for ($i = 0, $len = count($occurrences); $i < $len; ++$i) {
1152
-					// This will actually also give us appointments that have already started, but not yet ended. Since we want the next
1153
-					// reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
1154
-					// number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
1155
-					// time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
1156
-					// can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
1157
-
1158
-					if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
1159
-						$occ = $occurrences[$i];
1160
-
1161
-						break;
1162
-					}
1163
-				}
1164
-
1165
-				if ($occ) {
1166
-					mapi_setprops($this->message, [$this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60)]);
1167
-				}
1168
-				else {
1169
-					// Last reminder passed, no reminders any more.
1170
-					mapi_setprops($this->message, [$this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7FF00000]);
1171
-				}
1172
-			}
1173
-
1174
-			// Default data
1175
-			// Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
1176
-			$rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
1177
-
1178
-			if (isset($this->recur["startocc"], $this->recur["endocc"])) {
1179
-				// Set start and endtime in minutes
1180
-				$rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1181
-			}
1182
-
1183
-			// Detailed exception data
1184
-
1185
-			$changed_items = $this->recur["changed_occurrences"];
1186
-
1187
-			$rdata .= pack("v", count($changed_items));
1188
-
1189
-			foreach ($changed_items as $changed_item) {
1190
-				// Set start and end time of exception
1191
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1192
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1193
-				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1194
-
1195
-				// Bitmask
1196
-				$bitmask = 0;
1197
-
1198
-				// Check for changed strings
1199
-				if (isset($changed_item["subject"])) {
1200
-					$bitmask |= 1 << 0;
1201
-				}
1202
-				if (isset($changed_item["remind_before"])) {
1203
-					$bitmask |= 1 << 2;
1204
-				}
1205
-				if (isset($changed_item["reminder_set"])) {
1206
-					$bitmask |= 1 << 3;
1207
-				}
1208
-				if (isset($changed_item["location"])) {
1209
-					$bitmask |= 1 << 4;
1210
-				}
1211
-				if (isset($changed_item["busystatus"])) {
1212
-					$bitmask |= 1 << 5;
1213
-				}
1214
-				if (isset($changed_item["alldayevent"])) {
1215
-					$bitmask |= 1 << 7;
1216
-				}
1217
-				if (isset($changed_item["label"])) {
1218
-					$bitmask |= 1 << 8;
1219
-				}
1220
-
1221
-				$rdata .= pack("v", $bitmask);
1222
-
1223
-				// Set "subject"
1224
-				if (isset($changed_item["subject"])) {
1225
-					// convert UTF-8 to non-unicode blob string (US-ASCII?)
1226
-					$subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1227
-					$length = strlen($subject);
1228
-					$rdata .= pack("vv", $length + 1, $length);
1229
-					$rdata .= pack("a" . $length, $subject);
1230
-				}
1231
-				if (isset($changed_item["remind_before"])) {
1232
-					$rdata .= pack("V", $changed_item["remind_before"]);
1233
-				}
1234
-				if (isset($changed_item["reminder_set"])) {
1235
-					$rdata .= pack("V", $changed_item["reminder_set"]);
1236
-				}
1237
-				if (isset($changed_item["location"])) {
1238
-					$location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1239
-					$length = strlen($location);
1240
-					$rdata .= pack("vv", $length + 1, $length);
1241
-					$rdata .= pack("a" . $length, $location);
1242
-				}
1243
-				if (isset($changed_item["busystatus"])) {
1244
-					$rdata .= pack("V", $changed_item["busystatus"]);
1245
-				}
1246
-				if (isset($changed_item["alldayevent"])) {
1247
-					$rdata .= pack("V", $changed_item["alldayevent"]);
1248
-				}
1249
-				if (isset($changed_item["label"])) {
1250
-					$rdata .= pack("V", $changed_item["label"]);
1251
-				}
1252
-			}
1253
-
1254
-			$rdata .= pack("V", 0);
1255
-
1256
-			// write extended data
1257
-			foreach ($changed_items as $changed_item) {
1258
-				$rdata .= pack("V", 0);
1259
-				if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1260
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1261
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1262
-					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1263
-				}
1264
-
1265
-				if (isset($changed_item["subject"])) {
1266
-					$subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1267
-					$length = iconv_strlen($subject, "UCS-2LE");
1268
-					$rdata .= pack("v", $length);
1269
-					$rdata .= pack("a" . $length * 2, $subject);
1270
-				}
1271
-
1272
-				if (isset($changed_item["location"])) {
1273
-					$location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1274
-					$length = iconv_strlen($location, "UCS-2LE");
1275
-					$rdata .= pack("v", $length);
1276
-					$rdata .= pack("a" . $length * 2, $location);
1277
-				}
1278
-				if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1279
-					$rdata .= pack("V", 0);
1280
-				}
1281
-			}
1282
-
1283
-			$rdata .= pack("V", 0);
1284
-
1285
-			// Set props
1286
-			mapi_setprops($this->message, [$this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true]);
1287
-			if (isset($this->tz) && $this->tz) {
1288
-				$timezone = "GMT";
1289
-				if ($this->tz["timezone"] != 0) {
1290
-					// Create user readable timezone information
1291
-					$timezone = sprintf(
1292
-						"(GMT %s%02d:%02d)",
1293
-						(-$this->tz["timezone"] > 0 ? "+" : "-"),
1294
-						abs($this->tz["timezone"] / 60),
1295
-						abs($this->tz["timezone"] % 60)
1296
-					);
1297
-				}
1298
-				mapi_setprops($this->message, [$this->proptags["timezone_data"] => $this->getTimezoneData($this->tz),
1299
-					$this->proptags["timezone"] => $timezone, ]);
1300
-			}
1301
-		}
1302
-
1303
-		/**
1304
-		 * Function which converts a recurrence date timestamp to an Unix date timestamp.
1305
-		 *
1306
-		 * @author Steve Hardy
1307
-		 *
1308
-		 * @param int $rdate the date which will be converted
1309
-		 *
1310
-		 * @return int the converted date
1311
-		 */
1312
-		public function recurDataToUnixData($rdate) {
1313
-			return ($rdate - 194074560) * 60;
1314
-		}
1315
-
1316
-		/**
1317
-		 * Function which converts an Unix date timestamp to recurrence date timestamp.
1318
-		 *
1319
-		 * @author Johnny Biemans
1320
-		 *
1321
-		 * @param Date $date the date which will be converted
1322
-		 *
1323
-		 * @return int the converted date in minutes
1324
-		 */
1325
-		public function unixDataToRecurData($date) {
1326
-			return ($date / 60) + 194074560;
1327
-		}
1328
-
1329
-		/**
1330
-		 * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1331
-		 *
1332
-		 * @author Steve Hardy
1333
-		 *
1334
-		 * @param mixed $ts
1335
-		 */
1336
-		public function GetTZOffset($ts) {
1337
-			$Offset = date("O", $ts);
1338
-
1339
-			$Parity = $Offset < 0 ? -1 : 1;
1340
-			$Offset = $Parity * $Offset;
1341
-			$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1342
-
1343
-			return $Parity * $Offset;
1344
-		}
1345
-
1346
-		/**
1347
-		 * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1348
-		 *
1349
-		 * @author Steve Hardy
1350
-		 *
1351
-		 * @param Date $time
1352
-		 *
1353
-		 * @return Date GMT Time
1354
-		 */
1355
-		public function gmtime($time) {
1356
-			$TZOffset = $this->GetTZOffset($time);
1357
-
1358
-			$t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1359
-
1360
-			return localtime($t_time, 1);
1361
-		}
1362
-
1363
-		public function isLeapYear($year) {
1364
-			return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0);
1365
-		}
1366
-
1367
-		public function getMonthInSeconds($year, $month) {
1368
-			if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1369
-				$day = 31;
1370
-			}
1371
-			elseif (in_array($month, [4, 6, 9, 11])) {
1372
-				$day = 30;
1373
-			}
1374
-			else {
1375
-				$day = 28;
1376
-				if ($this->isLeapYear($year) == 1) {
1377
-					++$day;
1378
-				}
1379
-			}
1380
-
1381
-			return $day * 24 * 60 * 60;
1382
-		}
1383
-
1384
-		/**
1385
-		 * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.
1386
-		 *
1387
-		 * @param int $year
1388
-		 * @param int $month
1389
-		 * @param int $week
1390
-		 * @param int $day
1391
-		 * @param int $hour
1392
-		 *
1393
-		 * @return returns the timestamp of the given date, timezone independent
1394
-		 */
1395
-		public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) {
1396
-			// get first day of month
1397
-			$date = gmmktime(0, 0, 0, $month, 0, $year + 1900);
1398
-
1399
-			// get wday info
1400
-			$gmdate = $this->gmtime($date);
1401
-
1402
-			$date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
1403
-
1404
-			$date += $week * 7 * 24 * 60 * 60; // go to correct week nr
1405
-			$date += $day * 24 * 60 * 60;
1406
-			$date += $hour * 60 * 60;
1407
-
1408
-			$gmdate = $this->gmtime($date);
1409
-
1410
-			// if we are in the next month, then back up a week, because week '5' means
1411
-			// 'last week of month'
1412
-
1413
-			if ($month != $gmdate["tm_mon"] + 1) {
1414
-				$date -= 7 * 24 * 60 * 60;
1415
-			}
1416
-
1417
-			return $date;
1418
-		}
1419
-
1420
-		/**
1421
-		 * getTimezone gives the timezone offset (in minutes) of the given
1422
-		 * local date/time according to the given TZ info.
1423
-		 *
1424
-		 * @param mixed $tz
1425
-		 * @param mixed $date
1426
-		 */
1427
-		public function getTimezone($tz, $date) {
1428
-			// No timezone -> GMT (+0)
1429
-			if (!isset($tz["timezone"])) {
1430
-				return 0;
1431
-			}
1432
-
1433
-			$dst = false;
1434
-			$gmdate = $this->gmtime($date);
1435
-
1436
-			$dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
1437
-			$dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
1438
-
1439
-			if ($dststart <= $dstend) {
1440
-				// Northern hemisphere, eg DST is during Mar-Oct
1441
-				if ($date > $dststart && $date < $dstend) {
1442
-					$dst = true;
1443
-				}
1444
-			}
1445
-			else {
1446
-				// Southern hemisphere, eg DST is during Oct-Mar
1447
-				if ($date < $dstend || $date > $dststart) {
1448
-					$dst = true;
1449
-				}
1450
-			}
1451
-
1452
-			if ($dst) {
1453
-				return $tz["timezone"] + $tz["timezonedst"];
1454
-			}
1455
-
1456
-			return $tz["timezone"];
1457
-		}
1458
-
1459
-		/**
1460
-		 * getWeekNr() returns the week nr of the month (ie first week of february is 1).
1461
-		 *
1462
-		 * @param mixed $date
1463
-		 */
1464
-		public function getWeekNr($date) {
1465
-			$gmdate = gmtime($date);
1466
-			$gmdate["tm_mday"] = 0;
1467
-
1468
-			return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
1469
-		}
1470
-
1471
-		/**
1472
-		 * parseTimezone parses the timezone as specified in named property 0x8233
1473
-		 * in Outlook calendar messages. Returns the timezone in minutes negative
1474
-		 * offset (GMT +2:00 -> -120).
1475
-		 *
1476
-		 * @param mixed $data
1477
-		 */
1478
-		public function parseTimezone($data) {
1479
-			if (strlen($data) < 48) {
1480
-				return;
1481
-			}
1482
-
1483
-			return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
1484
-		}
1485
-
1486
-		public function getTimezoneData($tz) {
1487
-			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);
1488
-		}
1489
-
1490
-		/**
1491
-		 * createTimezone creates the timezone as specified in the named property 0x8233
1492
-		 * see also parseTimezone()
1493
-		 * $tz is an array with the timezone data.
1494
-		 *
1495
-		 * @param mixed $tz
1496
-		 */
1497
-		public function createTimezone($tz) {
1498
-			return pack(
1499
-				"lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
1500
-				$tz["timezone"],
1501
-				array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0,
1502
-				array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0,
1503
-				array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0,
1504
-				array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0,
1505
-				array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0,
1506
-				array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0,
1507
-				array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0
1508
-			);
1509
-		}
1510
-
1511
-		/**
1512
-		 * toGMT returns a timestamp in GMT time for the time and timezone given.
1513
-		 *
1514
-		 * @param mixed $tz
1515
-		 * @param mixed $date
1516
-		 */
1517
-		public function toGMT($tz, $date) {
1518
-			if (!isset($tz['timezone'])) {
1519
-				return $date;
1520
-			}
1521
-			$offset = $this->getTimezone($tz, $date);
1522
-
1523
-			return $date + $offset * 60;
1524
-		}
1525
-
1526
-		/**
1527
-		 * fromGMT returns a timestamp in the local timezone given from the GMT time given.
1528
-		 *
1529
-		 * @param mixed $tz
1530
-		 * @param mixed $date
1531
-		 */
1532
-		public function fromGMT($tz, $date) {
1533
-			$offset = $this->getTimezone($tz, $date);
1534
-
1535
-			return $date - $offset * 60;
1536
-		}
1537
-
1538
-		/**
1539
-		 * Function to get timestamp of the beginning of the day of the timestamp given.
1540
-		 *
1541
-		 * @param date $date
1542
-		 *
1543
-		 * @return date timestamp referring to same day but at 00:00:00
1544
-		 */
1545
-		public function dayStartOf($date) {
1546
-			$time1 = $this->gmtime($date);
1547
-
1548
-			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
1549
-		}
1550
-
1551
-		/**
1552
-		 * Function to get timestamp of the beginning of the month of the timestamp given.
1553
-		 *
1554
-		 * @param date $date
1555
-		 *
1556
-		 * @return date Timestamp referring to same month but on the first day, and at 00:00:00
1557
-		 */
1558
-		public function monthStartOf($date) {
1559
-			$time1 = $this->gmtime($date);
1560
-
1561
-			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
1562
-		}
1563
-
1564
-		/**
1565
-		 * Function to get timestamp of the beginning of the year of the timestamp given.
1566
-		 *
1567
-		 * @param date $date
1568
-		 *
1569
-		 * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00
1570
-		 */
1571
-		public function yearStartOf($date) {
1572
-			$time1 = $this->gmtime($date);
1573
-
1574
-			return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
1575
-		}
1576
-
1577
-		/**
1578
-		 * Function which returns the items in a given interval. This included expansion of the recurrence and
1579
-		 * processing of exceptions (modified and deleted).
1580
-		 *
1581
-		 * @param string $entryid       the entryid of the message
1582
-		 * @param array  $props         the properties of the message
1583
-		 * @param date   $start         start time of the interval (GMT)
1584
-		 * @param date   $end           end time of the interval (GMT)
1585
-		 * @param mixed  $limit
1586
-		 * @param mixed  $remindersonly
1587
-		 */
1588
-		public function getItems($start, $end, $limit = 0, $remindersonly = false) {
1589
-			$items = [];
1590
-
1591
-			if (isset($this->recur)) {
1592
-				// Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
1593
-				// exceptions are in range and have a reminder set
1594
-				if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
1595
-					// Sort exceptions by start time
1596
-					uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]);
1597
-
1598
-					// Loop through all changed exceptions
1599
-					foreach ($this->recur["changed_occurrences"] as $exception) {
1600
-						// Check reminder set
1601
-						if (!isset($exception["reminder"]) || $exception["reminder"] == false) {
1602
-							continue;
1603
-						}
1604
-
1605
-						// Convert to GMT
1606
-						$occstart = $this->toGMT($this->tz, $exception["start"]);
1607
-						$occend = $this->toGMT($this->tz, $exception["end"]);
1608
-
1609
-						// Check range criterium
1610
-						if ($occstart > $end || $occend < $start) {
1611
-							continue;
1612
-						}
1613
-
1614
-						// OK, add to items.
1615
-						array_push($items, $this->getExceptionProperties($exception));
1616
-						if ($limit && (count($items) == $limit)) {
1617
-							break;
1618
-						}
1619
-					}
1620
-
1621
-					uasort($items, [$this, "sortStarttime"]);
1622
-
1623
-					return $items;
1624
-				}
1625
-
1626
-				// From here on, the dates of the occurrences are calculated in local time, so the days we're looking
1627
-				// at are calculated from the local time dates of $start and $end
1628
-
1629
-				if ($this->recur['regen'] && isset($this->action['datecompleted'])) {
1630
-					$daystart = $this->dayStartOf($this->action['datecompleted']);
1631
-				}
1632
-				else {
1633
-					$daystart = $this->dayStartOf($this->recur["start"]);
1634
-				} // start on first day of occurrence
1635
-
1636
-				// Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
1637
-				// or the end of the recurrence, whichever comes first
1638
-				if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1639
-					$rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1640
-				}
1641
-				else {
1642
-					$rangeend = $end;
1643
-				}
1644
-
1645
-				$dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
1646
-
1647
-				// Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1648
-
1649
-				switch ($this->recur["type"]) {
1650
-				case 10:
1651
-					// Daily
1652
-					if ($this->recur["everyn"] <= 0) {
1653
-						$this->recur["everyn"] = 1440;
1654
-					}
1655
-
1656
-					if ($this->recur["subtype"] == 0) {
1657
-						// Every Nth day
1658
-						for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1659
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1660
-						}
1661
-						break;
1662
-					}
1663
-					// Every workday
1664
-					for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1665
-						$nowtime = $this->gmtime($now);
1666
-						if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1667
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1668
-						}
1669
-					}
1670
-					break;
1671
-
1672
-				case 11:
1673
-					// Weekly
1674
-					if ($this->recur["everyn"] <= 0) {
1675
-						$this->recur["everyn"] = 1;
1676
-					}
1677
-
1678
-					// If sliding flag is set then move to 'n' weeks
1679
-					if ($this->recur['regen']) {
1680
-						$daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1681
-					}
1682
-
1683
-					for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1684
-						if ($this->recur['regen']) {
1685
-							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1686
-
1687
-							break;
1688
-						}
1689
-						// Loop through the whole following week to the first occurrence of the week, add each day that is specified
1690
-						for ($wday = 0; $wday < 7; ++$wday) {
1691
-							$daynow = $now + $wday * 60 * 60 * 24;
1692
-							// checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1693
-							if ($daynow > $dayend) {
1694
-								continue;
1695
-							}
1696
-							$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1697
-							if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1698
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1699
-							}
1700
-						}
1701
-					}
1702
-					break;
1703
-
1704
-				case 12:
1705
-					// Monthly
1706
-					if ($this->recur["everyn"] <= 0) {
1707
-						$this->recur["everyn"] = 1;
1708
-					}
1709
-
1710
-					// Loop through all months from start to end of occurrence, starting at beginning of first month
1711
-					for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1712
-						if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1713
-							$difference = 1;
1714
-							if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1715
-								$difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1716
-							}
1717
-
1718
-							$daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1719
-							// checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1720
-							if ($daynow <= $dayend) {
1721
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1722
-							}
1723
-						}
1724
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1725
-							// Sanitize input
1726
-							if ($this->recur["weekdays"] == 0) {
1727
-								$this->recur["weekdays"] = 1;
1728
-							}
1729
-
1730
-							// If nday is not set to the last day in the month
1731
-							if ($this->recur["nday"] < 5) {
1732
-								// keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1733
-								$ndaycounter = 0;
1734
-								// Find matching weekday in this month
1735
-								for ($day = 0; $day < $this->daysInMonth($now, 1); ++$day) {
1736
-									$daynow = $now + $day * 60 * 60 * 24;
1737
-									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1738
-
1739
-									if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1740
-										++$ndaycounter;
1741
-									}
1742
-									// check the selected pattern is same as asked Nth weekday,If so set the firstday
1743
-									if ($this->recur["nday"] == $ndaycounter) {
1744
-										$firstday = $day;
1745
-
1746
-										break;
1747
-									}
1748
-								}
1749
-								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1750
-								$daynow = $now + $firstday * 60 * 60 * 24;
1751
-							}
1752
-							else {
1753
-								// Find last day in the month ($now is the firstday of the month)
1754
-								$NumDaysInMonth = $this->daysInMonth($now, 1);
1755
-								$daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1756
-
1757
-								$nowtime = $this->gmtime($daynow);
1758
-								while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1759
-									$daynow -= 86400;
1760
-									$nowtime = $this->gmtime($daynow);
1761
-								}
1762
-							}
1763
-
1764
-							/*
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
+         * @param resource $store      MAPI Message Store Object
46
+         * @param resource $message    the MAPI (appointment) message
47
+         * @param array    $properties the list of MAPI properties the message has
48
+         */
49
+        public function __construct($store, $message) {
50
+            $this->store = $store;
51
+
52
+            if (is_array($message)) {
53
+                $this->messageprops = $message;
54
+            }
55
+            else {
56
+                $this->message = $message;
57
+                $this->messageprops = mapi_getprops($this->message, $this->proptags);
58
+            }
59
+
60
+            if (isset($this->messageprops[$this->proptags["recurring_data"]])) {
61
+                // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
62
+                if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
63
+                    $this->getFullRecurrenceBlob();
64
+                }
65
+
66
+                $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
67
+            }
68
+            if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) {
69
+                $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
70
+            }
71
+        }
72
+
73
+        public function getRecurrence() {
74
+            return $this->recur;
75
+        }
76
+
77
+        public function getFullRecurrenceBlob() {
78
+            $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
79
+
80
+            $recurrBlob = '';
81
+            $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
82
+            $stat = mapi_stream_stat($stream);
83
+
84
+            for ($i = 0; $i < $stat['cb']; $i += 1024) {
85
+                $recurrBlob .= mapi_stream_read($stream, 1024);
86
+            }
87
+            if (!empty($recurrBlob)) {
88
+                $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
89
+            }
90
+        }
91
+
92
+        /**
93
+         * Function for parsing the Recurrence value of a Calendar item.
94
+         *
95
+         * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
96
+         * data to this function
97
+         *
98
+         * Returns a structure containing the data:
99
+         *
100
+         * type		- type of recurrence: day=10, week=11, month=12, year=13
101
+         * subtype	- type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (i.e. 2nd Tuesday and Wednesday)
102
+         * start	- Unix timestamp of first occurrence
103
+         * end		- Unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
104
+         * numoccur     - occurrences (may be very large when there is no end data)
105
+         *
106
+         * then, for each type:
107
+         *
108
+         * Daily:
109
+         *  everyn	- every [everyn] days in minutes
110
+         *  regen	- regenerating event (like tasks)
111
+         *
112
+         * Weekly:
113
+         *  everyn	- every [everyn] weeks in weeks
114
+         *  regen	- regenerating event (like tasks)
115
+         *  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
116
+         *
117
+         * Monthly:
118
+         *  everyn	- every [everyn] months
119
+         *  regen	- regenerating event (like tasks)
120
+         *
121
+         *  subtype 2:
122
+         *	  monthday - on day [monthday] of the month
123
+         *
124
+         *  subtype 3:
125
+         *	  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
126
+         *   nday	- on [nday]'th [weekdays] of the month
127
+         *
128
+         * Yearly:
129
+         *  everyn	- every [everyn] months (12, 24, 36, ...)
130
+         *  month	- in month [month] (although the month is encoded in minutes since the startning of the year ........)
131
+         *  regen	- regenerating event (like tasks)
132
+         *
133
+         *  subtype 2:
134
+         *   monthday - on day [monthday] of the month
135
+         *
136
+         *  subtype 3:
137
+         *   weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
138
+         *	  nday	- on [nday]'th [weekdays] of the month [month]
139
+         *
140
+         * @param string $rdata Binary string
141
+         *
142
+         * @return array recurrence data
143
+         */
144
+        public function parseRecurrence($rdata) {
145
+            if (strlen($rdata) < 10) {
146
+                return;
147
+            }
148
+
149
+            $ret["changed_occurrences"] = [];
150
+            $ret["deleted_occurrences"] = [];
151
+
152
+            $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
153
+
154
+            $ret["type"] = $data["rtype"];
155
+            $ret["subtype"] = $data["rtype2"];
156
+            $rdata = substr($rdata, 10);
157
+
158
+            switch ($data["rtype"]) {
159
+            case 0x0A:
160
+                // Daily
161
+                if (strlen($rdata) < 12) {
162
+                    return $ret;
163
+                }
164
+                $data = unpack("Vunknown/Veveryn/Vregen", $rdata);
165
+                $ret["everyn"] = $data["everyn"];
166
+                $ret["regen"] = $data["regen"];
167
+
168
+                switch ($ret["subtype"]) {
169
+                case 0:
170
+                    $rdata = substr($rdata, 12);
171
+                    break;
172
+
173
+                case 1:
174
+                    $rdata = substr($rdata, 16);
175
+                    break;
176
+                }
177
+                break;
178
+
179
+            case 0x0B:
180
+                // Weekly
181
+                if (strlen($rdata) < 16) {
182
+                    return $ret;
183
+                }
184
+
185
+                $data = unpack("Vconst1/Veveryn/Vregen", $rdata);
186
+                $rdata = substr($rdata, 12);
187
+
188
+                $ret["everyn"] = $data["everyn"];
189
+                $ret["regen"] = $data["regen"];
190
+                $ret["weekdays"] = 0;
191
+
192
+                if ($data["regen"] == 0) {
193
+                    $data = unpack("Vweekdays", $rdata);
194
+                    $rdata = substr($rdata, 4);
195
+                    $ret["weekdays"] = $data["weekdays"];
196
+                }
197
+                break;
198
+
199
+            case 0x0C:
200
+                // Monthly
201
+                if (strlen($rdata) < 16) {
202
+                    return $ret;
203
+                }
204
+
205
+                $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
206
+                $ret["everyn"] = $data["everyn"];
207
+                $ret["regen"] = $data["regen"];
208
+                if ($ret["subtype"] == 3) {
209
+                    $ret["weekdays"] = $data["monthday"];
210
+                }
211
+                else {
212
+                    $ret["monthday"] = $data["monthday"];
213
+                }
214
+
215
+                $rdata = substr($rdata, 16);
216
+                if ($ret["subtype"] == 3) {
217
+                    $data = unpack("Vnday", $rdata);
218
+                    $ret["nday"] = $data["nday"];
219
+                    $rdata = substr($rdata, 4);
220
+                }
221
+                break;
222
+
223
+            case 0x0D:
224
+                // Yearly
225
+                if (strlen($rdata) < 16) {
226
+                    return $ret;
227
+                }
228
+
229
+                $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
230
+                $ret["month"] = $data["month"];
231
+                $ret["everyn"] = $data["everyn"];
232
+                $ret["regen"] = $data["regen"];
233
+                if ($ret["subtype"] == 3) {
234
+                    $ret["weekdays"] = $data["monthday"];
235
+                }
236
+                else {
237
+                    $ret["monthday"] = $data["monthday"];
238
+                }
239
+
240
+                $rdata = substr($rdata, 16);
241
+                if ($ret["subtype"] == 3) {
242
+                    $data = unpack("Vnday", $rdata);
243
+                    $ret["nday"] = $data["nday"];
244
+                    $rdata = substr($rdata, 4);
245
+                }
246
+                break;
247
+            }
248
+
249
+            if (strlen($rdata) < 16) {
250
+                return $ret;
251
+            }
252
+
253
+            $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
254
+
255
+            $rdata = substr($rdata, 16);
256
+
257
+            $ret["term"] = $data["term"];
258
+            $ret["numoccur"] = $data["numoccur"];
259
+            $ret["numexcept"] = $data["numexcept"];
260
+
261
+            // exc_base_dates are *all* the base dates that have been either deleted or modified
262
+            $exc_base_dates = [];
263
+            for ($i = 0; $i < $ret["numexcept"]; ++$i) {
264
+                if (strlen($rdata) < 4) {
265
+                    // We shouldn't arrive here, because that implies
266
+                    // numexcept does not match the amount of data
267
+                    // which is available for the exceptions.
268
+                    return $ret;
269
+                }
270
+                $data = unpack("Vbasedate", $rdata);
271
+                $rdata = substr($rdata, 4);
272
+                $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
273
+            }
274
+
275
+            if (strlen($rdata) < 4) {
276
+                return $ret;
277
+            }
278
+            $data = unpack("Vnumexceptmod", $rdata);
279
+            $rdata = substr($rdata, 4);
280
+
281
+            $ret["numexceptmod"] = $data["numexceptmod"];
282
+
283
+            // exc_changed are the base dates of *modified* occurrences. exactly what is modified
284
+            // is in the attachments *and* in the data further down this function.
285
+            $exc_changed = [];
286
+            for ($i = 0; $i < $ret["numexceptmod"]; ++$i) {
287
+                if (strlen($rdata) < 4) {
288
+                    // We shouldn't arrive here, because that implies
289
+                    // numexceptmod does not match the amount of data
290
+                    // which is available for the exceptions.
291
+                    return $ret;
292
+                }
293
+                $data = unpack("Vstartdate", $rdata);
294
+                $rdata = substr($rdata, 4);
295
+                $exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
296
+            }
297
+
298
+            if (strlen($rdata) < 8) {
299
+                return $ret;
300
+            }
301
+            $data = unpack("Vstart/Vend", $rdata);
302
+            $rdata = substr($rdata, 8);
303
+
304
+            $ret["start"] = $this->recurDataToUnixData($data["start"]);
305
+            $ret["end"] = $this->recurDataToUnixData($data["end"]);
306
+
307
+            // this is where task recurrence stop
308
+            if (strlen($rdata) < 16) {
309
+                return $ret;
310
+            }
311
+            $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
312
+            $rdata = substr($rdata, 16);
313
+
314
+            $ret["startocc"] = $data["startmin"];
315
+            $ret["endocc"] = $data["endmin"];
316
+            $readerversion = $data["readerversion"];
317
+            $writerversion = $data["writerversion"];
318
+
319
+            $data = unpack("vnumber", $rdata);
320
+            $rdata = substr($rdata, 2);
321
+
322
+            $nexceptions = $data["number"];
323
+            $exc_changed_details = [];
324
+
325
+            // Parse n modified exceptions
326
+            for ($i = 0; $i < $nexceptions; ++$i) {
327
+                $item = [];
328
+
329
+                // Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
330
+                $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
331
+                $rdata = substr($rdata, 12);
332
+
333
+                // Convert recurtimestamp to Unix timestamp
334
+                $startdate = $this->recurDataToUnixData($data["startdate"]);
335
+                $enddate = $this->recurDataToUnixData($data["enddate"]);
336
+                $basedate = $this->recurDataToUnixData($data["basedate"]);
337
+
338
+                // Set the right properties
339
+                $item["basedate"] = $this->dayStartOf($basedate);
340
+                $item["start"] = $startdate;
341
+                $item["end"] = $enddate;
342
+
343
+                $data = unpack("vbitmask", $rdata);
344
+                $rdata = substr($rdata, 2);
345
+                $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
346
+
347
+                // Bitmask to verify what properties are changed
348
+                $bitmask = $data["bitmask"];
349
+
350
+                // ARO_SUBJECT: 0x0001
351
+                // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
352
+                if (($bitmask & (1 << 0))) {
353
+                    $data = unpack("vnull_length/vlength", $rdata);
354
+                    $rdata = substr($rdata, 4);
355
+
356
+                    $length = $data["length"];
357
+                    $item["subject"] = ""; // Normalized subject
358
+                    for ($j = 0; $j < $length && strlen($rdata); ++$j) {
359
+                        $data = unpack("Cchar", $rdata);
360
+                        $rdata = substr($rdata, 1);
361
+
362
+                        $item["subject"] .= chr($data["char"]);
363
+                    }
364
+                }
365
+
366
+                // ARO_MEETINGTYPE: 0x0002
367
+                if (($bitmask & (1 << 1))) {
368
+                    $rdata = substr($rdata, 4);
369
+                }
370
+                // Attendees modified: no data here (only in attachment)
371
+
372
+                // ARO_REMINDERDELTA: 0x0004
373
+                // Look for field: ReminderDelta (4b)
374
+                if (($bitmask & (1 << 2))) {
375
+                    $data = unpack("Vremind_before", $rdata);
376
+                    $rdata = substr($rdata, 4);
377
+
378
+                    $item["remind_before"] = $data["remind_before"];
379
+                }
380
+
381
+                // ARO_REMINDER: 0x0008
382
+                // Look field: ReminderSet (4b)
383
+                if (($bitmask & (1 << 3))) {
384
+                    $data = unpack("Vreminder_set", $rdata);
385
+                    $rdata = substr($rdata, 4);
386
+
387
+                    $item["reminder_set"] = $data["reminder_set"];
388
+                }
389
+
390
+                // ARO_LOCATION: 0x0010
391
+                // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
392
+                // Similar to ARO_SUBJECT above.
393
+                if (($bitmask & (1 << 4))) {
394
+                    $data = unpack("vnull_length/vlength", $rdata);
395
+                    $rdata = substr($rdata, 4);
396
+
397
+                    $item["location"] = "";
398
+
399
+                    $length = $data["length"];
400
+                    $data = substr($rdata, 0, $length);
401
+                    $rdata = substr($rdata, $length);
402
+
403
+                    $item["location"] .= $data;
404
+                }
405
+
406
+                // ARO_BUSYSTATUS: 0x0020
407
+                // Look for field: BusyStatus (4b)
408
+                if (($bitmask & (1 << 5))) {
409
+                    $data = unpack("Vbusystatus", $rdata);
410
+                    $rdata = substr($rdata, 4);
411
+
412
+                    $item["busystatus"] = $data["busystatus"];
413
+                }
414
+
415
+                // ARO_ATTACHMENT: 0x0040
416
+                if (($bitmask & (1 << 6))) {
417
+                    // no data: RESERVED
418
+                    $rdata = substr($rdata, 4);
419
+                }
420
+
421
+                // ARO_SUBTYPE: 0x0080
422
+                // Look for field: SubType (4b). Determines whether it is an allday event.
423
+                if (($bitmask & (1 << 7))) {
424
+                    $data = unpack("Vallday", $rdata);
425
+                    $rdata = substr($rdata, 4);
426
+
427
+                    $item["alldayevent"] = $data["allday"];
428
+                }
429
+
430
+                // ARO_APPTCOLOR: 0x0100
431
+                // Look for field: AppointmentColor (4b)
432
+                if (($bitmask & (1 << 8))) {
433
+                    $data = unpack("Vlabel", $rdata);
434
+                    $rdata = substr($rdata, 4);
435
+
436
+                    $item["label"] = $data["label"];
437
+                }
438
+
439
+                // ARO_EXCEPTIONAL_BODY: 0x0200
440
+                if (($bitmask & (1 << 9)))
441
+                    /* nothing */; // Notes or Attachments modified: no data here (only in attachment)
442
+
443
+                array_push($exc_changed_details, $item);
444
+            }
445
+
446
+            /**
447
+             * We now have $exc_changed, $exc_base_dates and $exc_changed_details
448
+             * We will ignore $exc_changed, as this information is available in $exc_changed_details
449
+             * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
450
+             * has been deleted.
451
+             */
452
+
453
+            // Find deleted occurrences
454
+            $deleted_occurrences = [];
455
+
456
+            foreach ($exc_base_dates as $base_date) {
457
+                $found = false;
458
+
459
+                foreach ($exc_changed_details as $details) {
460
+                    if ($details["basedate"] == $base_date) {
461
+                        $found = true;
462
+
463
+                        break;
464
+                    }
465
+                }
466
+                if (!$found) {
467
+                    // item was not in exc_changed_details, so it must be deleted
468
+                    $deleted_occurrences[] = $base_date;
469
+                }
470
+            }
471
+
472
+            $ret["deleted_occurrences"] = $deleted_occurrences;
473
+            $ret["changed_occurrences"] = $exc_changed_details;
474
+
475
+            // enough data for normal exception (no extended data)
476
+            if (strlen($rdata) < 16) {
477
+                return $ret;
478
+            }
479
+
480
+            $data = unpack("Vreservedsize", $rdata);
481
+            $rdata = substr($rdata, 4 + $data["reservedsize"]);
482
+
483
+            for ($i = 0; $i < $nexceptions; ++$i) {
484
+                // subject and location in UCS-2 to UTF-8
485
+                if ($writerversion >= 0x3009) {
486
+                    $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
487
+                    $rdata = substr($rdata, 4 + $data["size"]);
488
+                }
489
+
490
+                $data = unpack("Vreservedsize", $rdata);
491
+                $rdata = substr($rdata, 4 + $data["reservedsize"]);
492
+
493
+                // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
494
+                if ($exc_changed_details[$i]["bitmask"] & 0x11) {
495
+                    $data = unpack("Vstart/Vend/Vorig", $rdata);
496
+                    $rdata = substr($rdata, 4 * 3);
497
+
498
+                    $exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
499
+                    $exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
500
+                    $exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
501
+                }
502
+
503
+                // ARO_SUBJECT
504
+                if ($exc_changed_details[$i]["bitmask"] & 0x01) {
505
+                    // decode UCS-2 string to UTF-8
506
+                    $data = unpack("vlength", $rdata);
507
+                    $rdata = substr($rdata, 2);
508
+                    $length = $data["length"];
509
+                    $data = substr($rdata, 0, $length * 2);
510
+                    $rdata = substr($rdata, $length * 2);
511
+                    $subject = iconv("UCS-2LE", "UTF-8", $data);
512
+                    // replace subject with unicode subject
513
+                    $exc_changed_details[$i]["subject"] = $subject;
514
+                }
515
+
516
+                // ARO_LOCATION
517
+                if ($exc_changed_details[$i]["bitmask"] & 0x10) {
518
+                    // decode UCS-2 string to UTF-8
519
+                    $data = unpack("vlength", $rdata);
520
+                    $rdata = substr($rdata, 2);
521
+                    $length = $data["length"];
522
+                    $data = substr($rdata, 0, $length * 2);
523
+                    $rdata = substr($rdata, $length * 2);
524
+                    $location = iconv("UCS-2LE", "UTF-8", $data);
525
+                    // replace subject with unicode subject
526
+                    $exc_changed_details[$i]["location"] = $location;
527
+                }
528
+
529
+                // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
530
+                if ($exc_changed_details[$i]["bitmask"] & 0x11) {
531
+                    $data = unpack("Vreservedsize", $rdata);
532
+                    $rdata = substr($rdata, 4 + $data["reservedsize"]);
533
+                }
534
+            }
535
+
536
+            // update with extended data
537
+            $ret["changed_occurrences"] = $exc_changed_details;
538
+
539
+            return $ret;
540
+        }
541
+
542
+        /**
543
+         * Saves the recurrence data to the recurrence property.
544
+         *
545
+         * @param array $properties the recurrence data
546
+         *
547
+         * @return string binary string
548
+         */
549
+        public function saveRecurrence() {
550
+            // Only save if a message was passed
551
+            if (!isset($this->message)) {
552
+                return;
553
+            }
554
+
555
+            // Abort if no recurrence was set
556
+            if (!isset($this->recur["type"]) && !isset($this->recur["subtype"])) {
557
+                return;
558
+            }
559
+            if (!isset($this->recur["start"]) && !isset($this->recur["end"])) {
560
+                return;
561
+            }
562
+            if (!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) {
563
+                return;
564
+            }
565
+
566
+            $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
567
+
568
+            $weekstart = 1; // monday
569
+            $forwardcount = 0;
570
+            $restocc = 0;
571
+            $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday)
572
+
573
+            $term = (int) $this->recur["type"];
574
+
575
+            switch ($term) {
576
+            case 0x0A:
577
+                // Daily
578
+                if (!isset($this->recur["everyn"])) {
579
+                    return;
580
+                }
581
+
582
+                if ($this->recur["subtype"] == 1) {
583
+                    // Daily every workday
584
+                    $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
585
+                }
586
+                else {
587
+                    // Daily every N days (everyN in minutes)
588
+                    $everyn = ((int) $this->recur["everyn"]) / 1440;
589
+                    // Calc first occ
590
+                    $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
591
+                    $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
592
+                }
593
+                break;
594
+
595
+            case 0x0B:
596
+                // Weekly
597
+                if (!isset($this->recur["everyn"])) {
598
+                    return;
599
+                }
600
+                if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
601
+                    return;
602
+                }
603
+
604
+                // No need to calculate startdate if sliding flag was set.
605
+                if (!$this->recur['regen']) {
606
+                    // Calculate start date of recurrence
607
+
608
+                    // Find the first day that matches one of the weekdays selected
609
+                    $daycount = 0;
610
+                    $dayskip = -1;
611
+                    for ($j = 0; $j < 7; ++$j) {
612
+                        if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
613
+                            if ($dayskip == -1) {
614
+                                $dayskip = $j;
615
+                            }
616
+                            ++$daycount;
617
+                        }
618
+                    }
619
+
620
+                    // $dayskip is the number of days to skip from the startdate until the first occurrence
621
+                    // $daycount is the number of days per week that an occurrence occurs
622
+                    $weekskip = 0;
623
+                    if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) {
624
+                        $weekskip = 1;
625
+                    }
626
+
627
+                    // Check if the recurrence ends after a number of occurrences, in that case we must calculate the
628
+                    // remaining occurrences based on the start of the recurrence.
629
+                    if (((int) $this->recur["term"]) == 0x22) {
630
+                        // $weekskip is the amount of weeks to skip from the startdate before the first occurrence
631
+                        // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
632
+                        // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
633
+                        // (eg when numoccur = 2, and daycount = 1)
634
+                        $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount);
635
+
636
+                        // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
637
+                        // for the occurrence on the first day
638
+                        $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1;
639
+
640
+                        // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
641
+                        $forwardcount *= (int) $this->recur["everyn"];
642
+                    }
643
+
644
+                    // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
645
+                    $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60);
646
+                }
647
+
648
+                // Calc first occ
649
+                $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60);
650
+                $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
651
+
652
+                if ($this->recur["regen"]) {
653
+                    $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
654
+                }
655
+                else {
656
+                    $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
657
+                }
658
+                break;
659
+
660
+            case 0x0C:
661
+                // Monthly
662
+            case 0x0D:
663
+                // Yearly
664
+                if (!isset($this->recur["everyn"])) {
665
+                    return;
666
+                }
667
+                if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) {
668
+                    return;
669
+                }
670
+                if ($term == 0x0C /* monthly */) {
671
+                    $everyn = (int) $this->recur["everyn"];
672
+                }
673
+                else {
674
+                    $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
675
+                }
676
+
677
+                // Get montday/month/year of original start
678
+                $curmonthday = gmdate("j", (int) $this->recur["start"]);
679
+                $curyear = gmdate("Y", (int) $this->recur["start"]);
680
+                $curmonth = gmdate("n", (int) $this->recur["start"]);
681
+
682
+                // Check if the recurrence ends after a number of occurrences, in that case we must calculate the
683
+                // remaining occurrences based on the start of the recurrence.
684
+                if (((int) $this->recur["term"]) == 0x22) {
685
+                    // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
686
+                    // one to make sure there are always at least one occurrence left)
687
+                    $forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn);
688
+                }
689
+
690
+                // Get month for yearly on D'th day of month M
691
+                if ($term == 0x0D /* yearly */) {
692
+                    $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1;
693
+                } // 1=jan, 2=feb, eg
694
+
695
+                switch ((int) $this->recur["subtype"]) {
696
+                // on D day of every M month
697
+                case 2:
698
+                    if (!isset($this->recur["monthday"])) {
699
+                        return;
700
+                    }
701
+                    // Recalc startdate
702
+                    // Set on the right begin day
703
+                    // Go the beginning of the month
704
+                    $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60;
705
+                    // Go the the correct month day
706
+                    $this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60;
707
+
708
+                    // If the previous calculation gave us a start date *before* the original start date, then we need to skip to the next occurrence
709
+                    if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) ||
710
+                        ($term == 0x0D /* yearly */ && ($selmonth < $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) {
711
+                        if ($term == 0x0D /* yearly */) {
712
+                            $count = ($everyn - ($curmonth - $selmonth));
713
+                        } // Yearly, go to next occurrence in 'everyn' months minus difference in first occurrence and original date
714
+                        else {
715
+                            $count = $everyn;
716
+                        } // Monthly, go to next occurrence in 'everyn' months
717
+
718
+                        // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
719
+                        for ($i = 0; $i < $count; ++$i) {
720
+                            $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
721
+                            if ($curmonth == 12) {
722
+                                ++$curyear;
723
+                                $curmonth = 0;
724
+                            }
725
+                            ++$curmonth;
726
+                        }
727
+                    }
728
+
729
+                    // "start" is now pointing to the first occurrence, except that it will overshoot if the
730
+                    // month in which it occurs has less days than specified as the day of the month. So 31st
731
+                    // of each month will overshoot in february (29 days). We compensate for that by checking
732
+                    // if the day of the month we got is wrong, and then back up to the last day of the previous
733
+                    // month.
734
+                    if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
735
+                        gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) {
736
+                        $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60;
737
+                    }
738
+
739
+                    // "start" is now the first occurrence
740
+                    if ($term == 0x0C /* monthly */) {
741
+                        // Calc first occ
742
+                        $monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
743
+
744
+                        $firstocc = 0;
745
+                        for ($i = 0; $i < $monthIndex; ++$i) {
746
+                            $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
747
+                        }
748
+                        $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
749
+                    }
750
+                    else {
751
+                        // Calc first occ
752
+                        $firstocc = 0;
753
+                        $monthIndex = (int) gmdate("n", $this->recur["start"]);
754
+                        for ($i = 1; $i < $monthIndex; ++$i) {
755
+                            $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
756
+                        }
757
+                        $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
758
+                    }
759
+                    break;
760
+
761
+                case 3:
762
+                    // monthly: on Nth weekday of every M month
763
+                    // yearly: on Nth weekday of M month
764
+                    if (!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) {
765
+                        return;
766
+                    }
767
+
768
+                    $weekdays = (int) $this->recur["weekdays"];
769
+                    $nday = (int) $this->recur["nday"];
770
+
771
+                    // Calc startdate
772
+                    $monthbegindow = (int) $this->recur["start"];
773
+
774
+                    if ($nday == 5) {
775
+                        // Set date on the last day of the last month
776
+                        $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
777
+                    }
778
+                    else {
779
+                        // Set on the first day of the month
780
+                        $monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
781
+                    }
782
+
783
+                    if ($term == 0x0D /* yearly */) {
784
+                        // Set on right month
785
+                        if ($selmonth < $curmonth) {
786
+                            $tmp = 12 - $curmonth + $selmonth;
787
+                        }
788
+                        else {
789
+                            $tmp = ($selmonth - $curmonth);
790
+                        }
791
+
792
+                        for ($i = 0; $i < $tmp; ++$i) {
793
+                            $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
794
+                            if ($curmonth == 12) {
795
+                                ++$curyear;
796
+                                $curmonth = 0;
797
+                            }
798
+                            ++$curmonth;
799
+                        }
800
+                    }
801
+                    else {
802
+                        // Check or you exist in the right month
803
+                        for ($i = 0; $i < 7; ++$i) {
804
+                            if ($nday == 5 && (1 << ((gmdate("w", $monthbegindow) - $i) % 7)) & $weekdays) {
805
+                                $day = gmdate("j", $monthbegindow) - $i;
806
+
807
+                                break;
808
+                            }
809
+                            if ($nday != 5 && (1 << ((gmdate("w", $monthbegindow) + $i) % 7)) & $weekdays) {
810
+                                $day = (($nday - 1) * 7) + ($i + 1);
811
+
812
+                                break;
813
+                            }
814
+                        }
815
+
816
+                        // Goto the next X month
817
+                        if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) {
818
+                            if ($nday == 5) {
819
+                                $monthbegindow += 24 * 60 * 60;
820
+                                if ($curmonth == 12) {
821
+                                    ++$curyear;
822
+                                    $curmonth = 0;
823
+                                }
824
+                                ++$curmonth;
825
+                            }
826
+
827
+                            for ($i = 0; $i < $everyn; ++$i) {
828
+                                $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
829
+                                if ($curmonth == 12) {
830
+                                    ++$curyear;
831
+                                    $curmonth = 0;
832
+                                }
833
+                                ++$curmonth;
834
+                            }
835
+                            if ($nday == 5) {
836
+                                $monthbegindow -= 24 * 60 * 60;
837
+                            }
838
+                        }
839
+                    }
840
+
841
+                    // FIXME: weekstart?
842
+
843
+                    $day = 0;
844
+                    // Set start on the right day
845
+                    for ($i = 0; $i < 7; ++$i) {
846
+                        if ($nday == 5 && (1 << ((gmdate("w", $monthbegindow) - $i) % 7)) & $weekdays) {
847
+                            $day = $i;
848
+
849
+                            break;
850
+                        }
851
+                        if ($nday != 5 && (1 << ((gmdate("w", $monthbegindow) + $i) % 7)) & $weekdays) {
852
+                            $day = ($nday - 1) * 7 + ($i + 1);
853
+
854
+                            break;
855
+                        }
856
+                    }
857
+                    if ($nday == 5) {
858
+                        $monthbegindow -= $day * 24 * 60 * 60;
859
+                    }
860
+                    else {
861
+                        $monthbegindow += ($day - 1) * 24 * 60 * 60;
862
+                    }
863
+
864
+                    $firstocc = 0;
865
+                    if ($term == 0x0C /* monthly */) {
866
+                        // Calc first occ
867
+                        $monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn;
868
+                        for ($i = 0; $i < $monthIndex; ++$i) {
869
+                            $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
870
+                        }
871
+                        $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
872
+                        break;
873
+                    }
874
+                    // Calc first occ
875
+                    $monthIndex = (int) gmdate("n", $this->recur["start"]);
876
+                    for ($i = 1; $i < $monthIndex; ++$i) {
877
+                        $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60;
878
+                    }
879
+                    $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
880
+                    break;
881
+                }
882
+                break;
883
+            }
884
+
885
+            if (!isset($this->recur["term"])) {
886
+                return;
887
+            }
888
+
889
+            // Terminate
890
+            $term = (int) $this->recur["term"];
891
+            $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
892
+
893
+            switch ($term) {
894
+            // After the given enddate
895
+            case 0x21:
896
+                $rdata .= pack("V", 10);
897
+                break;
898
+            // After a number of times
899
+            case 0x22:
900
+                if (!isset($this->recur["numoccur"])) {
901
+                    return;
902
+                }
903
+                $rdata .= pack("V", (int) $this->recur["numoccur"]);
904
+                break;
905
+            // Never ends
906
+            case 0x23:
907
+                $rdata .= pack("V", 0);
908
+                break;
909
+            }
910
+
911
+            // Strange little thing for the recurrence type "every workday"
912
+            if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
913
+                $rdata .= pack("V", 1);
914
+            }
915
+            else { // Other recurrences
916
+                $rdata .= pack("V", 0);
917
+            }
918
+
919
+            // Exception data
920
+
921
+            // Get all exceptions
922
+            $deleted_items = $this->recur["deleted_occurrences"];
923
+            $changed_items = $this->recur["changed_occurrences"];
924
+
925
+            // Merge deleted and changed items into one list
926
+            $items = $deleted_items;
927
+
928
+            foreach ($changed_items as $changed_item) {
929
+                array_push($items, $changed_item["basedate"]);
930
+            }
931
+
932
+            sort($items);
933
+
934
+            // Add the merged list in to the rdata
935
+            $rdata .= pack("V", count($items));
936
+            foreach ($items as $item) {
937
+                $rdata .= pack("V", $this->unixDataToRecurData($item));
938
+            }
939
+
940
+            // Loop through the changed exceptions (not deleted)
941
+            $rdata .= pack("V", count($changed_items));
942
+            $items = [];
943
+
944
+            foreach ($changed_items as $changed_item) {
945
+                $items[] = $this->dayStartOf($changed_item["start"]);
946
+            }
947
+
948
+            sort($items);
949
+
950
+            // Add the changed items list int the rdata
951
+            foreach ($items as $item) {
952
+                $rdata .= pack("V", $this->unixDataToRecurData($item));
953
+            }
954
+
955
+            // Set start date
956
+            $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
957
+
958
+            // Set enddate
959
+            switch ($term) {
960
+            // After the given enddate
961
+            case 0x21:
962
+                $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
963
+                break;
964
+            // After a number of times
965
+            case 0x22:
966
+                // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
967
+                $occenddate = (int) $this->recur["start"];
968
+
969
+                switch ((int) $this->recur["type"]) {
970
+                case 0x0A: // daily
971
+                    if ($this->recur["subtype"] != 1) {
972
+                        // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
973
+                        $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1)));
974
+                        break;
975
+                    }
976
+                    // Daily every workday
977
+                    $restocc = (int) $this->recur["numoccur"];
978
+                    // Get starting weekday
979
+                    $nowtime = $this->gmtime($occenddate);
980
+                    $j = $nowtime["tm_wday"];
981
+
982
+                    while (1) {
983
+                        if (($j % 7) > 0 && ($j % 7) < 6) {
984
+                            --$restocc;
985
+                        }
986
+                        ++$j;
987
+                        if ($restocc <= 0) {
988
+                            break;
989
+                        }
990
+                        $occenddate += 24 * 60 * 60;
991
+                    }
992
+                    break;
993
+
994
+                case 0x0B: // weekly
995
+                    // Needed values
996
+                    // $forwardcount - number of weeks we can skip forward
997
+                    // $restocc - number of remaining occurrences after the week skip
998
+
999
+                    // Add the weeks till the last item
1000
+                    $occenddate += ($forwardcount * 7 * 24 * 60 * 60);
1001
+                    $dayofweek = gmdate("w", $occenddate);
1002
+
1003
+                    // Loop through the last occurrences until we have had them all
1004
+                    for ($j = 1; $restocc > 0; ++$j) {
1005
+                        // Jump to the next week (which may be N weeks away) when going over the week boundary
1006
+                        if ((($dayofweek + $j) % 7) == $weekstart) {
1007
+                            $occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60;
1008
+                        }
1009
+
1010
+                        // If this is a matching day, once less occurrence to process
1011
+                        if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) {
1012
+                            --$restocc;
1013
+                        }
1014
+
1015
+                        // Next day
1016
+                        $occenddate += 24 * 60 * 60;
1017
+                    }
1018
+                    break;
1019
+
1020
+                case 0x0C: // monthly
1021
+                case 0x0D: // yearly
1022
+                    $curyear = gmdate("Y", (int) $this->recur["start"]);
1023
+                    $curmonth = gmdate("n", (int) $this->recur["start"]);
1024
+                    // $forwardcount = months
1025
+
1026
+                    switch ((int) $this->recur["subtype"]) {
1027
+                    case 2: // on D day of every M month
1028
+                        while ($forwardcount > 0) {
1029
+                            $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1030
+                            if ($curmonth >= 12) {
1031
+                                $curmonth = 1;
1032
+                                ++$curyear;
1033
+                            }
1034
+                            else {
1035
+                                ++$curmonth;
1036
+                            }
1037
+                            --$forwardcount;
1038
+                        }
1039
+
1040
+                        // compensation between 28 and 31
1041
+                        if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 &&
1042
+                            gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1043
+                            if (gmdate("j", $occenddate) < 28) {
1044
+                                $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1045
+                            }
1046
+                            else {
1047
+                                $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1048
+                            }
1049
+                        }
1050
+                        break;
1051
+
1052
+                    case 3: // on Nth weekday of every M month
1053
+                        $nday = (int) $this->recur["nday"]; // 1 tot 5
1054
+                        $weekdays = (int) $this->recur["weekdays"];
1055
+                        while ($forwardcount > 0) {
1056
+                            $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1057
+                            if ($curmonth >= 12) {
1058
+                                $curmonth = 1;
1059
+                                ++$curyear;
1060
+                            }
1061
+                            else {
1062
+                                ++$curmonth;
1063
+                            }
1064
+                            --$forwardcount;
1065
+                        }
1066
+                        if ($nday == 5) {
1067
+                            // Set date on the last day of the last month
1068
+                            $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1069
+                        }
1070
+                        else {
1071
+                            // Set date on the first day of the last month
1072
+                            $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1073
+                        }
1074
+
1075
+                        for ($i = 0; $i < 7; ++$i) {
1076
+                            if ($nday == 5 && (1 << ((gmdate("w", $occenddate) - $i) % 7)) & $weekdays) {
1077
+                                $occenddate -= $i * 24 * 60 * 60;
1078
+
1079
+                                break;
1080
+                            }
1081
+                            if ($nday != 5 && (1 << ((gmdate("w", $occenddate) + $i) % 7)) & $weekdays) {
1082
+                                $occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60;
1083
+
1084
+                                break;
1085
+                            }
1086
+                        }
1087
+                        break; // case 3:
1088
+                    }
1089
+                    break;
1090
+                }
1091
+
1092
+                if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) {
1093
+                    $occenddate = PHP_INT_MAX;
1094
+                }
1095
+
1096
+                $this->recur["end"] = $occenddate;
1097
+                $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
1098
+                break;
1099
+            // Never ends
1100
+            case 0x23:
1101
+            default:
1102
+                $this->recur["end"] = 0x7FFFFFFF; // max date -> 2038
1103
+                $rdata .= pack("V", 0x5AE980DF);
1104
+                break;
1105
+            }
1106
+
1107
+            // UTC date
1108
+            $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1109
+            $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1110
+
1111
+            // utc date+time
1112
+            $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart;
1113
+            $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1114
+
1115
+            // update reminder time
1116
+            mapi_setprops($this->message, [$this->proptags["reminder_time"] => $utcfirstoccstartdatetime]);
1117
+
1118
+            // update first occurrence date
1119
+            mapi_setprops($this->message, [$this->proptags["startdate"] => $utcfirstoccstartdatetime]);
1120
+            mapi_setprops($this->message, [$this->proptags["duedate"] => $utcfirstoccenddatetime]);
1121
+            mapi_setprops($this->message, [$this->proptags["commonstart"] => $utcfirstoccstartdatetime]);
1122
+            mapi_setprops($this->message, [$this->proptags["commonend"] => $utcfirstoccenddatetime]);
1123
+
1124
+            // Set Outlook properties, if it is an appointment
1125
+            if (isset($this->recur["message_class"]) && $this->recur["message_class"] == "IPM.Appointment") {
1126
+                // update real begin and real end date
1127
+                mapi_setprops($this->message, [$this->proptags["startdate_recurring"] => $utcstart]);
1128
+                mapi_setprops($this->message, [$this->proptags["enddate_recurring"] => $utcend]);
1129
+
1130
+                // recurrencetype
1131
+                // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1132
+                mapi_setprops($this->message, [$this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9]);
1133
+
1134
+                // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1135
+                mapi_setprops($this->message, [$this->proptags["side_effects"] => 369]);
1136
+            }
1137
+            else {
1138
+                mapi_setprops($this->message, [$this->proptags["side_effects"] => 3441]);
1139
+            }
1140
+
1141
+            // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
1142
+            // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
1143
+            // to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to
1144
+            // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
1145
+            // with the reminder flag set.
1146
+            $reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]);
1147
+            if (isset($reminderprops[$this->proptags["reminder_minutes"]])) {
1148
+                $occ = false;
1149
+                $occurrences = $this->getItems(time(), 0x7FF00000, 3, true);
1150
+
1151
+                for ($i = 0, $len = count($occurrences); $i < $len; ++$i) {
1152
+                    // This will actually also give us appointments that have already started, but not yet ended. Since we want the next
1153
+                    // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
1154
+                    // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
1155
+                    // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
1156
+                    // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
1157
+
1158
+                    if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
1159
+                        $occ = $occurrences[$i];
1160
+
1161
+                        break;
1162
+                    }
1163
+                }
1164
+
1165
+                if ($occ) {
1166
+                    mapi_setprops($this->message, [$this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60)]);
1167
+                }
1168
+                else {
1169
+                    // Last reminder passed, no reminders any more.
1170
+                    mapi_setprops($this->message, [$this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7FF00000]);
1171
+                }
1172
+            }
1173
+
1174
+            // Default data
1175
+            // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
1176
+            $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
1177
+
1178
+            if (isset($this->recur["startocc"], $this->recur["endocc"])) {
1179
+                // Set start and endtime in minutes
1180
+                $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1181
+            }
1182
+
1183
+            // Detailed exception data
1184
+
1185
+            $changed_items = $this->recur["changed_occurrences"];
1186
+
1187
+            $rdata .= pack("v", count($changed_items));
1188
+
1189
+            foreach ($changed_items as $changed_item) {
1190
+                // Set start and end time of exception
1191
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1192
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1193
+                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1194
+
1195
+                // Bitmask
1196
+                $bitmask = 0;
1197
+
1198
+                // Check for changed strings
1199
+                if (isset($changed_item["subject"])) {
1200
+                    $bitmask |= 1 << 0;
1201
+                }
1202
+                if (isset($changed_item["remind_before"])) {
1203
+                    $bitmask |= 1 << 2;
1204
+                }
1205
+                if (isset($changed_item["reminder_set"])) {
1206
+                    $bitmask |= 1 << 3;
1207
+                }
1208
+                if (isset($changed_item["location"])) {
1209
+                    $bitmask |= 1 << 4;
1210
+                }
1211
+                if (isset($changed_item["busystatus"])) {
1212
+                    $bitmask |= 1 << 5;
1213
+                }
1214
+                if (isset($changed_item["alldayevent"])) {
1215
+                    $bitmask |= 1 << 7;
1216
+                }
1217
+                if (isset($changed_item["label"])) {
1218
+                    $bitmask |= 1 << 8;
1219
+                }
1220
+
1221
+                $rdata .= pack("v", $bitmask);
1222
+
1223
+                // Set "subject"
1224
+                if (isset($changed_item["subject"])) {
1225
+                    // convert UTF-8 to non-unicode blob string (US-ASCII?)
1226
+                    $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1227
+                    $length = strlen($subject);
1228
+                    $rdata .= pack("vv", $length + 1, $length);
1229
+                    $rdata .= pack("a" . $length, $subject);
1230
+                }
1231
+                if (isset($changed_item["remind_before"])) {
1232
+                    $rdata .= pack("V", $changed_item["remind_before"]);
1233
+                }
1234
+                if (isset($changed_item["reminder_set"])) {
1235
+                    $rdata .= pack("V", $changed_item["reminder_set"]);
1236
+                }
1237
+                if (isset($changed_item["location"])) {
1238
+                    $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1239
+                    $length = strlen($location);
1240
+                    $rdata .= pack("vv", $length + 1, $length);
1241
+                    $rdata .= pack("a" . $length, $location);
1242
+                }
1243
+                if (isset($changed_item["busystatus"])) {
1244
+                    $rdata .= pack("V", $changed_item["busystatus"]);
1245
+                }
1246
+                if (isset($changed_item["alldayevent"])) {
1247
+                    $rdata .= pack("V", $changed_item["alldayevent"]);
1248
+                }
1249
+                if (isset($changed_item["label"])) {
1250
+                    $rdata .= pack("V", $changed_item["label"]);
1251
+                }
1252
+            }
1253
+
1254
+            $rdata .= pack("V", 0);
1255
+
1256
+            // write extended data
1257
+            foreach ($changed_items as $changed_item) {
1258
+                $rdata .= pack("V", 0);
1259
+                if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1260
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1261
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1262
+                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1263
+                }
1264
+
1265
+                if (isset($changed_item["subject"])) {
1266
+                    $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1267
+                    $length = iconv_strlen($subject, "UCS-2LE");
1268
+                    $rdata .= pack("v", $length);
1269
+                    $rdata .= pack("a" . $length * 2, $subject);
1270
+                }
1271
+
1272
+                if (isset($changed_item["location"])) {
1273
+                    $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1274
+                    $length = iconv_strlen($location, "UCS-2LE");
1275
+                    $rdata .= pack("v", $length);
1276
+                    $rdata .= pack("a" . $length * 2, $location);
1277
+                }
1278
+                if (isset($changed_item["subject"]) || isset($changed_item["location"])) {
1279
+                    $rdata .= pack("V", 0);
1280
+                }
1281
+            }
1282
+
1283
+            $rdata .= pack("V", 0);
1284
+
1285
+            // Set props
1286
+            mapi_setprops($this->message, [$this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true]);
1287
+            if (isset($this->tz) && $this->tz) {
1288
+                $timezone = "GMT";
1289
+                if ($this->tz["timezone"] != 0) {
1290
+                    // Create user readable timezone information
1291
+                    $timezone = sprintf(
1292
+                        "(GMT %s%02d:%02d)",
1293
+                        (-$this->tz["timezone"] > 0 ? "+" : "-"),
1294
+                        abs($this->tz["timezone"] / 60),
1295
+                        abs($this->tz["timezone"] % 60)
1296
+                    );
1297
+                }
1298
+                mapi_setprops($this->message, [$this->proptags["timezone_data"] => $this->getTimezoneData($this->tz),
1299
+                    $this->proptags["timezone"] => $timezone, ]);
1300
+            }
1301
+        }
1302
+
1303
+        /**
1304
+         * Function which converts a recurrence date timestamp to an Unix date timestamp.
1305
+         *
1306
+         * @author Steve Hardy
1307
+         *
1308
+         * @param int $rdate the date which will be converted
1309
+         *
1310
+         * @return int the converted date
1311
+         */
1312
+        public function recurDataToUnixData($rdate) {
1313
+            return ($rdate - 194074560) * 60;
1314
+        }
1315
+
1316
+        /**
1317
+         * Function which converts an Unix date timestamp to recurrence date timestamp.
1318
+         *
1319
+         * @author Johnny Biemans
1320
+         *
1321
+         * @param Date $date the date which will be converted
1322
+         *
1323
+         * @return int the converted date in minutes
1324
+         */
1325
+        public function unixDataToRecurData($date) {
1326
+            return ($date / 60) + 194074560;
1327
+        }
1328
+
1329
+        /**
1330
+         * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1331
+         *
1332
+         * @author Steve Hardy
1333
+         *
1334
+         * @param mixed $ts
1335
+         */
1336
+        public function GetTZOffset($ts) {
1337
+            $Offset = date("O", $ts);
1338
+
1339
+            $Parity = $Offset < 0 ? -1 : 1;
1340
+            $Offset = $Parity * $Offset;
1341
+            $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1342
+
1343
+            return $Parity * $Offset;
1344
+        }
1345
+
1346
+        /**
1347
+         * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves.
1348
+         *
1349
+         * @author Steve Hardy
1350
+         *
1351
+         * @param Date $time
1352
+         *
1353
+         * @return Date GMT Time
1354
+         */
1355
+        public function gmtime($time) {
1356
+            $TZOffset = $this->GetTZOffset($time);
1357
+
1358
+            $t_time = $time - $TZOffset * 60; # Counter adjust for localtime()
1359
+
1360
+            return localtime($t_time, 1);
1361
+        }
1362
+
1363
+        public function isLeapYear($year) {
1364
+            return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0);
1365
+        }
1366
+
1367
+        public function getMonthInSeconds($year, $month) {
1368
+            if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1369
+                $day = 31;
1370
+            }
1371
+            elseif (in_array($month, [4, 6, 9, 11])) {
1372
+                $day = 30;
1373
+            }
1374
+            else {
1375
+                $day = 28;
1376
+                if ($this->isLeapYear($year) == 1) {
1377
+                    ++$day;
1378
+                }
1379
+            }
1380
+
1381
+            return $day * 24 * 60 * 60;
1382
+        }
1383
+
1384
+        /**
1385
+         * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.
1386
+         *
1387
+         * @param int $year
1388
+         * @param int $month
1389
+         * @param int $week
1390
+         * @param int $day
1391
+         * @param int $hour
1392
+         *
1393
+         * @return returns the timestamp of the given date, timezone independent
1394
+         */
1395
+        public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) {
1396
+            // get first day of month
1397
+            $date = gmmktime(0, 0, 0, $month, 0, $year + 1900);
1398
+
1399
+            // get wday info
1400
+            $gmdate = $this->gmtime($date);
1401
+
1402
+            $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
1403
+
1404
+            $date += $week * 7 * 24 * 60 * 60; // go to correct week nr
1405
+            $date += $day * 24 * 60 * 60;
1406
+            $date += $hour * 60 * 60;
1407
+
1408
+            $gmdate = $this->gmtime($date);
1409
+
1410
+            // if we are in the next month, then back up a week, because week '5' means
1411
+            // 'last week of month'
1412
+
1413
+            if ($month != $gmdate["tm_mon"] + 1) {
1414
+                $date -= 7 * 24 * 60 * 60;
1415
+            }
1416
+
1417
+            return $date;
1418
+        }
1419
+
1420
+        /**
1421
+         * getTimezone gives the timezone offset (in minutes) of the given
1422
+         * local date/time according to the given TZ info.
1423
+         *
1424
+         * @param mixed $tz
1425
+         * @param mixed $date
1426
+         */
1427
+        public function getTimezone($tz, $date) {
1428
+            // No timezone -> GMT (+0)
1429
+            if (!isset($tz["timezone"])) {
1430
+                return 0;
1431
+            }
1432
+
1433
+            $dst = false;
1434
+            $gmdate = $this->gmtime($date);
1435
+
1436
+            $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
1437
+            $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
1438
+
1439
+            if ($dststart <= $dstend) {
1440
+                // Northern hemisphere, eg DST is during Mar-Oct
1441
+                if ($date > $dststart && $date < $dstend) {
1442
+                    $dst = true;
1443
+                }
1444
+            }
1445
+            else {
1446
+                // Southern hemisphere, eg DST is during Oct-Mar
1447
+                if ($date < $dstend || $date > $dststart) {
1448
+                    $dst = true;
1449
+                }
1450
+            }
1451
+
1452
+            if ($dst) {
1453
+                return $tz["timezone"] + $tz["timezonedst"];
1454
+            }
1455
+
1456
+            return $tz["timezone"];
1457
+        }
1458
+
1459
+        /**
1460
+         * getWeekNr() returns the week nr of the month (ie first week of february is 1).
1461
+         *
1462
+         * @param mixed $date
1463
+         */
1464
+        public function getWeekNr($date) {
1465
+            $gmdate = gmtime($date);
1466
+            $gmdate["tm_mday"] = 0;
1467
+
1468
+            return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
1469
+        }
1470
+
1471
+        /**
1472
+         * parseTimezone parses the timezone as specified in named property 0x8233
1473
+         * in Outlook calendar messages. Returns the timezone in minutes negative
1474
+         * offset (GMT +2:00 -> -120).
1475
+         *
1476
+         * @param mixed $data
1477
+         */
1478
+        public function parseTimezone($data) {
1479
+            if (strlen($data) < 48) {
1480
+                return;
1481
+            }
1482
+
1483
+            return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
1484
+        }
1485
+
1486
+        public function getTimezoneData($tz) {
1487
+            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);
1488
+        }
1489
+
1490
+        /**
1491
+         * createTimezone creates the timezone as specified in the named property 0x8233
1492
+         * see also parseTimezone()
1493
+         * $tz is an array with the timezone data.
1494
+         *
1495
+         * @param mixed $tz
1496
+         */
1497
+        public function createTimezone($tz) {
1498
+            return pack(
1499
+                "lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
1500
+                $tz["timezone"],
1501
+                array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0,
1502
+                array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0,
1503
+                array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0,
1504
+                array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0,
1505
+                array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0,
1506
+                array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0,
1507
+                array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0
1508
+            );
1509
+        }
1510
+
1511
+        /**
1512
+         * toGMT returns a timestamp in GMT time for the time and timezone given.
1513
+         *
1514
+         * @param mixed $tz
1515
+         * @param mixed $date
1516
+         */
1517
+        public function toGMT($tz, $date) {
1518
+            if (!isset($tz['timezone'])) {
1519
+                return $date;
1520
+            }
1521
+            $offset = $this->getTimezone($tz, $date);
1522
+
1523
+            return $date + $offset * 60;
1524
+        }
1525
+
1526
+        /**
1527
+         * fromGMT returns a timestamp in the local timezone given from the GMT time given.
1528
+         *
1529
+         * @param mixed $tz
1530
+         * @param mixed $date
1531
+         */
1532
+        public function fromGMT($tz, $date) {
1533
+            $offset = $this->getTimezone($tz, $date);
1534
+
1535
+            return $date - $offset * 60;
1536
+        }
1537
+
1538
+        /**
1539
+         * Function to get timestamp of the beginning of the day of the timestamp given.
1540
+         *
1541
+         * @param date $date
1542
+         *
1543
+         * @return date timestamp referring to same day but at 00:00:00
1544
+         */
1545
+        public function dayStartOf($date) {
1546
+            $time1 = $this->gmtime($date);
1547
+
1548
+            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
1549
+        }
1550
+
1551
+        /**
1552
+         * Function to get timestamp of the beginning of the month of the timestamp given.
1553
+         *
1554
+         * @param date $date
1555
+         *
1556
+         * @return date Timestamp referring to same month but on the first day, and at 00:00:00
1557
+         */
1558
+        public function monthStartOf($date) {
1559
+            $time1 = $this->gmtime($date);
1560
+
1561
+            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
1562
+        }
1563
+
1564
+        /**
1565
+         * Function to get timestamp of the beginning of the year of the timestamp given.
1566
+         *
1567
+         * @param date $date
1568
+         *
1569
+         * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00
1570
+         */
1571
+        public function yearStartOf($date) {
1572
+            $time1 = $this->gmtime($date);
1573
+
1574
+            return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
1575
+        }
1576
+
1577
+        /**
1578
+         * Function which returns the items in a given interval. This included expansion of the recurrence and
1579
+         * processing of exceptions (modified and deleted).
1580
+         *
1581
+         * @param string $entryid       the entryid of the message
1582
+         * @param array  $props         the properties of the message
1583
+         * @param date   $start         start time of the interval (GMT)
1584
+         * @param date   $end           end time of the interval (GMT)
1585
+         * @param mixed  $limit
1586
+         * @param mixed  $remindersonly
1587
+         */
1588
+        public function getItems($start, $end, $limit = 0, $remindersonly = false) {
1589
+            $items = [];
1590
+
1591
+            if (isset($this->recur)) {
1592
+                // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
1593
+                // exceptions are in range and have a reminder set
1594
+                if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
1595
+                    // Sort exceptions by start time
1596
+                    uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]);
1597
+
1598
+                    // Loop through all changed exceptions
1599
+                    foreach ($this->recur["changed_occurrences"] as $exception) {
1600
+                        // Check reminder set
1601
+                        if (!isset($exception["reminder"]) || $exception["reminder"] == false) {
1602
+                            continue;
1603
+                        }
1604
+
1605
+                        // Convert to GMT
1606
+                        $occstart = $this->toGMT($this->tz, $exception["start"]);
1607
+                        $occend = $this->toGMT($this->tz, $exception["end"]);
1608
+
1609
+                        // Check range criterium
1610
+                        if ($occstart > $end || $occend < $start) {
1611
+                            continue;
1612
+                        }
1613
+
1614
+                        // OK, add to items.
1615
+                        array_push($items, $this->getExceptionProperties($exception));
1616
+                        if ($limit && (count($items) == $limit)) {
1617
+                            break;
1618
+                        }
1619
+                    }
1620
+
1621
+                    uasort($items, [$this, "sortStarttime"]);
1622
+
1623
+                    return $items;
1624
+                }
1625
+
1626
+                // From here on, the dates of the occurrences are calculated in local time, so the days we're looking
1627
+                // at are calculated from the local time dates of $start and $end
1628
+
1629
+                if ($this->recur['regen'] && isset($this->action['datecompleted'])) {
1630
+                    $daystart = $this->dayStartOf($this->action['datecompleted']);
1631
+                }
1632
+                else {
1633
+                    $daystart = $this->dayStartOf($this->recur["start"]);
1634
+                } // start on first day of occurrence
1635
+
1636
+                // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
1637
+                // or the end of the recurrence, whichever comes first
1638
+                if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1639
+                    $rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1640
+                }
1641
+                else {
1642
+                    $rangeend = $end;
1643
+                }
1644
+
1645
+                $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
1646
+
1647
+                // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1648
+
1649
+                switch ($this->recur["type"]) {
1650
+                case 10:
1651
+                    // Daily
1652
+                    if ($this->recur["everyn"] <= 0) {
1653
+                        $this->recur["everyn"] = 1440;
1654
+                    }
1655
+
1656
+                    if ($this->recur["subtype"] == 0) {
1657
+                        // Every Nth day
1658
+                        for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1659
+                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1660
+                        }
1661
+                        break;
1662
+                    }
1663
+                    // Every workday
1664
+                    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) {
1665
+                        $nowtime = $this->gmtime($now);
1666
+                        if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1667
+                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1668
+                        }
1669
+                    }
1670
+                    break;
1671
+
1672
+                case 11:
1673
+                    // Weekly
1674
+                    if ($this->recur["everyn"] <= 0) {
1675
+                        $this->recur["everyn"] = 1;
1676
+                    }
1677
+
1678
+                    // If sliding flag is set then move to 'n' weeks
1679
+                    if ($this->recur['regen']) {
1680
+                        $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1681
+                    }
1682
+
1683
+                    for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) {
1684
+                        if ($this->recur['regen']) {
1685
+                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1686
+
1687
+                            break;
1688
+                        }
1689
+                        // Loop through the whole following week to the first occurrence of the week, add each day that is specified
1690
+                        for ($wday = 0; $wday < 7; ++$wday) {
1691
+                            $daynow = $now + $wday * 60 * 60 * 24;
1692
+                            // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1693
+                            if ($daynow > $dayend) {
1694
+                                continue;
1695
+                            }
1696
+                            $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1697
+                            if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ?
1698
+                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1699
+                            }
1700
+                        }
1701
+                    }
1702
+                    break;
1703
+
1704
+                case 12:
1705
+                    // Monthly
1706
+                    if ($this->recur["everyn"] <= 0) {
1707
+                        $this->recur["everyn"] = 1;
1708
+                    }
1709
+
1710
+                    // Loop through all months from start to end of occurrence, starting at beginning of first month
1711
+                    for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1712
+                        if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1713
+                            $difference = 1;
1714
+                            if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
1715
+                                $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1716
+                            }
1717
+
1718
+                            $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1719
+                            // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1720
+                            if ($daynow <= $dayend) {
1721
+                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1722
+                            }
1723
+                        }
1724
+                        elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1725
+                            // Sanitize input
1726
+                            if ($this->recur["weekdays"] == 0) {
1727
+                                $this->recur["weekdays"] = 1;
1728
+                            }
1729
+
1730
+                            // If nday is not set to the last day in the month
1731
+                            if ($this->recur["nday"] < 5) {
1732
+                                // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1733
+                                $ndaycounter = 0;
1734
+                                // Find matching weekday in this month
1735
+                                for ($day = 0; $day < $this->daysInMonth($now, 1); ++$day) {
1736
+                                    $daynow = $now + $day * 60 * 60 * 24;
1737
+                                    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1738
+
1739
+                                    if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1740
+                                        ++$ndaycounter;
1741
+                                    }
1742
+                                    // check the selected pattern is same as asked Nth weekday,If so set the firstday
1743
+                                    if ($this->recur["nday"] == $ndaycounter) {
1744
+                                        $firstday = $day;
1745
+
1746
+                                        break;
1747
+                                    }
1748
+                                }
1749
+                                // $firstday is the day of the month on which the asked pattern of nth weekday matches
1750
+                                $daynow = $now + $firstday * 60 * 60 * 24;
1751
+                            }
1752
+                            else {
1753
+                                // Find last day in the month ($now is the firstday of the month)
1754
+                                $NumDaysInMonth = $this->daysInMonth($now, 1);
1755
+                                $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
1756
+
1757
+                                $nowtime = $this->gmtime($daynow);
1758
+                                while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) {
1759
+                                    $daynow -= 86400;
1760
+                                    $nowtime = $this->gmtime($daynow);
1761
+                                }
1762
+                            }
1763
+
1764
+                            /*
1765 1765
 							 * 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.
1766 1766
 							 */
1767
-							if ($daynow <= $dayend && $daynow >= $daystart) {
1768
-								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1769
-							}
1770
-						}
1771
-						elseif ($this->recur['regen']) {
1772
-							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1773
-							$now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1774
-							if ($now <= $dayend) {
1775
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1776
-							}
1777
-						}
1778
-					}
1779
-					break;
1780
-
1781
-				case 13:
1782
-					// Yearly
1783
-					if ($this->recur["everyn"] <= 0) {
1784
-						$this->recur["everyn"] = 12;
1785
-					}
1786
-
1787
-					for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1788
-						if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1789
-							// recur["month"] is in minutes since the beginning of the year
1790
-							$month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1791
-							$monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1792
-							$monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1793
-							if ($monthday > $this->daysInMonth($monthstart, 1)) {
1794
-								$monthday = $this->daysInMonth($monthstart, 1);
1795
-							}	// Cap $monthday on month length (eg 28 feb instead of 29 feb)
1796
-							$daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1797
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1798
-						}
1799
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1800
-							// Go the correct month
1801
-							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1802
-
1803
-							// Find first matching weekday in this month
1804
-							for ($wday = 0; $wday < 7; ++$wday) {
1805
-								$daynow = $monthnow + $wday * 60 * 60 * 24;
1806
-								$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1807
-
1808
-								if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1809
-									$firstday = $wday;
1810
-
1811
-									break;
1812
-								}
1813
-							}
1814
-
1815
-							// Same as above (monthly)
1816
-							$daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1817
-							while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1818
-								$daynow -= 7 * 60 * 60 * 24;
1819
-							}
1820
-
1821
-							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1822
-						}
1823
-						elseif ($this->recur['regen']) {
1824
-							$year_starttime = $this->gmtime($now);
1825
-							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);	// +1 next year
1826
-							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1827
-							if ($now <= $dayend) {
1828
-								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1829
-							}
1830
-						}
1831
-					}
1832
-				}
1833
-				// to get all exception items
1834
-				if (!empty($this->recur['changed_occurrences'])) {
1835
-					$this->processExceptionItems($items, $start, $end);
1836
-				}
1837
-			}
1838
-
1839
-			// sort items on starttime
1840
-			usort($items, [$this, "sortStarttime"]);
1841
-
1842
-			// Return the MAPI-compatible list of items for this object
1843
-			return $items;
1844
-		}
1845
-
1846
-		public function sortStarttime($a, $b) {
1847
-			$aTime = $a[$this->proptags["startdate"]];
1848
-			$bTime = $b[$this->proptags["startdate"]];
1849
-
1850
-			return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1);
1851
-		}
1852
-
1853
-		/**
1854
-		 * daysInMonth.
1855
-		 *
1856
-		 * Returns the number of days in the upcoming number of months. If you specify 1 month as
1857
-		 * $months it will give you the number of days in the month of $date. If you specify more it
1858
-		 * will also count the days in the upcoming months and add that to the number of days. So
1859
-		 * if you have a date in march and you specify $months as 2 it will return 61.
1860
-		 *
1861
-		 * @param int $date   specified date as timestamp from which you want to know the number
1862
-		 *                    of days in the month
1863
-		 * @param int $months number of months you want to know the number of days in
1864
-		 * @returns Integer Number of days in the specified amount of months.
1865
-		 */
1866
-		public function daysInMonth($date, $months) {
1867
-			$days = 0;
1868
-			for ($i = 0; $i < $months; ++$i) {
1869
-				$days += date("t", $date + $days * 24 * 60 * 60);
1870
-			}
1871
-
1872
-			return $days;
1873
-		}
1874
-
1875
-		// Converts MAPI-style 'minutes' into the month of the year [0..11]
1876
-		public function monthOfYear($minutes) {
1877
-			$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
1878
-
1879
-			$d += $minutes * 60;
1880
-
1881
-			$dtime = $this->gmtime($d);
1882
-
1883
-			return $dtime["tm_mon"];
1884
-		}
1885
-
1886
-		public function sortExceptionStart($a, $b) {
1887
-			return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1);
1888
-		}
1889
-	}
1767
+                            if ($daynow <= $dayend && $daynow >= $daystart) {
1768
+                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1769
+                            }
1770
+                        }
1771
+                        elseif ($this->recur['regen']) {
1772
+                            $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1773
+                            $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1774
+                            if ($now <= $dayend) {
1775
+                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1776
+                            }
1777
+                        }
1778
+                    }
1779
+                    break;
1780
+
1781
+                case 13:
1782
+                    // Yearly
1783
+                    if ($this->recur["everyn"] <= 0) {
1784
+                        $this->recur["everyn"] = 12;
1785
+                    }
1786
+
1787
+                    for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) {
1788
+                        if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1789
+                            // recur["month"] is in minutes since the beginning of the year
1790
+                            $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1791
+                            $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1792
+                            $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1793
+                            if ($monthday > $this->daysInMonth($monthstart, 1)) {
1794
+                                $monthday = $this->daysInMonth($monthstart, 1);
1795
+                            }	// Cap $monthday on month length (eg 28 feb instead of 29 feb)
1796
+                            $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1797
+                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1798
+                        }
1799
+                        elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1800
+                            // Go the correct month
1801
+                            $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1802
+
1803
+                            // Find first matching weekday in this month
1804
+                            for ($wday = 0; $wday < 7; ++$wday) {
1805
+                                $daynow = $monthnow + $wday * 60 * 60 * 24;
1806
+                                $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1807
+
1808
+                                if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1809
+                                    $firstday = $wday;
1810
+
1811
+                                    break;
1812
+                                }
1813
+                            }
1814
+
1815
+                            // Same as above (monthly)
1816
+                            $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24;
1817
+                            while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
1818
+                                $daynow -= 7 * 60 * 60 * 24;
1819
+                            }
1820
+
1821
+                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1822
+                        }
1823
+                        elseif ($this->recur['regen']) {
1824
+                            $year_starttime = $this->gmtime($now);
1825
+                            $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);	// +1 next year
1826
+                            $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1827
+                            if ($now <= $dayend) {
1828
+                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1829
+                            }
1830
+                        }
1831
+                    }
1832
+                }
1833
+                // to get all exception items
1834
+                if (!empty($this->recur['changed_occurrences'])) {
1835
+                    $this->processExceptionItems($items, $start, $end);
1836
+                }
1837
+            }
1838
+
1839
+            // sort items on starttime
1840
+            usort($items, [$this, "sortStarttime"]);
1841
+
1842
+            // Return the MAPI-compatible list of items for this object
1843
+            return $items;
1844
+        }
1845
+
1846
+        public function sortStarttime($a, $b) {
1847
+            $aTime = $a[$this->proptags["startdate"]];
1848
+            $bTime = $b[$this->proptags["startdate"]];
1849
+
1850
+            return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1);
1851
+        }
1852
+
1853
+        /**
1854
+         * daysInMonth.
1855
+         *
1856
+         * Returns the number of days in the upcoming number of months. If you specify 1 month as
1857
+         * $months it will give you the number of days in the month of $date. If you specify more it
1858
+         * will also count the days in the upcoming months and add that to the number of days. So
1859
+         * if you have a date in march and you specify $months as 2 it will return 61.
1860
+         *
1861
+         * @param int $date   specified date as timestamp from which you want to know the number
1862
+         *                    of days in the month
1863
+         * @param int $months number of months you want to know the number of days in
1864
+         * @returns Integer Number of days in the specified amount of months.
1865
+         */
1866
+        public function daysInMonth($date, $months) {
1867
+            $days = 0;
1868
+            for ($i = 0; $i < $months; ++$i) {
1869
+                $days += date("t", $date + $days * 24 * 60 * 60);
1870
+            }
1871
+
1872
+            return $days;
1873
+        }
1874
+
1875
+        // Converts MAPI-style 'minutes' into the month of the year [0..11]
1876
+        public function monthOfYear($minutes) {
1877
+            $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
1878
+
1879
+            $d += $minutes * 60;
1880
+
1881
+            $dtime = $this->gmtime($d);
1882
+
1883
+            return $dtime["tm_mon"];
1884
+        }
1885
+
1886
+        public function sortExceptionStart($a, $b) {
1887
+            return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1);
1888
+        }
1889
+    }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -1822,7 +1822,7 @@
 block discarded – undo
1822 1822
 						}
1823 1823
 						elseif ($this->recur['regen']) {
1824 1824
 							$year_starttime = $this->gmtime($now);
1825
-							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);	// +1 next year
1825
+							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year
1826 1826
 							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
1827 1827
 							if ($now <= $dayend) {
1828 1828
 								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
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
 
52 52
 			if (is_array($message)) {
53 53
 				$this->messageprops = $message;
54
-			}
55
-			else {
54
+			} else {
56 55
 				$this->message = $message;
57 56
 				$this->messageprops = mapi_getprops($this->message, $this->proptags);
58 57
 			}
@@ -207,8 +206,7 @@  discard block
 block discarded – undo
207 206
 				$ret["regen"] = $data["regen"];
208 207
 				if ($ret["subtype"] == 3) {
209 208
 					$ret["weekdays"] = $data["monthday"];
210
-				}
211
-				else {
209
+				} else {
212 210
 					$ret["monthday"] = $data["monthday"];
213 211
 				}
214 212
 
@@ -232,8 +230,7 @@  discard block
 block discarded – undo
232 230
 				$ret["regen"] = $data["regen"];
233 231
 				if ($ret["subtype"] == 3) {
234 232
 					$ret["weekdays"] = $data["monthday"];
235
-				}
236
-				else {
233
+				} else {
237 234
 					$ret["monthday"] = $data["monthday"];
238 235
 				}
239 236
 
@@ -582,8 +579,7 @@  discard block
 block discarded – undo
582 579
 				if ($this->recur["subtype"] == 1) {
583 580
 					// Daily every workday
584 581
 					$rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
585
-				}
586
-				else {
582
+				} else {
587 583
 					// Daily every N days (everyN in minutes)
588 584
 					$everyn = ((int) $this->recur["everyn"]) / 1440;
589 585
 					// Calc first occ
@@ -651,8 +647,7 @@  discard block
 block discarded – undo
651 647
 
652 648
 				if ($this->recur["regen"]) {
653 649
 					$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
654
-				}
655
-				else {
650
+				} else {
656 651
 					$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
657 652
 				}
658 653
 				break;
@@ -669,8 +664,7 @@  discard block
 block discarded – undo
669 664
 				}
670 665
 				if ($term == 0x0C /* monthly */) {
671 666
 					$everyn = (int) $this->recur["everyn"];
672
-				}
673
-				else {
667
+				} else {
674 668
 					$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
675 669
 				}
676 670
 
@@ -746,8 +740,7 @@  discard block
 block discarded – undo
746 740
 							$firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60;
747 741
 						}
748 742
 						$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
749
-					}
750
-					else {
743
+					} else {
751 744
 						// Calc first occ
752 745
 						$firstocc = 0;
753 746
 						$monthIndex = (int) gmdate("n", $this->recur["start"]);
@@ -774,8 +767,7 @@  discard block
 block discarded – undo
774 767
 					if ($nday == 5) {
775 768
 						// Set date on the last day of the last month
776 769
 						$monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60;
777
-					}
778
-					else {
770
+					} else {
779 771
 						// Set on the first day of the month
780 772
 						$monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60);
781 773
 					}
@@ -784,8 +776,7 @@  discard block
 block discarded – undo
784 776
 						// Set on right month
785 777
 						if ($selmonth < $curmonth) {
786 778
 							$tmp = 12 - $curmonth + $selmonth;
787
-						}
788
-						else {
779
+						} else {
789 780
 							$tmp = ($selmonth - $curmonth);
790 781
 						}
791 782
 
@@ -797,8 +788,7 @@  discard block
 block discarded – undo
797 788
 							}
798 789
 							++$curmonth;
799 790
 						}
800
-					}
801
-					else {
791
+					} else {
802 792
 						// Check or you exist in the right month
803 793
 						for ($i = 0; $i < 7; ++$i) {
804 794
 							if ($nday == 5 && (1 << ((gmdate("w", $monthbegindow) - $i) % 7)) & $weekdays) {
@@ -856,8 +846,7 @@  discard block
 block discarded – undo
856 846
 					}
857 847
 					if ($nday == 5) {
858 848
 						$monthbegindow -= $day * 24 * 60 * 60;
859
-					}
860
-					else {
849
+					} else {
861 850
 						$monthbegindow += ($day - 1) * 24 * 60 * 60;
862 851
 					}
863 852
 
@@ -911,8 +900,7 @@  discard block
 block discarded – undo
911 900
 			// Strange little thing for the recurrence type "every workday"
912 901
 			if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
913 902
 				$rdata .= pack("V", 1);
914
-			}
915
-			else { // Other recurrences
903
+			} else { // Other recurrences
916 904
 				$rdata .= pack("V", 0);
917 905
 			}
918 906
 
@@ -1030,8 +1018,7 @@  discard block
 block discarded – undo
1030 1018
 							if ($curmonth >= 12) {
1031 1019
 								$curmonth = 1;
1032 1020
 								++$curyear;
1033
-							}
1034
-							else {
1021
+							} else {
1035 1022
 								++$curmonth;
1036 1023
 							}
1037 1024
 							--$forwardcount;
@@ -1042,8 +1029,7 @@  discard block
 block discarded – undo
1042 1029
 							gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) {
1043 1030
 							if (gmdate("j", $occenddate) < 28) {
1044 1031
 								$occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60;
1045
-							}
1046
-							else {
1032
+							} else {
1047 1033
 								$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1048 1034
 							}
1049 1035
 						}
@@ -1057,8 +1043,7 @@  discard block
 block discarded – undo
1057 1043
 							if ($curmonth >= 12) {
1058 1044
 								$curmonth = 1;
1059 1045
 								++$curyear;
1060
-							}
1061
-							else {
1046
+							} else {
1062 1047
 								++$curmonth;
1063 1048
 							}
1064 1049
 							--$forwardcount;
@@ -1066,8 +1051,7 @@  discard block
 block discarded – undo
1066 1051
 						if ($nday == 5) {
1067 1052
 							// Set date on the last day of the last month
1068 1053
 							$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60;
1069
-						}
1070
-						else {
1054
+						} else {
1071 1055
 							// Set date on the first day of the last month
1072 1056
 							$occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60;
1073 1057
 						}
@@ -1133,8 +1117,7 @@  discard block
 block discarded – undo
1133 1117
 
1134 1118
 				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1135 1119
 				mapi_setprops($this->message, [$this->proptags["side_effects"] => 369]);
1136
-			}
1137
-			else {
1120
+			} else {
1138 1121
 				mapi_setprops($this->message, [$this->proptags["side_effects"] => 3441]);
1139 1122
 			}
1140 1123
 
@@ -1164,8 +1147,7 @@  discard block
 block discarded – undo
1164 1147
 
1165 1148
 				if ($occ) {
1166 1149
 					mapi_setprops($this->message, [$this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60)]);
1167
-				}
1168
-				else {
1150
+				} else {
1169 1151
 					// Last reminder passed, no reminders any more.
1170 1152
 					mapi_setprops($this->message, [$this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7FF00000]);
1171 1153
 				}
@@ -1367,11 +1349,9 @@  discard block
 block discarded – undo
1367 1349
 		public function getMonthInSeconds($year, $month) {
1368 1350
 			if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) {
1369 1351
 				$day = 31;
1370
-			}
1371
-			elseif (in_array($month, [4, 6, 9, 11])) {
1352
+			} elseif (in_array($month, [4, 6, 9, 11])) {
1372 1353
 				$day = 30;
1373
-			}
1374
-			else {
1354
+			} else {
1375 1355
 				$day = 28;
1376 1356
 				if ($this->isLeapYear($year) == 1) {
1377 1357
 					++$day;
@@ -1441,8 +1421,7 @@  discard block
 block discarded – undo
1441 1421
 				if ($date > $dststart && $date < $dstend) {
1442 1422
 					$dst = true;
1443 1423
 				}
1444
-			}
1445
-			else {
1424
+			} else {
1446 1425
 				// Southern hemisphere, eg DST is during Oct-Mar
1447 1426
 				if ($date < $dstend || $date > $dststart) {
1448 1427
 					$dst = true;
@@ -1628,8 +1607,7 @@  discard block
 block discarded – undo
1628 1607
 
1629 1608
 				if ($this->recur['regen'] && isset($this->action['datecompleted'])) {
1630 1609
 					$daystart = $this->dayStartOf($this->action['datecompleted']);
1631
-				}
1632
-				else {
1610
+				} else {
1633 1611
 					$daystart = $this->dayStartOf($this->recur["start"]);
1634 1612
 				} // start on first day of occurrence
1635 1613
 
@@ -1637,8 +1615,7 @@  discard block
 block discarded – undo
1637 1615
 				// or the end of the recurrence, whichever comes first
1638 1616
 				if ($end > $this->toGMT($this->tz, $this->recur["end"])) {
1639 1617
 					$rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1640
-				}
1641
-				else {
1618
+				} else {
1642 1619
 					$rangeend = $end;
1643 1620
 				}
1644 1621
 
@@ -1720,8 +1697,7 @@  discard block
 block discarded – undo
1720 1697
 							if ($daynow <= $dayend) {
1721 1698
 								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1722 1699
 							}
1723
-						}
1724
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1700
+						} elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months
1725 1701
 							// Sanitize input
1726 1702
 							if ($this->recur["weekdays"] == 0) {
1727 1703
 								$this->recur["weekdays"] = 1;
@@ -1748,8 +1724,7 @@  discard block
 block discarded – undo
1748 1724
 								}
1749 1725
 								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1750 1726
 								$daynow = $now + $firstday * 60 * 60 * 24;
1751
-							}
1752
-							else {
1727
+							} else {
1753 1728
 								// Find last day in the month ($now is the firstday of the month)
1754 1729
 								$NumDaysInMonth = $this->daysInMonth($now, 1);
1755 1730
 								$daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60);
@@ -1767,8 +1742,7 @@  discard block
 block discarded – undo
1767 1742
 							if ($daynow <= $dayend && $daynow >= $daystart) {
1768 1743
 								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1769 1744
 							}
1770
-						}
1771
-						elseif ($this->recur['regen']) {
1745
+						} elseif ($this->recur['regen']) {
1772 1746
 							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1773 1747
 							$now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1774 1748
 							if ($now <= $dayend) {
@@ -1795,8 +1769,7 @@  discard block
 block discarded – undo
1795 1769
 							}	// Cap $monthday on month length (eg 28 feb instead of 29 feb)
1796 1770
 							$daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60;
1797 1771
 							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1798
-						}
1799
-						elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1772
+						} elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1800 1773
 							// Go the correct month
1801 1774
 							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1802 1775
 
@@ -1819,8 +1792,7 @@  discard block
 block discarded – undo
1819 1792
 							}
1820 1793
 
1821 1794
 							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1822
-						}
1823
-						elseif ($this->recur['regen']) {
1795
+						} elseif ($this->recur['regen']) {
1824 1796
 							$year_starttime = $this->gmtime($now);
1825 1797
 							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);	// +1 next year
1826 1798
 							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */);
Please login to merge, or discard this patch.
version.php 2 patches
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -8,13 +8,13 @@
 block discarded – undo
8 8
  */
9 9
 
10 10
 if (!defined("GDAV_VERSION")) {
11
-	$path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME'])));
12
-	$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/\""));
13
-	$version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
14
-	if ($branch && $version) {
15
-		define("GDAV_VERSION", $branch . '-' . $version);
16
-	}
17
-	else {
18
-		define("GDAV_VERSION", "GIT");
19
-	}
11
+    $path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME'])));
12
+    $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/\""));
13
+    $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
14
+    if ($branch && $version) {
15
+        define("GDAV_VERSION", $branch . '-' . $version);
16
+    }
17
+    else {
18
+        define("GDAV_VERSION", "GIT");
19
+    }
20 20
 }
Please login to merge, or discard this patch.
Braces   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -13,8 +13,7 @@
 block discarded – undo
13 13
 	$version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe  --always 2>/dev/null");
14 14
 	if ($branch && $version) {
15 15
 		define("GDAV_VERSION", $branch . '-' . $version);
16
-	}
17
-	else {
16
+	} else {
18 17
 		define("GDAV_VERSION", "GIT");
19 18
 	}
20 19
 }
Please login to merge, or discard this patch.
lib/PHPWrapper.php 2 patches
Indentation   +155 added lines, -155 removed lines patch added patch discarded remove patch
@@ -10,159 +10,159 @@
 block discarded – undo
10 10
 namespace grommunio\DAV;
11 11
 
12 12
 class PHPWrapper {
13
-	private $store;
14
-	private $logger;
15
-	private $props;
16
-	private $fileext;
17
-	private $added;
18
-	private $modified;
19
-	private $deleted;
20
-
21
-	/**
22
-	 * Constructor.
23
-	 *
24
-	 * @param MAPIStore          $store
25
-	 * @param GLogger            $logger
26
-	 * @param mixed              $props
27
-	 * @param string             $fileext
28
-	 * @param GrommunioSyncState $syncstate
29
-	 * @param string             $folderid
30
-	 */
31
-	public function __construct($store, $logger, $props, $fileext, $syncstate, $folderid) {
32
-		$this->store = $store;
33
-		$this->logger = $logger;
34
-		$this->props = $props;
35
-		$this->fileext = $fileext;
36
-		$this->syncstate = $syncstate;
37
-		$this->folderid = $folderid;
38
-
39
-		$this->added = [];
40
-		$this->modified = [];
41
-		$this->deleted = [];
42
-	}
43
-
44
-	/**
45
-	 * Accessor for $this->added.
46
-	 *
47
-	 * @return array
48
-	 */
49
-	public function GetAdded() {
50
-		return $this->added;
51
-	}
52
-
53
-	/**
54
-	 * Accessor for $this->modified.
55
-	 *
56
-	 * @return array
57
-	 */
58
-	public function GetModified() {
59
-		return $this->modified;
60
-	}
61
-
62
-	/**
63
-	 * Accessor for $this->deleted.
64
-	 *
65
-	 * @return array
66
-	 */
67
-	public function GetDeleted() {
68
-		return $this->deleted;
69
-	}
70
-
71
-	/**
72
-	 * Returns total changes.
73
-	 *
74
-	 * @return int
75
-	 */
76
-	public function Total() {
77
-		return count($this->added) + count($this->modified) + count($this->deleted);
78
-	}
79
-
80
-	/**
81
-	 * Imports a single message.
82
-	 *
83
-	 * @param array  $props
84
-	 * @param long   $flags
85
-	 * @param object $retmapimessage
86
-	 *
87
-	 * @return long
88
-	 */
89
-	public function ImportMessageChange($props, $flags, $retmapimessage) {
90
-		// if the entryid is not available, do the fallback to the sourcekey
91
-		if (isset($props[PR_ENTRYID])) {
92
-			$entryid = $props[PR_ENTRYID];
93
-		}
94
-		elseif (isset($props[PR_SOURCE_KEY], $props[PR_PARENT_SOURCE_KEY])) {
95
-			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $props[PR_PARENT_SOURCE_KEY], $props[PR_SOURCE_KEY]);
96
-		}
97
-		$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
98
-		$messageProps = mapi_getprops($mapimessage, [PR_SOURCE_KEY, $this->props["goid"]]);
99
-
100
-		$url = null;
101
-		if (isset($messageProps[$this->props["goid"]])) {
102
-			// get uid from goid and check if it's a valid one
103
-			$url = getUidFromGoid($messageProps[$this->props["goid"]]);
104
-			if ($url != null) {
105
-				$this->logger->trace("got %s (goid: %s uid: %s), flags: %d", bin2hex($messageProps[PR_SOURCE_KEY]), bin2hex($messageProps[$this->props["goid"]]), $url, $flags);
106
-				$this->syncstate->rememberAppttsref($this->folderid, bin2hex($messageProps[PR_SOURCE_KEY]), $url);
107
-			}
108
-		}
109
-		if (!$url) {
110
-			$this->logger->trace("got %s (PR_SOURCE_KEY), flags: %d", bin2hex($messageProps[PR_SOURCE_KEY]), $flags);
111
-			$url = bin2hex($messageProps[PR_SOURCE_KEY]);
112
-		}
113
-
114
-		if ($flags == SYNC_NEW_MESSAGE) {
115
-			$this->added[] = $url . $this->fileext;
116
-		}
117
-		else {
118
-			$this->modified[] = $url . $this->fileext;
119
-		}
120
-
121
-		return SYNC_E_IGNORE;
122
-	}
123
-
124
-	/**
125
-	 * Imports a list of messages to be deleted.
126
-	 *
127
-	 * @param long  $flags
128
-	 * @param array $sourcekeys array with sourcekeys
129
-	 *
130
-	 * @return
131
-	 */
132
-	public function ImportMessageDeletion($flags, $sourcekeys) {
133
-		foreach ($sourcekeys as $sourcekey) {
134
-			$this->logger->trace("got %s", bin2hex($sourcekey));
135
-			$appttsref = $this->syncstate->getAppttsref($this->folderid, bin2hex($sourcekey));
136
-			if ($appttsref != null) {
137
-				$this->deleted[] = $appttsref . $this->fileext;
138
-			}
139
-			else {
140
-				$this->deleted[] = bin2hex($sourcekey) . $this->fileext;
141
-			}
142
-		}
143
-	}
144
-
145
-	/** Implement MAPI interface */
146
-	public function Config($stream, $flags = 0) {
147
-	}
148
-
149
-	public function GetLastError($hresult, $ulflags, &$lpmapierror) {
150
-	}
151
-
152
-	public function UpdateState($stream) {
153
-	}
154
-
155
-	public function ImportMessageMove($sourcekeysrcfolder, $sourcekeysrcmessage, $message, $sourcekeydestmessage, $changenumdestmessage) {
156
-	}
157
-
158
-	public function ImportPerUserReadStateChange($readstates) {
159
-	}
160
-
161
-	public function ImportFolderChange($props) {
162
-		return 0;
163
-	}
164
-
165
-	public function ImportFolderDeletion($flags, $sourcekeys) {
166
-		return 0;
167
-	}
13
+    private $store;
14
+    private $logger;
15
+    private $props;
16
+    private $fileext;
17
+    private $added;
18
+    private $modified;
19
+    private $deleted;
20
+
21
+    /**
22
+     * Constructor.
23
+     *
24
+     * @param MAPIStore          $store
25
+     * @param GLogger            $logger
26
+     * @param mixed              $props
27
+     * @param string             $fileext
28
+     * @param GrommunioSyncState $syncstate
29
+     * @param string             $folderid
30
+     */
31
+    public function __construct($store, $logger, $props, $fileext, $syncstate, $folderid) {
32
+        $this->store = $store;
33
+        $this->logger = $logger;
34
+        $this->props = $props;
35
+        $this->fileext = $fileext;
36
+        $this->syncstate = $syncstate;
37
+        $this->folderid = $folderid;
38
+
39
+        $this->added = [];
40
+        $this->modified = [];
41
+        $this->deleted = [];
42
+    }
43
+
44
+    /**
45
+     * Accessor for $this->added.
46
+     *
47
+     * @return array
48
+     */
49
+    public function GetAdded() {
50
+        return $this->added;
51
+    }
52
+
53
+    /**
54
+     * Accessor for $this->modified.
55
+     *
56
+     * @return array
57
+     */
58
+    public function GetModified() {
59
+        return $this->modified;
60
+    }
61
+
62
+    /**
63
+     * Accessor for $this->deleted.
64
+     *
65
+     * @return array
66
+     */
67
+    public function GetDeleted() {
68
+        return $this->deleted;
69
+    }
70
+
71
+    /**
72
+     * Returns total changes.
73
+     *
74
+     * @return int
75
+     */
76
+    public function Total() {
77
+        return count($this->added) + count($this->modified) + count($this->deleted);
78
+    }
79
+
80
+    /**
81
+     * Imports a single message.
82
+     *
83
+     * @param array  $props
84
+     * @param long   $flags
85
+     * @param object $retmapimessage
86
+     *
87
+     * @return long
88
+     */
89
+    public function ImportMessageChange($props, $flags, $retmapimessage) {
90
+        // if the entryid is not available, do the fallback to the sourcekey
91
+        if (isset($props[PR_ENTRYID])) {
92
+            $entryid = $props[PR_ENTRYID];
93
+        }
94
+        elseif (isset($props[PR_SOURCE_KEY], $props[PR_PARENT_SOURCE_KEY])) {
95
+            $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $props[PR_PARENT_SOURCE_KEY], $props[PR_SOURCE_KEY]);
96
+        }
97
+        $mapimessage = mapi_msgstore_openentry($this->store, $entryid);
98
+        $messageProps = mapi_getprops($mapimessage, [PR_SOURCE_KEY, $this->props["goid"]]);
99
+
100
+        $url = null;
101
+        if (isset($messageProps[$this->props["goid"]])) {
102
+            // get uid from goid and check if it's a valid one
103
+            $url = getUidFromGoid($messageProps[$this->props["goid"]]);
104
+            if ($url != null) {
105
+                $this->logger->trace("got %s (goid: %s uid: %s), flags: %d", bin2hex($messageProps[PR_SOURCE_KEY]), bin2hex($messageProps[$this->props["goid"]]), $url, $flags);
106
+                $this->syncstate->rememberAppttsref($this->folderid, bin2hex($messageProps[PR_SOURCE_KEY]), $url);
107
+            }
108
+        }
109
+        if (!$url) {
110
+            $this->logger->trace("got %s (PR_SOURCE_KEY), flags: %d", bin2hex($messageProps[PR_SOURCE_KEY]), $flags);
111
+            $url = bin2hex($messageProps[PR_SOURCE_KEY]);
112
+        }
113
+
114
+        if ($flags == SYNC_NEW_MESSAGE) {
115
+            $this->added[] = $url . $this->fileext;
116
+        }
117
+        else {
118
+            $this->modified[] = $url . $this->fileext;
119
+        }
120
+
121
+        return SYNC_E_IGNORE;
122
+    }
123
+
124
+    /**
125
+     * Imports a list of messages to be deleted.
126
+     *
127
+     * @param long  $flags
128
+     * @param array $sourcekeys array with sourcekeys
129
+     *
130
+     * @return
131
+     */
132
+    public function ImportMessageDeletion($flags, $sourcekeys) {
133
+        foreach ($sourcekeys as $sourcekey) {
134
+            $this->logger->trace("got %s", bin2hex($sourcekey));
135
+            $appttsref = $this->syncstate->getAppttsref($this->folderid, bin2hex($sourcekey));
136
+            if ($appttsref != null) {
137
+                $this->deleted[] = $appttsref . $this->fileext;
138
+            }
139
+            else {
140
+                $this->deleted[] = bin2hex($sourcekey) . $this->fileext;
141
+            }
142
+        }
143
+    }
144
+
145
+    /** Implement MAPI interface */
146
+    public function Config($stream, $flags = 0) {
147
+    }
148
+
149
+    public function GetLastError($hresult, $ulflags, &$lpmapierror) {
150
+    }
151
+
152
+    public function UpdateState($stream) {
153
+    }
154
+
155
+    public function ImportMessageMove($sourcekeysrcfolder, $sourcekeysrcmessage, $message, $sourcekeydestmessage, $changenumdestmessage) {
156
+    }
157
+
158
+    public function ImportPerUserReadStateChange($readstates) {
159
+    }
160
+
161
+    public function ImportFolderChange($props) {
162
+        return 0;
163
+    }
164
+
165
+    public function ImportFolderDeletion($flags, $sourcekeys) {
166
+        return 0;
167
+    }
168 168
 }
Please login to merge, or discard this patch.
Braces   +3 added lines, -6 removed lines patch added patch discarded remove patch
@@ -90,8 +90,7 @@  discard block
 block discarded – undo
90 90
 		// if the entryid is not available, do the fallback to the sourcekey
91 91
 		if (isset($props[PR_ENTRYID])) {
92 92
 			$entryid = $props[PR_ENTRYID];
93
-		}
94
-		elseif (isset($props[PR_SOURCE_KEY], $props[PR_PARENT_SOURCE_KEY])) {
93
+		} elseif (isset($props[PR_SOURCE_KEY], $props[PR_PARENT_SOURCE_KEY])) {
95 94
 			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $props[PR_PARENT_SOURCE_KEY], $props[PR_SOURCE_KEY]);
96 95
 		}
97 96
 		$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
@@ -113,8 +112,7 @@  discard block
 block discarded – undo
113 112
 
114 113
 		if ($flags == SYNC_NEW_MESSAGE) {
115 114
 			$this->added[] = $url . $this->fileext;
116
-		}
117
-		else {
115
+		} else {
118 116
 			$this->modified[] = $url . $this->fileext;
119 117
 		}
120 118
 
@@ -135,8 +133,7 @@  discard block
 block discarded – undo
135 133
 			$appttsref = $this->syncstate->getAppttsref($this->folderid, bin2hex($sourcekey));
136 134
 			if ($appttsref != null) {
137 135
 				$this->deleted[] = $appttsref . $this->fileext;
138
-			}
139
-			else {
136
+			} else {
140 137
 				$this->deleted[] = bin2hex($sourcekey) . $this->fileext;
141 138
 			}
142 139
 		}
Please login to merge, or discard this patch.
lib/AuthBasicBackend.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -10,29 +10,29 @@
 block discarded – undo
10 10
 namespace grommunio\DAV;
11 11
 
12 12
 class AuthBasicBackend extends \Sabre\DAV\Auth\Backend\AbstractBasic {
13
-	protected $gDavBackend;
13
+    protected $gDavBackend;
14 14
 
15
-	/**
16
-	 * Constructor.
17
-	 */
18
-	public function __construct(GrommunioDavBackend $gDavBackend) {
19
-		$this->gDavBackend = $gDavBackend;
20
-	}
15
+    /**
16
+     * Constructor.
17
+     */
18
+    public function __construct(GrommunioDavBackend $gDavBackend) {
19
+        $this->gDavBackend = $gDavBackend;
20
+    }
21 21
 
22
-	/**
23
-	 * Validates a username and password.
24
-	 *
25
-	 * This method should return true or false depending on if login
26
-	 * succeeded.
27
-	 *
28
-	 * @see \Sabre\DAV\Auth\Backend\AbstractBasic::validateUserPass()
29
-	 *
30
-	 * @param string $username
31
-	 * @param string $password
32
-	 *
33
-	 * @return bool
34
-	 */
35
-	protected function validateUserPass($username, $password) {
36
-		return $this->gDavBackend->Logon($username, $password);
37
-	}
22
+    /**
23
+     * Validates a username and password.
24
+     *
25
+     * This method should return true or false depending on if login
26
+     * succeeded.
27
+     *
28
+     * @see \Sabre\DAV\Auth\Backend\AbstractBasic::validateUserPass()
29
+     *
30
+     * @param string $username
31
+     * @param string $password
32
+     *
33
+     * @return bool
34
+     */
35
+    protected function validateUserPass($username, $password) {
36
+        return $this->gDavBackend->Logon($username, $password);
37
+    }
38 38
 }
Please login to merge, or discard this patch.
lib/GrommunioSchedulePlugin.php 1 patch
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -10,58 +10,58 @@
 block discarded – undo
10 10
 namespace grommunio\DAV;
11 11
 
12 12
 class GrommunioSchedulePlugin extends \Sabre\CalDAV\Schedule\Plugin {
13
-	/**
14
-	 * Constructor.
15
-	 */
16
-	public function __construct(GrommunioDavBackend $gDavBackend, GLogger $glogger) {
17
-		$this->gDavBackend = $gDavBackend;
18
-		$this->logger = $glogger;
19
-	}
13
+    /**
14
+     * Constructor.
15
+     */
16
+    public function __construct(GrommunioDavBackend $gDavBackend, GLogger $glogger) {
17
+        $this->gDavBackend = $gDavBackend;
18
+        $this->logger = $glogger;
19
+    }
20 20
 
21
-	/**
22
-	 * Get the Free/Busy information for a recipient.
23
-	 *
24
-	 * Given email, start and end time the function will return
25
-	 * the freebusy blocks.
26
-	 *
27
-	 * @param string $email
28
-	 *
29
-	 * @return array
30
-	 */
31
-	protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, \Sabre\VObject\Component $request) {
32
-		$this->logger->trace("email: %s - start: %d - end: %d", $email, $start->getTimestamp(), $end->getTimestamp());
21
+    /**
22
+     * Get the Free/Busy information for a recipient.
23
+     *
24
+     * Given email, start and end time the function will return
25
+     * the freebusy blocks.
26
+     *
27
+     * @param string $email
28
+     *
29
+     * @return array
30
+     */
31
+    protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, \Sabre\VObject\Component $request) {
32
+        $this->logger->trace("email: %s - start: %d - end: %d", $email, $start->getTimestamp(), $end->getTimestamp());
33 33
 
34
-		$addrbook = $this->gDavBackend->GetAddressBook();
35
-		$fbsupport = mapi_freebusysupport_open($this->gDavBackend->GetSession());
36
-		$email = preg_replace('!^mailto:!i', '', $email);
37
-		$search = [[PR_DISPLAY_NAME => $email]];
38
-		$userarr = mapi_ab_resolvename($addrbook, $search, EMS_AB_ADDRESS_LOOKUP);
39
-		if (!$userarr) {
40
-			return [
41
-				'request-status' => '3.7;Could not find principal',
42
-				'href' => 'mailto:' . $email,
43
-			];
44
-		}
34
+        $addrbook = $this->gDavBackend->GetAddressBook();
35
+        $fbsupport = mapi_freebusysupport_open($this->gDavBackend->GetSession());
36
+        $email = preg_replace('!^mailto:!i', '', $email);
37
+        $search = [[PR_DISPLAY_NAME => $email]];
38
+        $userarr = mapi_ab_resolvename($addrbook, $search, EMS_AB_ADDRESS_LOOKUP);
39
+        if (!$userarr) {
40
+            return [
41
+                'request-status' => '3.7;Could not find principal',
42
+                'href' => 'mailto:' . $email,
43
+            ];
44
+        }
45 45
 
46
-		$fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$userarr[0][PR_ENTRYID]]);
47
-		if (!$fbDataArray || !$fbDataArray[0]) {
48
-			return [
49
-				'calendar-data' => null,
50
-				'request-status' => '2.0;Success',
51
-				'href' => 'mailto:' . $email,
52
-			];
53
-		}
46
+        $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$userarr[0][PR_ENTRYID]]);
47
+        if (!$fbDataArray || !$fbDataArray[0]) {
48
+            return [
49
+                'calendar-data' => null,
50
+                'request-status' => '2.0;Success',
51
+                'href' => 'mailto:' . $email,
52
+            ];
53
+        }
54 54
 
55
-		$enumblock = mapi_freebusydata_enumblocks($fbDataArray[0], $start->getTimestamp(), $end->getTimestamp());
56
-		$result = mapi_freebusyenumblock_ical($addrbook, $enumblock, 100, $start->getTimestamp(), $end->getTimestamp(), $email, $email, "");
57
-		if ($result) {
58
-			$vcalendar = \Sabre\VObject\Reader::read($result, \Sabre\VObject\Reader::OPTION_FORGIVING);
55
+        $enumblock = mapi_freebusydata_enumblocks($fbDataArray[0], $start->getTimestamp(), $end->getTimestamp());
56
+        $result = mapi_freebusyenumblock_ical($addrbook, $enumblock, 100, $start->getTimestamp(), $end->getTimestamp(), $email, $email, "");
57
+        if ($result) {
58
+            $vcalendar = \Sabre\VObject\Reader::read($result, \Sabre\VObject\Reader::OPTION_FORGIVING);
59 59
 
60
-			return [
61
-				'calendar-data' => $vcalendar,
62
-				'request-status' => '2.0;Success',
63
-				'href' => 'mailto:' . $email,
64
-			];
65
-		}
66
-	}
60
+            return [
61
+                'calendar-data' => $vcalendar,
62
+                'request-status' => '2.0;Success',
63
+                'href' => 'mailto:' . $email,
64
+            ];
65
+        }
66
+    }
67 67
 }
Please login to merge, or discard this patch.
lib/GLogger.php 2 patches
Indentation   +390 added lines, -390 removed lines patch added patch discarded remove patch
@@ -19,394 +19,394 @@
 block discarded – undo
19 19
  * If you want other methods of Logger please add a wrapper method to this class.
20 20
  */
21 21
 class GLogger {
22
-	protected static $listOfLoggers = [];
23
-	protected $logger;
24
-
25
-	/**
26
-	 * Constructor.
27
-	 *
28
-	 * @param mixed $name
29
-	 */
30
-	public function __construct($name) {
31
-		$this->logger = \Logger::getLogger($name);
32
-
33
-		// keep an output puffer in case we do debug logging
34
-		if ($this->logger->isDebugEnabled()) {
35
-			ob_start();
36
-		}
37
-
38
-		// let GLogger handle error messages
39
-		set_error_handler('\\grommunio\\DAV\\GLogger::ErrorHandler');
40
-	}
41
-
42
-	/**
43
-	 * Configures log4php.
44
-	 *
45
-	 * This method needs to be called before the first logging event has
46
-	 * occurred. If this method is not called before then the default
47
-	 * configuration will be used.
48
-	 *
49
-	 * @param array|string              $configuration either a path to the configuration
50
-	 *                                                 file, or a configuration array
51
-	 * @param LoggerConfigurator|string $configurator  A custom
52
-	 *                                                 configurator class: either a class name (string), or an object which
53
-	 *                                                 implements the LoggerConfigurator interface. If left empty, the default
54
-	 *                                                 configurator implementation will be used.
55
-	 */
56
-	public static function configure($configuration = null, $configurator = null) {
57
-		\Logger::configure($configuration, $configurator);
58
-	}
59
-
60
-	/**
61
-	 * Destroy configurations for logger definitions.
62
-	 */
63
-	public function resetConfiguration() {
64
-		\Logger::resetConfiguration();
65
-	}
66
-
67
-	/**
68
-	 * Returns a GLogger by name. If it does not exist, it will be created.
69
-	 *
70
-	 * @param string $name  The logger name
71
-	 * @param mixed  $class
72
-	 *
73
-	 * @return Logger
74
-	 */
75
-	public static function GetLogger($class) {
76
-		if (!isset(static::$listOfLoggers[$class])) {
77
-			static::$listOfLoggers[$class] = new GLogger(static::GetClassnameOnly($class));
78
-		}
79
-
80
-		return static::$listOfLoggers[$class];
81
-	}
82
-
83
-	/**
84
-	 * Cuts of the namespace and returns just the classname.
85
-	 *
86
-	 * @param string $namespaceWithClass
87
-	 *
88
-	 * @return string
89
-	 */
90
-	protected static function GetClassnameOnly($namespaceWithClass) {
91
-		if (strpos($namespaceWithClass, '\\') == false) {
92
-			return $namespaceWithClass;
93
-		}
94
-
95
-		return substr(strrchr($namespaceWithClass, '\\'), 1);
96
-	}
97
-
98
-	/**
99
-	 * Logs the incoming data (headers + body) to debug.
100
-	 */
101
-	public function LogIncoming(\Sabre\HTTP\RequestInterface $request) {
102
-		// only do any of this is we are looking for debug messages
103
-		if ($this->logger->isDebugEnabled()) {
104
-			$inputHeader = $request->getMethod() . ' ' . $request->getUrl() . ' HTTP/' . $request->getHTTPVersion() . "\r\n";
105
-			foreach ($request->getHeaders() as $key => $value) {
106
-				if ($key === 'Authorization') {
107
-					list($value) = explode(' ', implode(',', $value), 2);
108
-					$value = [$value . ' REDACTED'];
109
-				}
110
-				$inputHeader .= $key . ": " . implode(',', $value) . "\r\n";
111
-			}
112
-			// reopen the input so we can read it (again)
113
-			$inputBody = stream_get_contents(fopen('php://input', 'r'));
114
-			// format incoming xml to be better human readable
115
-			if (stripos($inputBody, '<?xml') === 0) {
116
-				$dom = new \DOMDocument('1.0', 'utf-8');
117
-				$dom->preserveWhiteSpace = false;
118
-				$dom->formatOutput = true;
119
-				$dom->recover = true;
120
-				$dom->loadXML($inputBody);
121
-				$inputBody = $dom->saveXML();
122
-			}
123
-			// log incoming data
124
-			$this->debug("INPUT\n" . $inputHeader . "\n" . $inputBody);
125
-		}
126
-	}
127
-
128
-	/**
129
-	 * Logs the outgoing data (headers + body) to debug.
130
-	 */
131
-	public function LogOutgoing(\Sabre\HTTP\ResponseInterface $response) {
132
-		// only do any of this is we are looking for debug messages
133
-		if ($this->logger->isDebugEnabled()) {
134
-			$output = 'HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText() . "\n";
135
-			foreach ($response->getHeaders() as $key => $value) {
136
-				$output .= $key . ": " . implode(',', $value) . "\n";
137
-			}
138
-			$outputBody = ob_get_contents();
139
-			if (stripos($outputBody, '<?xml') === 0) {
140
-				$dom = new \DOMDocument('1.0', 'utf-8');
141
-				$dom->preserveWhiteSpace = false;
142
-				$dom->formatOutput = true;
143
-				$dom->recover = true;
144
-				$dom->loadXML($outputBody);
145
-				$outputBody = $dom->saveXML();
146
-			}
147
-			$this->debug("OUTPUT:\n" . $output . "\n" . $outputBody);
148
-
149
-			ob_end_flush();
150
-		}
151
-	}
152
-
153
-	/**
154
-	 * Runs the arguments through sprintf() and sends it to the logger.
155
-	 *
156
-	 * @param \LoggerLevel $level
157
-	 * @param array        $args
158
-	 * @param string       $suffix an optional suffix that is appended to the message
159
-	 */
160
-	protected function writeLog($level, $args, $suffix = '') {
161
-		$outArgs = [];
162
-		foreach ($args as $arg) {
163
-			if (is_array($arg)) {
164
-				$outArgs[] = print_r($arg, true);
165
-			}
166
-			$outArgs[] = $arg;
167
-		}
168
-		// Call sprintf() with the arguments only if there are format parameters because
169
-		// otherwise sprintf will complain about too few arguments.
170
-		// This also prevents throwing errors if there are %-chars in the $outArgs.
171
-		$message = (count($outArgs) > 1) ? call_user_func_array('sprintf', $outArgs) : $outArgs[0];
172
-		// prepend class+method and log the message
173
-		$this->logger->log($level, $this->getCaller(2) . $message . $suffix, null);
174
-	}
175
-
176
-	/**
177
-	 * Verifies if the dynamic amount of logging arguments matches the amount of variables (%) in the message.
178
-	 *
179
-	 * @param array $arguments
180
-	 *
181
-	 * @return bool
182
-	 */
183
-	protected function verifyLogSyntax($arguments) {
184
-		$count = count($arguments);
185
-		$quoted_procent = substr_count($arguments[0], "%%");
186
-		$t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
187
-
188
-		if ($count == 0) {
189
-			$this->logger->error(sprintf("No arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
190
-
191
-			return false;
192
-		}
193
-		// Only check formatting if there are format parameters. Otherwise there will be
194
-		// an error log if the $arguments[0] contain %-chars.
195
-		if (($count > 1) && ((substr_count($arguments[0], "%") - $quoted_procent * 2) !== $count - 1)) {
196
-			$this->logger->error(sprintf("Wrong number of arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
197
-
198
-			return false;
199
-		}
200
-
201
-		return true;
202
-	}
203
-
204
-	/**
205
-	 * Returns a string in the form of "Class->Method(): " or "file:line" if requested.
206
-	 *
207
-	 * @param number $level    the level you want the info from, default 1
208
-	 * @param bool   $fileline returns "file:line" if set to true
209
-	 *
210
-	 * @return string
211
-	 */
212
-	protected function getCaller($level = 1, $fileline = false) {
213
-		$wlevel = $level + 1;
214
-		$t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $wlevel + 1);
215
-		if (isset($t[$wlevel]['function'])) {
216
-			if ($fileline) {
217
-				return $t[$wlevel]['file'] . ":" . $t[$wlevel]['line'];
218
-			}
219
-
220
-			return $this->GetClassnameOnly($t[$wlevel]['class']) . '->' . $t[$wlevel]['function'] . '(): ';
221
-		}
222
-
223
-		return '';
224
-	}
225
-
226
-	/**
227
-	 * Format bytes to a more human readable value.
228
-	 *
229
-	 * @param int $bytes
230
-	 * @param int $precision
231
-	 *
232
-	 * @return string
233
-	 */
234
-	public function FormatBytes($bytes, $precision = 2) {
235
-		if ($bytes <= 0) {
236
-			return '0 B';
237
-		}
238
-
239
-		$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'];
240
-		$base = log($bytes, 1024);
241
-		$fBase = floor($base);
242
-		$pow = pow(1024, $base - $fBase);
243
-
244
-		return sprintf("%.{$precision}f %s", $pow, $units[$fBase]);
245
-	}
246
-
247
-	/**
248
-	 * The GrommunioDav error handler.
249
-	 *
250
-	 * @param int    $errno
251
-	 * @param string $errstr
252
-	 * @param string $errfile
253
-	 * @param int    $errline
254
-	 * @param mixed  $errcontext
255
-	 */
256
-	public static function ErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
257
-		// this is from Z-Push but might be helpful in the future: https://wiki.z-hub.io/x/sIEa
258
-		if (defined('LOG_ERROR_MASK')) {
259
-			$errno &= LOG_ERROR_MASK;
260
-		}
261
-
262
-		switch ($errno) {
263
-			case 0:
264
-				// logging disabled by LOG_ERROR_MASK
265
-				break;
266
-
267
-			case E_DEPRECATED:
268
-				// do not handle this message
269
-				break;
270
-
271
-			case E_NOTICE:
272
-			case E_WARNING:
273
-				$logger = \Logger::getLogger('error');
274
-				$logger->warn("{$errfile}:{$errline} {$errstr} ({$errno})");
275
-				break;
276
-
277
-			default:
278
-				$bt = debug_backtrace();
279
-				$logger = \Logger::getLogger('error');
280
-				$logger->error("trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps");
281
-				for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) {
282
-					$file = $line = "unknown";
283
-					if (isset($bt[$i]['file'])) {
284
-						$file = $bt[$i]['file'];
285
-					}
286
-					if (isset($bt[$i]['line'])) {
287
-						$line = $bt[$i]['line'];
288
-					}
289
-					$logger->error("trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()");
290
-				}
291
-				break;
292
-		}
293
-	}
294
-
295
-	/**
296
-	 * Wrapper of the \Logger class.
297
-	 */
298
-
299
-	/**
300
-	 * Log a message object with the TRACE level.
301
-	 * It has the same footprint as sprintf(), but arguments are only processed
302
-	 * if the loglevel is activated.
303
-	 *
304
-	 * @param mixed $message message
305
-	 * @param mixed ...params
306
-	 */
307
-	public function trace() {
308
-		if (DEVELOPER_MODE) {
309
-			if (!$this->verifyLogSyntax(func_get_args())) {
310
-				return;
311
-			}
312
-		}
313
-		if ($this->logger->isTraceEnabled()) {
314
-			$this->writeLog(\LoggerLevel::getLevelTrace(), func_get_args());
315
-		}
316
-	}
317
-
318
-	/**
319
-	 * Log a message object with the DEBUG level.
320
-	 * It has the same footprint as sprintf(), but arguments are only processed
321
-	 * if the loglevel is activated.
322
-	 *
323
-	 * @param mixed $message message
324
-	 * @param mixed ...params
325
-	 */
326
-	public function debug() {
327
-		if (DEVELOPER_MODE) {
328
-			if (!$this->verifyLogSyntax(func_get_args())) {
329
-				return;
330
-			}
331
-		}
332
-		if ($this->logger->isDebugEnabled()) {
333
-			$this->writeLog(\LoggerLevel::getLevelDebug(), func_get_args());
334
-		}
335
-	}
336
-
337
-	/**
338
-	 * Log a message object with the INFO level.
339
-	 * It has the same footprint as sprintf(), but arguments are only processed
340
-	 * if the loglevel is activated.
341
-	 *
342
-	 * @param mixed $message message
343
-	 * @param mixed ...params
344
-	 */
345
-	public function info() {
346
-		if (DEVELOPER_MODE) {
347
-			if (!$this->verifyLogSyntax(func_get_args())) {
348
-				return;
349
-			}
350
-		}
351
-		if ($this->logger->isInfoEnabled()) {
352
-			$this->writeLog(\LoggerLevel::getLevelInfo(), func_get_args());
353
-		}
354
-	}
355
-
356
-	/**
357
-	 * Log a message object with the WARN level.
358
-	 * It has the same footprint as sprintf(), but arguments are only processed
359
-	 * if the loglevel is activated.
360
-	 *
361
-	 * @param mixed $message message
362
-	 * @param mixed ...params
363
-	 */
364
-	public function warn() {
365
-		if (DEVELOPER_MODE) {
366
-			if (!$this->verifyLogSyntax(func_get_args())) {
367
-				return;
368
-			}
369
-		}
370
-		if ($this->logger->isWarnEnabled()) {
371
-			$this->writeLog(\LoggerLevel::getLevelWarn(), func_get_args(), ' - ' . $this->getCaller(1, true));
372
-		}
373
-	}
374
-
375
-	/**
376
-	 * Log a message object with the ERROR level.
377
-	 * It has the same footprint as sprintf(), but arguments are only processed
378
-	 * if the loglevel is activated.
379
-	 *
380
-	 * @param mixed $message message
381
-	 * @param mixed ...params
382
-	 */
383
-	public function error() {
384
-		if (DEVELOPER_MODE) {
385
-			if (!$this->verifyLogSyntax(func_get_args())) {
386
-				return;
387
-			}
388
-		}
389
-		if ($this->logger->isErrorEnabled()) {
390
-			$this->writeLog(\LoggerLevel::getLevelError(), func_get_args(), ' - ' . $this->getCaller(1, true));
391
-		}
392
-	}
393
-
394
-	/**
395
-	 * Log a message object with the FATAL level.
396
-	 * It has the same footprint as sprintf(), but arguments are only processed
397
-	 * if the loglevel is activated.
398
-	 *
399
-	 * @param mixed $message message
400
-	 * @param mixed ...params
401
-	 */
402
-	public function fatal() {
403
-		if (DEVELOPER_MODE) {
404
-			if (!$this->verifyLogSyntax(func_get_args())) {
405
-				return;
406
-			}
407
-		}
408
-		if ($this->logger->isFatalEnabled()) {
409
-			$this->writeLog(\LoggerLevel::getLevelFatal(), func_get_args(), ' - ' . $this->getCaller(1, true));
410
-		}
411
-	}
22
+    protected static $listOfLoggers = [];
23
+    protected $logger;
24
+
25
+    /**
26
+     * Constructor.
27
+     *
28
+     * @param mixed $name
29
+     */
30
+    public function __construct($name) {
31
+        $this->logger = \Logger::getLogger($name);
32
+
33
+        // keep an output puffer in case we do debug logging
34
+        if ($this->logger->isDebugEnabled()) {
35
+            ob_start();
36
+        }
37
+
38
+        // let GLogger handle error messages
39
+        set_error_handler('\\grommunio\\DAV\\GLogger::ErrorHandler');
40
+    }
41
+
42
+    /**
43
+     * Configures log4php.
44
+     *
45
+     * This method needs to be called before the first logging event has
46
+     * occurred. If this method is not called before then the default
47
+     * configuration will be used.
48
+     *
49
+     * @param array|string              $configuration either a path to the configuration
50
+     *                                                 file, or a configuration array
51
+     * @param LoggerConfigurator|string $configurator  A custom
52
+     *                                                 configurator class: either a class name (string), or an object which
53
+     *                                                 implements the LoggerConfigurator interface. If left empty, the default
54
+     *                                                 configurator implementation will be used.
55
+     */
56
+    public static function configure($configuration = null, $configurator = null) {
57
+        \Logger::configure($configuration, $configurator);
58
+    }
59
+
60
+    /**
61
+     * Destroy configurations for logger definitions.
62
+     */
63
+    public function resetConfiguration() {
64
+        \Logger::resetConfiguration();
65
+    }
66
+
67
+    /**
68
+     * Returns a GLogger by name. If it does not exist, it will be created.
69
+     *
70
+     * @param string $name  The logger name
71
+     * @param mixed  $class
72
+     *
73
+     * @return Logger
74
+     */
75
+    public static function GetLogger($class) {
76
+        if (!isset(static::$listOfLoggers[$class])) {
77
+            static::$listOfLoggers[$class] = new GLogger(static::GetClassnameOnly($class));
78
+        }
79
+
80
+        return static::$listOfLoggers[$class];
81
+    }
82
+
83
+    /**
84
+     * Cuts of the namespace and returns just the classname.
85
+     *
86
+     * @param string $namespaceWithClass
87
+     *
88
+     * @return string
89
+     */
90
+    protected static function GetClassnameOnly($namespaceWithClass) {
91
+        if (strpos($namespaceWithClass, '\\') == false) {
92
+            return $namespaceWithClass;
93
+        }
94
+
95
+        return substr(strrchr($namespaceWithClass, '\\'), 1);
96
+    }
97
+
98
+    /**
99
+     * Logs the incoming data (headers + body) to debug.
100
+     */
101
+    public function LogIncoming(\Sabre\HTTP\RequestInterface $request) {
102
+        // only do any of this is we are looking for debug messages
103
+        if ($this->logger->isDebugEnabled()) {
104
+            $inputHeader = $request->getMethod() . ' ' . $request->getUrl() . ' HTTP/' . $request->getHTTPVersion() . "\r\n";
105
+            foreach ($request->getHeaders() as $key => $value) {
106
+                if ($key === 'Authorization') {
107
+                    list($value) = explode(' ', implode(',', $value), 2);
108
+                    $value = [$value . ' REDACTED'];
109
+                }
110
+                $inputHeader .= $key . ": " . implode(',', $value) . "\r\n";
111
+            }
112
+            // reopen the input so we can read it (again)
113
+            $inputBody = stream_get_contents(fopen('php://input', 'r'));
114
+            // format incoming xml to be better human readable
115
+            if (stripos($inputBody, '<?xml') === 0) {
116
+                $dom = new \DOMDocument('1.0', 'utf-8');
117
+                $dom->preserveWhiteSpace = false;
118
+                $dom->formatOutput = true;
119
+                $dom->recover = true;
120
+                $dom->loadXML($inputBody);
121
+                $inputBody = $dom->saveXML();
122
+            }
123
+            // log incoming data
124
+            $this->debug("INPUT\n" . $inputHeader . "\n" . $inputBody);
125
+        }
126
+    }
127
+
128
+    /**
129
+     * Logs the outgoing data (headers + body) to debug.
130
+     */
131
+    public function LogOutgoing(\Sabre\HTTP\ResponseInterface $response) {
132
+        // only do any of this is we are looking for debug messages
133
+        if ($this->logger->isDebugEnabled()) {
134
+            $output = 'HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText() . "\n";
135
+            foreach ($response->getHeaders() as $key => $value) {
136
+                $output .= $key . ": " . implode(',', $value) . "\n";
137
+            }
138
+            $outputBody = ob_get_contents();
139
+            if (stripos($outputBody, '<?xml') === 0) {
140
+                $dom = new \DOMDocument('1.0', 'utf-8');
141
+                $dom->preserveWhiteSpace = false;
142
+                $dom->formatOutput = true;
143
+                $dom->recover = true;
144
+                $dom->loadXML($outputBody);
145
+                $outputBody = $dom->saveXML();
146
+            }
147
+            $this->debug("OUTPUT:\n" . $output . "\n" . $outputBody);
148
+
149
+            ob_end_flush();
150
+        }
151
+    }
152
+
153
+    /**
154
+     * Runs the arguments through sprintf() and sends it to the logger.
155
+     *
156
+     * @param \LoggerLevel $level
157
+     * @param array        $args
158
+     * @param string       $suffix an optional suffix that is appended to the message
159
+     */
160
+    protected function writeLog($level, $args, $suffix = '') {
161
+        $outArgs = [];
162
+        foreach ($args as $arg) {
163
+            if (is_array($arg)) {
164
+                $outArgs[] = print_r($arg, true);
165
+            }
166
+            $outArgs[] = $arg;
167
+        }
168
+        // Call sprintf() with the arguments only if there are format parameters because
169
+        // otherwise sprintf will complain about too few arguments.
170
+        // This also prevents throwing errors if there are %-chars in the $outArgs.
171
+        $message = (count($outArgs) > 1) ? call_user_func_array('sprintf', $outArgs) : $outArgs[0];
172
+        // prepend class+method and log the message
173
+        $this->logger->log($level, $this->getCaller(2) . $message . $suffix, null);
174
+    }
175
+
176
+    /**
177
+     * Verifies if the dynamic amount of logging arguments matches the amount of variables (%) in the message.
178
+     *
179
+     * @param array $arguments
180
+     *
181
+     * @return bool
182
+     */
183
+    protected function verifyLogSyntax($arguments) {
184
+        $count = count($arguments);
185
+        $quoted_procent = substr_count($arguments[0], "%%");
186
+        $t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
187
+
188
+        if ($count == 0) {
189
+            $this->logger->error(sprintf("No arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
190
+
191
+            return false;
192
+        }
193
+        // Only check formatting if there are format parameters. Otherwise there will be
194
+        // an error log if the $arguments[0] contain %-chars.
195
+        if (($count > 1) && ((substr_count($arguments[0], "%") - $quoted_procent * 2) !== $count - 1)) {
196
+            $this->logger->error(sprintf("Wrong number of arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
197
+
198
+            return false;
199
+        }
200
+
201
+        return true;
202
+    }
203
+
204
+    /**
205
+     * Returns a string in the form of "Class->Method(): " or "file:line" if requested.
206
+     *
207
+     * @param number $level    the level you want the info from, default 1
208
+     * @param bool   $fileline returns "file:line" if set to true
209
+     *
210
+     * @return string
211
+     */
212
+    protected function getCaller($level = 1, $fileline = false) {
213
+        $wlevel = $level + 1;
214
+        $t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $wlevel + 1);
215
+        if (isset($t[$wlevel]['function'])) {
216
+            if ($fileline) {
217
+                return $t[$wlevel]['file'] . ":" . $t[$wlevel]['line'];
218
+            }
219
+
220
+            return $this->GetClassnameOnly($t[$wlevel]['class']) . '->' . $t[$wlevel]['function'] . '(): ';
221
+        }
222
+
223
+        return '';
224
+    }
225
+
226
+    /**
227
+     * Format bytes to a more human readable value.
228
+     *
229
+     * @param int $bytes
230
+     * @param int $precision
231
+     *
232
+     * @return string
233
+     */
234
+    public function FormatBytes($bytes, $precision = 2) {
235
+        if ($bytes <= 0) {
236
+            return '0 B';
237
+        }
238
+
239
+        $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'];
240
+        $base = log($bytes, 1024);
241
+        $fBase = floor($base);
242
+        $pow = pow(1024, $base - $fBase);
243
+
244
+        return sprintf("%.{$precision}f %s", $pow, $units[$fBase]);
245
+    }
246
+
247
+    /**
248
+     * The GrommunioDav error handler.
249
+     *
250
+     * @param int    $errno
251
+     * @param string $errstr
252
+     * @param string $errfile
253
+     * @param int    $errline
254
+     * @param mixed  $errcontext
255
+     */
256
+    public static function ErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
257
+        // this is from Z-Push but might be helpful in the future: https://wiki.z-hub.io/x/sIEa
258
+        if (defined('LOG_ERROR_MASK')) {
259
+            $errno &= LOG_ERROR_MASK;
260
+        }
261
+
262
+        switch ($errno) {
263
+            case 0:
264
+                // logging disabled by LOG_ERROR_MASK
265
+                break;
266
+
267
+            case E_DEPRECATED:
268
+                // do not handle this message
269
+                break;
270
+
271
+            case E_NOTICE:
272
+            case E_WARNING:
273
+                $logger = \Logger::getLogger('error');
274
+                $logger->warn("{$errfile}:{$errline} {$errstr} ({$errno})");
275
+                break;
276
+
277
+            default:
278
+                $bt = debug_backtrace();
279
+                $logger = \Logger::getLogger('error');
280
+                $logger->error("trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps");
281
+                for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) {
282
+                    $file = $line = "unknown";
283
+                    if (isset($bt[$i]['file'])) {
284
+                        $file = $bt[$i]['file'];
285
+                    }
286
+                    if (isset($bt[$i]['line'])) {
287
+                        $line = $bt[$i]['line'];
288
+                    }
289
+                    $logger->error("trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()");
290
+                }
291
+                break;
292
+        }
293
+    }
294
+
295
+    /**
296
+     * Wrapper of the \Logger class.
297
+     */
298
+
299
+    /**
300
+     * Log a message object with the TRACE level.
301
+     * It has the same footprint as sprintf(), but arguments are only processed
302
+     * if the loglevel is activated.
303
+     *
304
+     * @param mixed $message message
305
+     * @param mixed ...params
306
+     */
307
+    public function trace() {
308
+        if (DEVELOPER_MODE) {
309
+            if (!$this->verifyLogSyntax(func_get_args())) {
310
+                return;
311
+            }
312
+        }
313
+        if ($this->logger->isTraceEnabled()) {
314
+            $this->writeLog(\LoggerLevel::getLevelTrace(), func_get_args());
315
+        }
316
+    }
317
+
318
+    /**
319
+     * Log a message object with the DEBUG level.
320
+     * It has the same footprint as sprintf(), but arguments are only processed
321
+     * if the loglevel is activated.
322
+     *
323
+     * @param mixed $message message
324
+     * @param mixed ...params
325
+     */
326
+    public function debug() {
327
+        if (DEVELOPER_MODE) {
328
+            if (!$this->verifyLogSyntax(func_get_args())) {
329
+                return;
330
+            }
331
+        }
332
+        if ($this->logger->isDebugEnabled()) {
333
+            $this->writeLog(\LoggerLevel::getLevelDebug(), func_get_args());
334
+        }
335
+    }
336
+
337
+    /**
338
+     * Log a message object with the INFO level.
339
+     * It has the same footprint as sprintf(), but arguments are only processed
340
+     * if the loglevel is activated.
341
+     *
342
+     * @param mixed $message message
343
+     * @param mixed ...params
344
+     */
345
+    public function info() {
346
+        if (DEVELOPER_MODE) {
347
+            if (!$this->verifyLogSyntax(func_get_args())) {
348
+                return;
349
+            }
350
+        }
351
+        if ($this->logger->isInfoEnabled()) {
352
+            $this->writeLog(\LoggerLevel::getLevelInfo(), func_get_args());
353
+        }
354
+    }
355
+
356
+    /**
357
+     * Log a message object with the WARN level.
358
+     * It has the same footprint as sprintf(), but arguments are only processed
359
+     * if the loglevel is activated.
360
+     *
361
+     * @param mixed $message message
362
+     * @param mixed ...params
363
+     */
364
+    public function warn() {
365
+        if (DEVELOPER_MODE) {
366
+            if (!$this->verifyLogSyntax(func_get_args())) {
367
+                return;
368
+            }
369
+        }
370
+        if ($this->logger->isWarnEnabled()) {
371
+            $this->writeLog(\LoggerLevel::getLevelWarn(), func_get_args(), ' - ' . $this->getCaller(1, true));
372
+        }
373
+    }
374
+
375
+    /**
376
+     * Log a message object with the ERROR level.
377
+     * It has the same footprint as sprintf(), but arguments are only processed
378
+     * if the loglevel is activated.
379
+     *
380
+     * @param mixed $message message
381
+     * @param mixed ...params
382
+     */
383
+    public function error() {
384
+        if (DEVELOPER_MODE) {
385
+            if (!$this->verifyLogSyntax(func_get_args())) {
386
+                return;
387
+            }
388
+        }
389
+        if ($this->logger->isErrorEnabled()) {
390
+            $this->writeLog(\LoggerLevel::getLevelError(), func_get_args(), ' - ' . $this->getCaller(1, true));
391
+        }
392
+    }
393
+
394
+    /**
395
+     * Log a message object with the FATAL level.
396
+     * It has the same footprint as sprintf(), but arguments are only processed
397
+     * if the loglevel is activated.
398
+     *
399
+     * @param mixed $message message
400
+     * @param mixed ...params
401
+     */
402
+    public function fatal() {
403
+        if (DEVELOPER_MODE) {
404
+            if (!$this->verifyLogSyntax(func_get_args())) {
405
+                return;
406
+            }
407
+        }
408
+        if ($this->logger->isFatalEnabled()) {
409
+            $this->writeLog(\LoggerLevel::getLevelFatal(), func_get_args(), ' - ' . $this->getCaller(1, true));
410
+        }
411
+    }
412 412
 }
Please login to merge, or discard this patch.
Switch Indentation   +28 added lines, -28 removed lines patch added patch discarded remove patch
@@ -260,35 +260,35 @@
 block discarded – undo
260 260
 		}
261 261
 
262 262
 		switch ($errno) {
263
-			case 0:
264
-				// logging disabled by LOG_ERROR_MASK
265
-				break;
266
-
267
-			case E_DEPRECATED:
268
-				// do not handle this message
269
-				break;
270
-
271
-			case E_NOTICE:
272
-			case E_WARNING:
273
-				$logger = \Logger::getLogger('error');
274
-				$logger->warn("{$errfile}:{$errline} {$errstr} ({$errno})");
275
-				break;
276
-
277
-			default:
278
-				$bt = debug_backtrace();
279
-				$logger = \Logger::getLogger('error');
280
-				$logger->error("trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps");
281
-				for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) {
282
-					$file = $line = "unknown";
283
-					if (isset($bt[$i]['file'])) {
284
-						$file = $bt[$i]['file'];
285
-					}
286
-					if (isset($bt[$i]['line'])) {
287
-						$line = $bt[$i]['line'];
288
-					}
289
-					$logger->error("trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()");
263
+		case 0:
264
+			// logging disabled by LOG_ERROR_MASK
265
+			break;
266
+
267
+		case E_DEPRECATED:
268
+			// do not handle this message
269
+			break;
270
+
271
+		case E_NOTICE:
272
+		case E_WARNING:
273
+			$logger = \Logger::getLogger('error');
274
+			$logger->warn("{$errfile}:{$errline} {$errstr} ({$errno})");
275
+			break;
276
+
277
+		default:
278
+			$bt = debug_backtrace();
279
+			$logger = \Logger::getLogger('error');
280
+			$logger->error("trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps");
281
+			for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) {
282
+				$file = $line = "unknown";
283
+				if (isset($bt[$i]['file'])) {
284
+					$file = $bt[$i]['file'];
290 285
 				}
291
-				break;
286
+				if (isset($bt[$i]['line'])) {
287
+					$line = $bt[$i]['line'];
288
+				}
289
+				$logger->error("trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()");
290
+			}
291
+			break;
292 292
 		}
293 293
 	}
294 294
 
Please login to merge, or discard this patch.
lib/MapiProps.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -10,49 +10,49 @@
 block discarded – undo
10 10
 namespace grommunio\DAV;
11 11
 
12 12
 class MapiProps {
13
-	public const PROP_VCARDUID = "PT_UNICODE:PSETID_GROMOX:vcarduid";
13
+    public const PROP_VCARDUID = "PT_UNICODE:PSETID_GROMOX:vcarduid";
14 14
 
15
-	/**
16
-	 * Returns appointment specific MAPI properties
17
-	 * Origins: Z-Push.
18
-	 *
19
-	 * @return array
20
-	 */
21
-	public static function GetAppointmentProperties() {
22
-		return [
23
-			"sourcekey" => PR_SOURCE_KEY,
24
-			"representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
25
-			"representingname" => PR_SENT_REPRESENTING_NAME,
26
-			"sentrepresentingemail" => PR_SENT_REPRESENTING_EMAIL_ADDRESS,
27
-			"sentrepresentingaddt" => PR_SENT_REPRESENTING_ADDRTYPE,
28
-			"sentrepresentinsrchk" => PR_SENT_REPRESENTING_SEARCH_KEY,
29
-			"reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
30
-			"remindertime" => "PT_LONG:PSETID_Common:0x8501",
31
-			"meetingstatus" => "PT_LONG:PSETID_Appointment:0x8217",
32
-			"isrecurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
33
-			"recurringstate" => "PT_BINARY:PSETID_Appointment:0x8216",
34
-			"timezonetag" => "PT_BINARY:PSETID_Appointment:0x8233",
35
-			"timezonedesc" => "PT_STRING8:PSETID_Appointment:0x8234",
36
-			"recurrenceend" => "PT_SYSTIME:PSETID_Appointment:0x8236",
37
-			"responsestatus" => "PT_LONG:PSETID_Appointment:0x8218",
38
-			"commonstart" => "PT_SYSTIME:PSETID_Common:0x8516",
39
-			"commonend" => "PT_SYSTIME:PSETID_Common:0x8517",
40
-			"reminderstart" => "PT_SYSTIME:PSETID_Common:0x8502",
41
-			"duration" => "PT_LONG:PSETID_Appointment:0x8213",
42
-			"private" => "PT_BOOLEAN:PSETID_Common:0x8506",
43
-			"uid" => "PT_BINARY:PSETID_Meeting:0x23",
44
-			"sideeffects" => "PT_LONG:PSETID_Common:0x8510",
45
-			"flagdueby" => "PT_SYSTIME:PSETID_Common:0x8560",
46
-			"icon" => PR_ICON_INDEX,
47
-			"mrwassent" => "PT_BOOLEAN:PSETID_Appointment:0x8229",
48
-			"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e", // this is here for calendar restriction, tnef and ical
49
-			"starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d", // this is here for calendar restriction, tnef and ical
50
-			"clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235", // ical only
51
-			"recurrencetype" => "PT_LONG:PSETID_Appointment:0x8231",
52
-			"body" => PR_BODY,
53
-			"rtfcompressed" => PR_RTF_COMPRESSED,
54
-			"html" => PR_HTML,
55
-			"rtfinsync" => PR_RTF_IN_SYNC,
56
-		];
57
-	}
15
+    /**
16
+     * Returns appointment specific MAPI properties
17
+     * Origins: Z-Push.
18
+     *
19
+     * @return array
20
+     */
21
+    public static function GetAppointmentProperties() {
22
+        return [
23
+            "sourcekey" => PR_SOURCE_KEY,
24
+            "representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
25
+            "representingname" => PR_SENT_REPRESENTING_NAME,
26
+            "sentrepresentingemail" => PR_SENT_REPRESENTING_EMAIL_ADDRESS,
27
+            "sentrepresentingaddt" => PR_SENT_REPRESENTING_ADDRTYPE,
28
+            "sentrepresentinsrchk" => PR_SENT_REPRESENTING_SEARCH_KEY,
29
+            "reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
30
+            "remindertime" => "PT_LONG:PSETID_Common:0x8501",
31
+            "meetingstatus" => "PT_LONG:PSETID_Appointment:0x8217",
32
+            "isrecurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
33
+            "recurringstate" => "PT_BINARY:PSETID_Appointment:0x8216",
34
+            "timezonetag" => "PT_BINARY:PSETID_Appointment:0x8233",
35
+            "timezonedesc" => "PT_STRING8:PSETID_Appointment:0x8234",
36
+            "recurrenceend" => "PT_SYSTIME:PSETID_Appointment:0x8236",
37
+            "responsestatus" => "PT_LONG:PSETID_Appointment:0x8218",
38
+            "commonstart" => "PT_SYSTIME:PSETID_Common:0x8516",
39
+            "commonend" => "PT_SYSTIME:PSETID_Common:0x8517",
40
+            "reminderstart" => "PT_SYSTIME:PSETID_Common:0x8502",
41
+            "duration" => "PT_LONG:PSETID_Appointment:0x8213",
42
+            "private" => "PT_BOOLEAN:PSETID_Common:0x8506",
43
+            "uid" => "PT_BINARY:PSETID_Meeting:0x23",
44
+            "sideeffects" => "PT_LONG:PSETID_Common:0x8510",
45
+            "flagdueby" => "PT_SYSTIME:PSETID_Common:0x8560",
46
+            "icon" => PR_ICON_INDEX,
47
+            "mrwassent" => "PT_BOOLEAN:PSETID_Appointment:0x8229",
48
+            "endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e", // this is here for calendar restriction, tnef and ical
49
+            "starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d", // this is here for calendar restriction, tnef and ical
50
+            "clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235", // ical only
51
+            "recurrencetype" => "PT_LONG:PSETID_Appointment:0x8231",
52
+            "body" => PR_BODY,
53
+            "rtfcompressed" => PR_RTF_COMPRESSED,
54
+            "html" => PR_HTML,
55
+            "rtfinsync" => PR_RTF_IN_SYNC,
56
+        ];
57
+    }
58 58
 }
Please login to merge, or discard this patch.