class.taskrecurrence.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /*
  3. * Copyright 2005 - 2016 Zarafa and its licensors
  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.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Affero General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. */
  18. ?>
  19. <?php
  20. require_once("class.baserecurrence.php");
  21. class TaskRecurrence extends BaseRecurrence {
  22. /**
  23. * Timezone info which is always false for task
  24. */
  25. var $tz = false;
  26. function __construct($store, $message)
  27. {
  28. $this->store = $store;
  29. $this->message = $message;
  30. $properties = array();
  31. $properties["entryid"] = PR_ENTRYID;
  32. $properties["parent_entryid"] = PR_PARENT_ENTRYID;
  33. $properties["icon_index"] = PR_ICON_INDEX;
  34. $properties["message_class"] = PR_MESSAGE_CLASS;
  35. $properties["message_flags"] = PR_MESSAGE_FLAGS;
  36. $properties["subject"] = PR_SUBJECT;
  37. $properties["importance"] = PR_IMPORTANCE;
  38. $properties["sensitivity"] = PR_SENSITIVITY;
  39. $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
  40. $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
  41. $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
  42. $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
  43. $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
  44. $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
  45. $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
  46. $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
  47. $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
  48. $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
  49. $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
  50. $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
  51. $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
  52. $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
  53. $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
  54. $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
  55. $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
  56. $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
  57. $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
  58. $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
  59. $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
  60. $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
  61. $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
  62. $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
  63. $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
  64. $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
  65. $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
  66. $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
  67. $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
  68. $this->proptags = getPropIdsFromStrings($store, $properties);
  69. parent::__construct($store, $message, $properties);
  70. }
  71. /**
  72. * Function which saves recurrence and also regenerates task if necessary.
  73. *@param array $recur new recurrence properties
  74. *@return array of properties of regenerated task else false
  75. */
  76. function setRecurrence(&$recur)
  77. {
  78. $this->recur = $recur;
  79. $this->action =& $recur;
  80. if(!isset($this->recur["changed_occurences"]))
  81. $this->recur["changed_occurences"] = Array();
  82. if(!isset($this->recur["deleted_occurences"]))
  83. $this->recur["deleted_occurences"] = Array();
  84. if (!isset($this->recur['startocc'])) $this->recur['startocc'] = 0;
  85. if (!isset($this->recur['endocc'])) $this->recur['endocc'] = 0;
  86. // Save recurrence because we need proper startrecurrdate and endrecurrdate
  87. $this->saveRecurrence();
  88. // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saveing recurrence
  89. $msgProps = mapi_getprops($this->message, array($this->proptags['recurring_data']));
  90. $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
  91. foreach($recurring_data as $key => $value) {
  92. $this->recur[$key] = $value;
  93. }
  94. $this->setFirstOccurrence();
  95. // Let's see if next occurrence has to be generated
  96. return $this->moveToNextOccurrence();
  97. }
  98. /**
  99. * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence
  100. */
  101. function setFirstOccurrence()
  102. {
  103. // Check if it is already the first occurrence
  104. if($this->action['start'] == $this->recur["start"]){
  105. return;
  106. }else{
  107. $items = $this->getNextOccurrence();
  108. $props = array();
  109. $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
  110. $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
  111. $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
  112. $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
  113. mapi_setprops($this->message, $props);
  114. }
  115. }
  116. /**
  117. * Function which creates new task as current occurrence and moves the
  118. * existing task to next occurrence.
  119. *
  120. *@param array $recur $action from client
  121. *@return boolean if moving to next occurrence succeed then it returns
  122. * properties of either newly created task or existing task ELSE
  123. * false because that was last occurrence
  124. */
  125. function moveToNextOccurrence()
  126. {
  127. $result = false;
  128. /**
  129. * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
  130. * then we create first two occurrence separately and for first occurrence recurrence has ended.
  131. */
  132. if ((empty($this->action['startdate']) && empty($this->action['duedate']))
  133. || ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])){
  134. $nextOccurrence = $this->getNextOccurrence();
  135. $result = mapi_getprops($this->message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
  136. $props = array();
  137. if ($nextOccurrence) {
  138. if (!isset($this->action['deleteOccurrence'])) {
  139. // Create current occurrence as separate task
  140. $result = $this->regenerateTask($this->action['complete']);
  141. }
  142. // Set reminder for next occurrence
  143. $this->setReminder($nextOccurrence);
  144. // Update properties for next occurrence
  145. $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
  146. $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
  147. $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
  148. $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
  149. // If current task as been mark as 'Complete' then next occurrence should be incomplete.
  150. if (isset($this->action['complete']) && $this->action['complete'] == 1) {
  151. $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
  152. $this->action['complete'] = $props[$this->proptags["complete"]] = false;
  153. $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
  154. }
  155. $props[$this->proptags["dead_occurrence"]] = false;
  156. } else {
  157. if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])
  158. return false;
  159. // Didn't get next occurrence, probably this is the last one, so recurrence ends here
  160. $props[$this->proptags["dead_occurrence"]] = true;
  161. $props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
  162. $props[$this->proptags["task_f_creator"]] = true;
  163. //OL props
  164. $props[$this->proptags["side_effects"]] = 1296;
  165. $props[$this->proptags["icon_index"]] = 1280;
  166. }
  167. mapi_setprops($this->message, $props);
  168. }
  169. return $result;
  170. }
  171. /**
  172. * Function which return properties of next occurrence
  173. *@return array startdate/enddate of next occurrence
  174. */
  175. function getNextOccurrence()
  176. {
  177. if ($this->recur) {
  178. $items = array();
  179. //@TODO: fix start of range
  180. $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
  181. $dayend = ($this->recur['term'] == 0x23) ? 0x7fffffff : $this->dayStartOf($this->recur["end"]);
  182. // Fix recur object
  183. $this->recur['startocc'] = 0;
  184. $this->recur['endocc'] = 0;
  185. // Retrieve next occurrence
  186. $items = $this->getItems($start, $dayend, 1);
  187. return !empty($items) ? $items[0] : false;
  188. }
  189. }
  190. /**
  191. * Function which clones current occurrence and sets appropriate properties.
  192. * The original recurring item is moved to next occurrence.
  193. *@param boolean $markComplete true if existing occurrence has to be mark complete else false.
  194. */
  195. function regenerateTask($markComplete)
  196. {
  197. // Get all properties
  198. $taskItemProps = mapi_getprops($this->message);
  199. if (isset($this->action["subject"])) $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
  200. if (isset($this->action["importance"])) $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
  201. if (isset($this->action["startdate"])) {
  202. $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
  203. $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
  204. }
  205. if (isset($this->action["duedate"])) {
  206. $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
  207. $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
  208. }
  209. $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
  210. $newMessage = mapi_folder_createmessage($folder);
  211. $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
  212. $taskItemProps[$this->proptags["complete"]] = $markComplete;
  213. $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
  214. // This occurrence has been marked as 'Complete' so disable reminder
  215. if ($markComplete) {
  216. $taskItemProps[$this->proptags["reset_reminder"]] = false;
  217. $taskItemProps[$this->proptags["reminder"]] = false;
  218. $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
  219. unset($this->action[$this->proptags['datecompleted']]);
  220. }
  221. // Recurrence ends for this item
  222. $taskItemProps[$this->proptags["dead_occurrence"]] = true;
  223. $taskItemProps[$this->proptags["task_f_creator"]] = true;
  224. //OL props
  225. $taskItemProps[$this->proptags["side_effects"]] = 1296;
  226. $taskItemProps[$this->proptags["icon_index"]] = 1280;
  227. // Copy recipients
  228. $recipienttable = mapi_message_getrecipienttable($this->message);
  229. $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));
  230. $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
  231. $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
  232. foreach($copy_to_recipientRows as $recipient) {
  233. mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, array($recipient));
  234. }
  235. mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
  236. // Copy attachments
  237. $attachmentTable = mapi_message_getattachmenttable($this->message);
  238. if($attachmentTable) {
  239. $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));
  240. foreach($attachments as $attach_props){
  241. $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
  242. $attach_newResourceMsg = mapi_message_createattach($newMessage);
  243. mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
  244. mapi_savechanges($attach_newResourceMsg);
  245. }
  246. }
  247. mapi_setprops($newMessage, $taskItemProps);
  248. mapi_savechanges($newMessage);
  249. // Update body of original message
  250. $msgbody = mapi_message_openproperty($this->message, PR_BODY);
  251. $msgbody = trim($msgbody, "\0");
  252. $separator = "------------\r\n";
  253. if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
  254. $msgbody = $separator . $msgbody;
  255. $stream = mapi_openproperty($this->message, PR_BODY, 0, MAPI_CREATE | MAPI_MODIFY);
  256. mapi_stream_setsize($stream, strlen($msgbody));
  257. mapi_stream_write($stream, $msgbody);
  258. mapi_stream_commit($stream);
  259. }
  260. // We need these properties to notify client
  261. return mapi_getprops($newMessage, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
  262. }
  263. /**
  264. * processOccurrenceItem, adds an item to a list of occurrences, but only if the
  265. * resulting occurrence starts or ends in the interval <$start, $end>
  266. * @param array $items reference to the array to be added to
  267. * @param date $start start of timeframe in GMT TIME
  268. * @param date $end end of timeframe in GMT TIME
  269. * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
  270. */
  271. function processOccurrenceItem(&$items, $start, $end, $now)
  272. {
  273. if ($now <= $start)
  274. return;
  275. $newItem = array();
  276. $newItem[$this->proptags['startdate']] = $now;
  277. // If startdate and enddate are set on task, then slide enddate according to duration
  278. if (isset($this->messageprops[$this->proptags["startdate"]]) && isset($this->messageprops[$this->proptags["duedate"]]))
  279. $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
  280. else
  281. $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
  282. $items[] = $newItem;
  283. }
  284. /**
  285. * Function which marks existing occurrence to 'Complete'
  286. *@param array $recur array action from client
  287. *@return array of properties of regenerated task else false
  288. */
  289. function markOccurrenceComplete(&$recur)
  290. {
  291. // Fix timezone object
  292. $this->tz = false;
  293. $this->action =& $recur;
  294. $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
  295. if (!$dead_occurrence) {
  296. return $this->moveToNextOccurrence();
  297. }
  298. return false;
  299. }
  300. /**
  301. * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
  302. *@param array $nextOccurrence properties of next occurrence
  303. */
  304. function setReminder($nextOccurrence)
  305. {
  306. $props = array();
  307. if ($nextOccurrence) {
  308. // Check if reminder is reset. Default is 'false'
  309. $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
  310. $reminder = $this->messageprops[$this->proptags['reminder']];
  311. // Either reminder was already set OR reminder was set but was dismissed bty user
  312. if ($reminder || $reset_reminder) {
  313. // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
  314. $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
  315. $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
  316. $reminder_difference = $reminder_difference - $reminder_time;
  317. // Apply duration to next calculated duedate
  318. $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
  319. $props[$this->proptags['reminder_time']] = $next_reminder_time;
  320. $props[$this->proptags['flagdueby']] = $next_reminder_time;
  321. $this->action['reminder'] = $props[$this->proptags['reminder']] = true;
  322. }
  323. } else {
  324. // Didn't get next occurrence, probably this is the last occurrence
  325. $props[$this->proptags['reminder']] = false;
  326. $props[$this->proptags['reset_reminder']] = false;
  327. }
  328. if (!empty($props))
  329. mapi_setprops($this->message, $props);
  330. }
  331. /**
  332. * Function which recurring task to next occurrence.
  333. * It simply doesn't regenerate task
  334. @param array $action
  335. */
  336. function deleteOccurrence($action)
  337. {
  338. $this->tz = false;
  339. $this->action = $action;
  340. $result = $this->moveToNextOccurrence();
  341. mapi_savechanges($this->message);
  342. return $result;
  343. }
  344. }
  345. ?>