1 | <?php |
||||
2 | /* |
||||
3 | * SPDX-License-Identifier: AGPL-3.0-only |
||||
4 | * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH |
||||
5 | * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
||||
6 | */ |
||||
7 | |||||
8 | class TaskRecurrence extends BaseRecurrence { |
||||
9 | /** |
||||
10 | * Timezone info which is always false for task. |
||||
11 | * |
||||
12 | * @var false |
||||
13 | */ |
||||
14 | public $tz = false; |
||||
15 | |||||
16 | private $action; |
||||
17 | |||||
18 | public function __construct($store, $message) { |
||||
19 | $this->store = $store; |
||||
20 | $this->message = $message; |
||||
21 | |||||
22 | $properties = []; |
||||
23 | $properties["entryid"] = PR_ENTRYID; |
||||
24 | $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
||||
25 | $properties["icon_index"] = PR_ICON_INDEX; |
||||
26 | $properties["message_class"] = PR_MESSAGE_CLASS; |
||||
27 | $properties["message_flags"] = PR_MESSAGE_FLAGS; |
||||
28 | $properties["subject"] = PR_SUBJECT; |
||||
29 | $properties["importance"] = PR_IMPORTANCE; |
||||
30 | $properties["sensitivity"] = PR_SENSITIVITY; |
||||
31 | $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME; |
||||
32 | $properties["status"] = "PT_LONG:PSETID_Task:" . PidLidTaskStatus; |
||||
33 | $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:" . PidLidPercentComplete; |
||||
34 | $properties["startdate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskStartDate; |
||||
35 | $properties["duedate"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDueDate; |
||||
36 | $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107"; |
||||
37 | $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109"; |
||||
38 | $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:" . PidLidTaskDateCompleted; |
||||
39 | $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116"; |
||||
40 | $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110"; |
||||
41 | $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111"; |
||||
42 | $properties["complete"] = "PT_BOOLEAN:PSETID_Task:" . PidLidTaskComplete; |
||||
43 | $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e"; |
||||
44 | $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f"; |
||||
45 | $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126"; |
||||
46 | |||||
47 | $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:" . PidLidReminderDelta; |
||||
48 | $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderTime; |
||||
49 | $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:" . PidLidReminderSet; |
||||
50 | |||||
51 | $properties["private"] = "PT_BOOLEAN:PSETID_Common:" . PidLidPrivate; |
||||
52 | $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
||||
53 | $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
||||
54 | $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
||||
55 | |||||
56 | $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
||||
57 | $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
||||
58 | $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518"; |
||||
59 | $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:" . PidLidReminderSignalTime; |
||||
60 | $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510"; |
||||
61 | |||||
62 | $this->proptags = getPropIdsFromStrings($store, $properties); |
||||
63 | |||||
64 | parent::__construct($store, $message); |
||||
65 | } |
||||
66 | |||||
67 | /** |
||||
68 | * Function which saves recurrence and also regenerates task if necessary. |
||||
69 | * |
||||
70 | * @param mixed $recur new recurrence properties |
||||
71 | * |
||||
72 | * @return array|bool of properties of regenerated task else false |
||||
73 | */ |
||||
74 | public function setRecurrence(&$recur) { |
||||
75 | $this->recur = $recur; |
||||
76 | $this->action = &$recur; |
||||
77 | |||||
78 | if (!isset($this->recur["changed_occurrences"])) { |
||||
79 | $this->recur["changed_occurrences"] = []; |
||||
80 | } |
||||
81 | |||||
82 | if (!isset($this->recur["deleted_occurrences"])) { |
||||
83 | $this->recur["deleted_occurrences"] = []; |
||||
84 | } |
||||
85 | |||||
86 | if (!isset($this->recur['startocc'])) { |
||||
87 | $this->recur['startocc'] = 0; |
||||
88 | } |
||||
89 | if (!isset($this->recur['endocc'])) { |
||||
90 | $this->recur['endocc'] = 0; |
||||
91 | } |
||||
92 | |||||
93 | // Save recurrence because we need proper startrecurrdate and endrecurrdate |
||||
94 | $this->saveRecurrence(); |
||||
95 | |||||
96 | // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence |
||||
97 | $msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]); |
||||
98 | $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]); |
||||
99 | foreach ($recurring_data as $key => $value) { |
||||
100 | $this->recur[$key] = $value; |
||||
101 | } |
||||
102 | |||||
103 | $this->setFirstOccurrence(); |
||||
104 | |||||
105 | // Let's see if next occurrence has to be generated |
||||
106 | return $this->moveToNextOccurrence(); |
||||
107 | } |
||||
108 | |||||
109 | /** |
||||
110 | * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence. |
||||
111 | */ |
||||
112 | public function setFirstOccurrence() { |
||||
113 | // Check if it is already the first occurrence |
||||
114 | if ($this->action['start'] == $this->recur["start"]) { |
||||
115 | return; |
||||
116 | } |
||||
117 | $items = $this->getNextOccurrence(); |
||||
118 | |||||
119 | $props = []; |
||||
120 | $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']]; |
||||
121 | $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']]; |
||||
122 | |||||
123 | $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']]; |
||||
124 | $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']]; |
||||
125 | |||||
126 | mapi_setprops($this->message, $props); |
||||
127 | } |
||||
128 | |||||
129 | /** |
||||
130 | * Function which creates new task as current occurrence and moves the |
||||
131 | * existing task to next occurrence. |
||||
132 | * |
||||
133 | * @return array|bool properties of newly created task if moving to next occurrence succeeds |
||||
134 | * false if that was last occurrence |
||||
135 | */ |
||||
136 | public function moveToNextOccurrence() { |
||||
137 | $result = false; |
||||
138 | /* |
||||
139 | * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date |
||||
140 | * then we create first two occurrence separately and for first occurrence recurrence has ended. |
||||
141 | */ |
||||
142 | if ((empty($this->action['startdate']) && empty($this->action['duedate'])) || |
||||
143 | ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) { |
||||
144 | $nextOccurrence = $this->getNextOccurrence(); |
||||
145 | $result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
||||
146 | |||||
147 | $props = []; |
||||
148 | if (!empty($nextOccurrence)) { |
||||
149 | if (!isset($this->action['deleteOccurrence'])) { |
||||
150 | // Create current occurrence as separate task |
||||
151 | $result = $this->regenerateTask($this->action['complete']); |
||||
152 | } |
||||
153 | |||||
154 | // Set reminder for next occurrence |
||||
155 | $this->setReminder($nextOccurrence); |
||||
156 | |||||
157 | // Update properties for next occurrence |
||||
158 | $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']]; |
||||
159 | $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']]; |
||||
160 | |||||
161 | $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']]; |
||||
162 | $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']]; |
||||
163 | |||||
164 | // If current task as been mark as 'Complete' then next occurrence should be incomplete. |
||||
165 | if (isset($this->action['complete']) && $this->action['complete'] == 1) { |
||||
166 | $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted; |
||||
167 | $this->action['complete'] = $props[$this->proptags["complete"]] = false; |
||||
168 | $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0; |
||||
169 | } |
||||
170 | |||||
171 | $props[$this->proptags["dead_occurrence"]] = false; |
||||
172 | } |
||||
173 | else { |
||||
174 | if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) { |
||||
175 | return false; |
||||
176 | } |
||||
177 | |||||
178 | // Didn't get next occurrence, probably this is the last one, so recurrence ends here |
||||
179 | $props[$this->proptags["dead_occurrence"]] = true; |
||||
180 | $props[$this->proptags["datecompleted"]] = $this->action['datecompleted']; |
||||
181 | $props[$this->proptags["task_f_creator"]] = true; |
||||
182 | |||||
183 | // OL props |
||||
184 | $props[$this->proptags["side_effects"]] = 1296; |
||||
185 | $props[$this->proptags["icon_index"]] = 1280; |
||||
186 | } |
||||
187 | |||||
188 | mapi_setprops($this->message, $props); |
||||
189 | } |
||||
190 | |||||
191 | return $result; |
||||
192 | } |
||||
193 | |||||
194 | /** |
||||
195 | * Function which return properties of next occurrence. |
||||
196 | * |
||||
197 | * @return null|array|false|T startdate/enddate of next occurrence |
||||
198 | */ |
||||
199 | public function getNextOccurrence() { |
||||
200 | if ($this->recur) { |
||||
201 | // @TODO: fix start of range |
||||
202 | $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start']; |
||||
203 | $dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]); |
||||
204 | |||||
205 | // Fix recur object |
||||
206 | $this->recur['startocc'] = 0; |
||||
207 | $this->recur['endocc'] = 0; |
||||
208 | |||||
209 | // Retrieve next occurrence |
||||
210 | $items = $this->getItems($start, $dayend, 1); |
||||
211 | |||||
212 | return !empty($items) ? $items[0] : false; |
||||
213 | } |
||||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * Function which clones current occurrence and sets appropriate properties. |
||||
218 | * The original recurring item is moved to next occurrence. |
||||
219 | * |
||||
220 | * @param bool $markComplete true if existing occurrence has to be marked complete |
||||
221 | */ |
||||
222 | public function regenerateTask($markComplete) { |
||||
223 | // Get all properties |
||||
224 | $taskItemProps = mapi_getprops($this->message); |
||||
225 | |||||
226 | if (isset($this->action["subject"])) { |
||||
227 | $taskItemProps[$this->proptags["subject"]] = $this->action["subject"]; |
||||
228 | } |
||||
229 | if (isset($this->action["importance"])) { |
||||
230 | $taskItemProps[$this->proptags["importance"]] = $this->action["importance"]; |
||||
231 | } |
||||
232 | if (isset($this->action["startdate"])) { |
||||
233 | $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"]; |
||||
234 | $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"]; |
||||
235 | } |
||||
236 | if (isset($this->action["duedate"])) { |
||||
237 | $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"]; |
||||
238 | $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"]; |
||||
239 | } |
||||
240 | |||||
241 | $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
242 | $newMessage = mapi_folder_createmessage($folder); |
||||
243 | |||||
244 | $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted; |
||||
245 | $taskItemProps[$this->proptags["complete"]] = $markComplete; |
||||
246 | $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0; |
||||
247 | |||||
248 | // This occurrence has been marked as 'Complete' so disable reminder |
||||
249 | if ($markComplete) { |
||||
250 | $taskItemProps[$this->proptags["reset_reminder"]] = false; |
||||
251 | $taskItemProps[$this->proptags["reminder"]] = false; |
||||
252 | $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"]; |
||||
253 | |||||
254 | unset($this->action[$this->proptags['datecompleted']]); |
||||
255 | } |
||||
256 | |||||
257 | // Recurrence ends for this item |
||||
258 | $taskItemProps[$this->proptags["dead_occurrence"]] = true; |
||||
259 | $taskItemProps[$this->proptags["task_f_creator"]] = true; |
||||
260 | |||||
261 | // OL props |
||||
262 | $taskItemProps[$this->proptags["side_effects"]] = 1296; |
||||
263 | $taskItemProps[$this->proptags["icon_index"]] = 1280; |
||||
264 | |||||
265 | // Copy recipients |
||||
266 | $recipienttable = mapi_message_getrecipienttable($this->message); |
||||
267 | $recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]); |
||||
268 | |||||
269 | $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage); |
||||
270 | $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]); |
||||
271 | foreach ($copy_to_recipientRows as $recipient) { |
||||
272 | mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]); |
||||
273 | } |
||||
274 | mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients); |
||||
275 | |||||
276 | // Copy attachments |
||||
277 | $attachmentTable = mapi_message_getattachmenttable($this->message); |
||||
278 | if ($attachmentTable) { |
||||
0 ignored issues
–
show
|
|||||
279 | $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
||||
280 | |||||
281 | foreach ($attachments as $attach_props) { |
||||
282 | $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]); |
||||
283 | $attach_newResourceMsg = mapi_message_createattach($newMessage); |
||||
284 | |||||
285 | mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
||||
286 | mapi_savechanges($attach_newResourceMsg); |
||||
287 | } |
||||
288 | } |
||||
289 | |||||
290 | mapi_setprops($newMessage, $taskItemProps); |
||||
291 | mapi_savechanges($newMessage); |
||||
292 | |||||
293 | // Update body of original message |
||||
294 | $msgbody = mapi_openproperty($this->message, PR_BODY); |
||||
295 | $msgbody = trim($msgbody, "\0"); |
||||
0 ignored issues
–
show
$msgbody of type resource is incompatible with the type string expected by parameter $string of trim() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
296 | $separator = "------------\r\n"; |
||||
297 | |||||
298 | if (!empty($msgbody) && strrpos($msgbody, $separator) === false) { |
||||
299 | $msgbody = $separator . $msgbody; |
||||
300 | $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0); |
||||
0 ignored issues
–
show
The call to
mapi_openproperty() has too many arguments starting with IID_IStream .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||
301 | mapi_stream_setsize($stream, strlen($msgbody)); |
||||
302 | mapi_stream_write($stream, $msgbody); |
||||
303 | mapi_stream_commit($stream); |
||||
304 | } |
||||
305 | |||||
306 | // We need these properties to notify client |
||||
307 | return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
||||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * processOccurrenceItem, adds an item to a list of occurrences, but only if the |
||||
312 | * resulting occurrence starts or ends in the interval <$start, $end>. |
||||
313 | * |
||||
314 | * @param array $items reference to the array to be added to |
||||
315 | * @param int $start start of timeframe in GMT TIME |
||||
316 | * @param int $end end of timeframe in GMT TIME |
||||
317 | * @param int $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE |
||||
318 | * @param int $startocc start of occurrence since beginning of day in minutes |
||||
319 | * @param int $endocc end of occurrence since beginning of day in minutes |
||||
320 | * @param int $tz the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc ) |
||||
321 | * @param bool $reminderonly If TRUE, only add the item if the reminder is set |
||||
322 | */ |
||||
323 | public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) { |
||||
324 | if ($basedate > $start) { |
||||
325 | $newItem = []; |
||||
326 | $newItem[$this->proptags['startdate']] = $basedate; |
||||
327 | |||||
328 | // If startdate and enddate are set on task, then slide enddate according to duration |
||||
329 | if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) { |
||||
330 | $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]); |
||||
331 | } |
||||
332 | else { |
||||
333 | $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']]; |
||||
334 | } |
||||
335 | |||||
336 | $items[] = $newItem; |
||||
337 | } |
||||
338 | } |
||||
339 | |||||
340 | /** |
||||
341 | * Function which marks existing occurrence to 'Complete'. |
||||
342 | * |
||||
343 | * @param array $recur array action from client |
||||
344 | * |
||||
345 | * @return array|bool of properties of regenerated task else false |
||||
346 | */ |
||||
347 | public function markOccurrenceComplete(&$recur) { |
||||
348 | // Fix timezone object |
||||
349 | $this->tz = false; |
||||
350 | $this->action = &$recur; |
||||
351 | $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false; |
||||
352 | |||||
353 | if (!$dead_occurrence) { |
||||
354 | return $this->moveToNextOccurrence(); |
||||
355 | } |
||||
356 | |||||
357 | return false; |
||||
358 | } |
||||
359 | |||||
360 | /** |
||||
361 | * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete. |
||||
362 | * |
||||
363 | * @param mixed $nextOccurrence properties of next occurrence |
||||
364 | */ |
||||
365 | public function setReminder($nextOccurrence): void { |
||||
366 | $props = []; |
||||
367 | if (!empty($nextOccurrence)) { |
||||
368 | // Check if reminder is reset. Default is 'false' |
||||
369 | $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false; |
||||
370 | $reminder = $this->messageprops[$this->proptags['reminder']]; |
||||
371 | |||||
372 | // Either reminder was already set OR reminder was set but was dismissed bty user |
||||
373 | if ($reminder || $reset_reminder) { |
||||
374 | // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate |
||||
375 | $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0; |
||||
376 | $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0; |
||||
377 | $reminder_difference = $reminder_difference - $reminder_time; |
||||
378 | |||||
379 | // Apply duration to next calculated duedate |
||||
380 | $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference; |
||||
381 | |||||
382 | $props[$this->proptags['reminder_time']] = $next_reminder_time; |
||||
383 | $props[$this->proptags['flagdueby']] = $next_reminder_time; |
||||
384 | $this->action['reminder'] = $props[$this->proptags['reminder']] = true; |
||||
385 | } |
||||
386 | } |
||||
387 | else { |
||||
388 | // Didn't get next occurrence, probably this is the last occurrence |
||||
389 | $props[$this->proptags['reminder']] = false; |
||||
390 | $props[$this->proptags['reset_reminder']] = false; |
||||
391 | } |
||||
392 | |||||
393 | if (!empty($props)) { |
||||
394 | mapi_setprops($this->message, $props); |
||||
395 | } |
||||
396 | } |
||||
397 | |||||
398 | /** |
||||
399 | * Function which recurring task to next occurrence. |
||||
400 | * It simply doesn't regenerate task. |
||||
401 | * |
||||
402 | * @param array $action |
||||
403 | * |
||||
404 | * @return array|bool |
||||
405 | */ |
||||
406 | public function deleteOccurrence($action) { |
||||
407 | $this->tz = false; |
||||
408 | $this->action = $action; |
||||
409 | $result = $this->moveToNextOccurrence(); |
||||
410 | |||||
411 | mapi_savechanges($this->message); |
||||
412 | |||||
413 | return $result; |
||||
414 | } |
||||
415 | } |
||||
416 |