source: trunk/zpush/backend/zarafa/mapi/class.taskrecurrence.php @ 7589

Revision 7589, 22.2 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/*
3 * Copyright 2005 - 2012  Zarafa B.V.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License, version 3,
7 * as published by the Free Software Foundation with the following additional
8 * term according to sec. 7:
9 *
10 * According to sec. 7 of the GNU Affero General Public License, version
11 * 3, the terms of the AGPL are supplemented with the following terms:
12 *
13 * "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
14 * the Program under the AGPL does not imply a trademark license.
15 * Therefore any rights, title and interest in our trademarks remain
16 * entirely with us.
17 *
18 * However, if you propagate an unmodified version of the Program you are
19 * allowed to use the term "Zarafa" to indicate that you distribute the
20 * Program. Furthermore you may use our trademarks where it is necessary
21 * to indicate the intended purpose of a product or service provided you
22 * use it in accordance with honest practices in industrial or commercial
23 * matters.  If you want to propagate modified versions of the Program
24 * under the name "Zarafa" or "Zarafa Server", you may only do so if you
25 * have a written permission by Zarafa B.V. (to acquire a permission
26 * please contact Zarafa at trademark@zarafa.com).
27 *
28 * The interactive user interface of the software displays an attribution
29 * notice containing the term "Zarafa" and/or the logo of Zarafa.
30 * Interactive user interfaces of unmodified and modified versions must
31 * display Appropriate Legal Notices according to sec. 5 of the GNU
32 * Affero General Public License, version 3, when you propagate
33 * unmodified or modified versions of the Program. In accordance with
34 * sec. 7 b) of the GNU Affero General Public License, version 3, these
35 * Appropriate Legal Notices must retain the logo of Zarafa or display
36 * the words "Initial Development by Zarafa" if the display of the logo
37 * is not reasonably feasible for technical reasons. The use of the logo
38 * of Zarafa in Legal Notices is allowed for unmodified and modified
39 * versions of the software.
40 *
41 * This program is distributed in the hope that it will be useful,
42 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 * GNU Affero General Public License for more details.
45 *
46 * You should have received a copy of the GNU Affero General Public License
47 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
48 *
49 */
50
51
52    require_once("backend/zarafa/mapi/class.baserecurrence.php");
53
54    class TaskRecurrence extends BaseRecurrence
55    {
56        /**
57         * Timezone info which is always false for task
58         */
59        var $tz = false;
60
61        function TaskRecurrence($store, $message)
62        {
63            $this->store = $store;
64            $this->message = $message;
65
66            $properties = array();
67            $properties["entryid"] = PR_ENTRYID;
68            $properties["parent_entryid"] = PR_PARENT_ENTRYID;
69            $properties["icon_index"] = PR_ICON_INDEX;
70            $properties["message_class"] = PR_MESSAGE_CLASS;
71            $properties["message_flags"] = PR_MESSAGE_FLAGS;
72            $properties["subject"] = PR_SUBJECT;
73            $properties["importance"] = PR_IMPORTANCE;
74            $properties["sensitivity"] = PR_SENSITIVITY;
75            $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
76            $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
77            $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
78            $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
79            $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
80            $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
81            $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
82            $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
83            $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
84            $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
85            $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
86            $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
87            $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
88            $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
89            $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
90
91            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
92            $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
93            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
94
95            $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
96            $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
97            $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
98            $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
99
100            $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
101            $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
102            $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
103            $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
104            $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
105            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
106            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
107
108            $this->proptags = getPropIdsFromStrings($store, $properties);
109
110            parent::BaseRecurrence($store, $message, $properties);
111        }
112
113        /**
114         * Function which saves recurrence and also regenerates task if necessary.
115         *@param array $recur new recurrence properties
116         *@return array of properties of regenerated task else false
117         */
118        function setRecurrence(&$recur)
119        {
120            $this->recur = $recur;
121            $this->action =& $recur;
122
123            if(!isset($this->recur["changed_occurences"]))
124                $this->recur["changed_occurences"] = Array();
125
126            if(!isset($this->recur["deleted_occurences"]))
127                $this->recur["deleted_occurences"] = Array();
128
129            if (!isset($this->recur['startocc'])) $this->recur['startocc'] = 0;
130            if (!isset($this->recur['endocc'])) $this->recur['endocc'] = 0;
131
132            // Save recurrence because we need proper startrecurrdate and endrecurrdate
133            $this->saveRecurrence();
134
135            // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saveing recurrence
136            $msgProps = mapi_getprops($this->message, array($this->proptags['recurring_data']));
137            $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
138            foreach($recurring_data as $key => $value) {
139                $this->recur[$key] = $value;
140            }
141
142            $this->setFirstOccurrence();
143
144            // Let's see if next occurrence has to be generated
145            return $this->moveToNextOccurrence();
146        }
147
148        /**
149         * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence
150         */
151        function setFirstOccurrence()
152        {
153            // Check if it is already the first occurrence
154            if($this->action['start'] == $this->recur["start"]){
155                return;
156            }else{
157                $items = $this->getNextOccurrence();
158
159                $props = array();
160                $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
161                $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
162
163                $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
164                $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
165
166                mapi_setprops($this->message, $props);
167            }
168        }
169
170        /**
171         * Function which creates new task as current occurrence and moves the
172         * existing task to next occurrence.
173         *
174         *@param array $recur $action from client
175         *@return boolean if moving to next occurrence succeed then it returns
176         *        properties of either newly created task or existing task ELSE
177         *        false because that was last occurrence
178         */
179        function moveToNextOccurrence()
180        {
181            $result = false;
182            /**
183             * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
184             * then we create first two occurrence separately and for first occurrence recurrence has ended.
185             */
186            if ((empty($this->action['startdate']) && empty($this->action['duedate']))
187                || ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])){
188
189                $nextOccurrence = $this->getNextOccurrence();
190                $result = mapi_getprops($this->message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
191
192                $props = array();
193                if ($nextOccurrence) {
194                    if (!isset($this->action['deleteOccurrence'])) {
195                        // Create current occurrence as separate task
196                        $result = $this->regenerateTask($this->action['complete']);
197                    }
198
199                    // Set reminder for next occurrence
200                    $this->setReminder($nextOccurrence);
201
202                    // Update properties for next occurrence
203                    $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
204                    $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
205
206                    $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
207                    $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
208
209                    // If current task as been mark as 'Complete' then next occurrence should be uncomplete.
210                    if (isset($this->action['complete']) && $this->action['complete'] == 1) {
211                        $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
212                        $this->action['complete'] = $props[$this->proptags["complete"]] = false;
213                        $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
214                    }
215
216                    $props[$this->proptags["dead_occurrence"]] = false;
217                } else {
218                    if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])
219                        return false;
220
221                    // Didn't get next occurrence, probably this is the last one, so recurrence ends here
222                    $props[$this->proptags["dead_occurrence"]] = true;
223                    $props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
224                    $props[$this->proptags["task_f_creator"]] = true;
225
226                    //OL props
227                    $props[$this->proptags["side_effects"]] = 1296;
228                    $props[$this->proptags["icon_index"]] = 1280;
229                }
230
231                mapi_setprops($this->message, $props);
232            }
233
234            return $result;
235        }
236
237        /**
238         * Function which return properties of next occurrence
239         *@return array startdate/enddate of next occurrence
240         */
241        function getNextOccurrence()
242        {
243            if ($this->recur) {
244                $items = array();
245
246                //@TODO: fix start of range
247                $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
248                $dayend = ($this->recur['term'] == 0x23) ? 0x7fffffff : $this->dayStartOf($this->recur["end"]);
249
250                // Fix recur object
251                $this->recur['startocc'] = 0;
252                $this->recur['endocc'] = 0;
253
254                // Retrieve next occurrence
255                $items = $this->getItems($start, $dayend, 1);
256
257                return !empty($items) ? $items[0] : false;
258            }
259        }
260
261        /**
262         * Function which clones current occurrence and sets appropriate properties.
263         * The original recurring item is moved to next occurrence.
264         *@param boolean $markComplete true if existing occurrence has to be mark complete else false.
265         */
266        function regenerateTask($markComplete)
267        {
268            // Get all properties
269            $taskItemProps = mapi_getprops($this->message);
270
271            if (isset($this->action["subject"])) $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
272            if (isset($this->action["importance"])) $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
273            if (isset($this->action["startdate"])) {
274                $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
275                $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
276            }
277            if (isset($this->action["duedate"])) {
278                $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
279                $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
280            }
281
282            $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
283            $newMessage = mapi_folder_createmessage($folder);
284
285            $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
286            $taskItemProps[$this->proptags["complete"]] = $markComplete;
287            $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
288
289            // This occurrence has been marked as 'Complete' so disable reminder
290            if ($markComplete) {
291                $taskItemProps[$this->proptags["reset_reminder"]] = false;
292                $taskItemProps[$this->proptags["reminder"]] = false;
293                $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
294
295                unset($this->action[$this->proptags['datecompleted']]);
296            }
297
298            // Recurrence ends for this item
299            $taskItemProps[$this->proptags["dead_occurrence"]] = true;
300            $taskItemProps[$this->proptags["task_f_creator"]] = true;
301
302            //OL props
303            $taskItemProps[$this->proptags["side_effects"]] = 1296;
304            $taskItemProps[$this->proptags["icon_index"]] = 1280;
305
306            // Copy recipients
307            $recipienttable = mapi_message_getrecipienttable($this->message);
308            $recipients = mapi_table_queryallrows($recipienttable, array(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));
309
310            $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
311            $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
312            foreach($copy_to_recipientRows as $recipient) {
313                mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, array($recipient));
314            }
315            mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
316
317            // Copy attachments
318            $attachmentTable = mapi_message_getattachmenttable($this->message);
319            if($attachmentTable) {
320                $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD));
321
322                foreach($attachments as $attach_props){
323                    $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
324                    $attach_newResourceMsg = mapi_message_createattach($newMessage);
325
326                    mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
327                    mapi_savechanges($attach_newResourceMsg);
328                }
329            }
330
331            mapi_setprops($newMessage, $taskItemProps);
332            mapi_savechanges($newMessage);
333
334            // Update body of original message
335            $msgbody = mapi_message_openproperty($this->message, PR_BODY);
336            $msgbody = trim($this->windows1252_to_utf8($msgbody), "\0");
337            $separator = "------------\r\n";
338
339            if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
340                $msgbody = $separator . $msgbody;
341                $stream = mapi_openpropertytostream($this->message, PR_BODY, MAPI_CREATE | MAPI_MODIFY);
342                mapi_stream_setsize($stream, strlen($msgbody));
343                mapi_stream_write($stream, $msgbody);
344                mapi_stream_commit($stream);
345            }
346
347            // We need these properties to notify client
348            return mapi_getprops($newMessage, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
349        }
350
351        /**
352         * processOccurrenceItem, adds an item to a list of occurrences, but only if the
353         * resulting occurrence starts or ends in the interval <$start, $end>
354         * @param array $items reference to the array to be added to
355         * @param date $start start of timeframe in GMT TIME
356         * @param date $end end of timeframe in GMT TIME
357         * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
358         */
359        function processOccurrenceItem(&$items, $start, $end, $now)
360        {
361            if ($now > $start) {
362                $newItem = array();
363                $newItem[$this->proptags['startdate']] = $now;
364
365                // If startdate and enddate are set on task, then slide enddate according to duration
366                if (isset($this->messageprops[$this->proptags["startdate"]]) && isset($this->messageprops[$this->proptags["duedate"]])) {
367                    $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
368                } else {
369                    $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
370                }
371
372                $items[] = $newItem;
373            }
374        }
375
376        /**
377         * Function which marks existing occurrence to 'Complete'
378         *@param array $recur array action from client
379         *@return array of properties of regenerated task else false
380         */
381        function markOccurrenceComplete(&$recur)
382        {
383            // Fix timezone object
384            $this->tz = false;
385            $this->action =& $recur;
386            $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
387
388            if (!$dead_occurrence) {
389                return $this->moveToNextOccurrence();
390            }
391
392            return false;
393        }
394
395        /**
396         * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
397         *@param array $nextOccurrence properties of next occurrence
398         */
399        function setReminder($nextOccurrence)
400        {
401            $props = array();
402            if ($nextOccurrence) {
403                // Check if reminder is reset. Default is 'false'
404                $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
405                $reminder = $this->messageprops[$this->proptags['reminder']];
406
407                // Either reminder was already set OR reminder was set but was dismissed bty user
408                if ($reminder || $reset_reminder) {
409                    // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
410                    $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
411                    $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
412                    $reminder_difference = $reminder_difference - $reminder_time;
413
414                    // Apply duration to next calculated duedate
415                    $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
416
417                    $props[$this->proptags['reminder_time']] = $next_reminder_time;
418                    $props[$this->proptags['flagdueby']] = $next_reminder_time;
419                    $this->action['reminder'] = $props[$this->proptags['reminder']] = true;
420                }
421            } else {
422                // Didn't get next occurrence, probably this is the last occurrence
423                $props[$this->proptags['reminder']] = false;
424                $props[$this->proptags['reset_reminder']] = false;
425            }
426
427            if (!empty($props))
428                mapi_setprops($this->message, $props);
429        }
430
431        /**
432         * Function which recurring task to next occurrence.
433         * It simply doesn't regenerate task
434         @param array $action
435         */
436        function deleteOccurrence($action)
437        {
438            $this->tz = false;
439            $this->action = $action;
440            $result = $this->moveToNextOccurrence();
441
442            mapi_savechanges($this->message);
443
444            return $result;
445        }
446
447        /**
448        * Convert from windows-1252 encoded string to UTF-8 string
449        *
450        * The same conversion rules as utf8_to_windows1252 apply.
451        *
452        * @param string $string the Windows-1252 string to convert
453        * @return string UTF-8 representation of the string
454        */
455        function windows1252_to_utf8($string)
456        {
457            if (function_exists("iconv")){
458                return iconv("Windows-1252", "UTF-8//TRANSLIT", $string);
459            }else{
460                return utf8_encode($string); // no euro support here
461            }
462        }
463    }
464?>
Note: See TracBrowser for help on using the repository browser.