class.taskrequest.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  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. /*
  21. * In general
  22. *
  23. * This class never actually modifies a task item unless we receive a task request update. This means
  24. * that setting all the properties to make the task item itself behave like a task request is up to the
  25. * caller.
  26. *
  27. * The only exception to this is the generation of the TaskGlobalObjId, the unique identifier identifying
  28. * this task request to both the organizer and the assignee. The globalobjectid is generated when the
  29. * task request is sent via sendTaskRequest.
  30. */
  31. /* The TaskMode value is only used for the IPM.TaskRequest items. It must 0 (tdmtNothing) on IPM.Task items.
  32. *
  33. * It is used to indicate the type of change that is being carried in the IPM.TaskRequest item (although this
  34. * information seems redundant due to that information already being available in PR_MESSAGE_CLASS).
  35. */
  36. define('tdmtNothing', 0); // Value in IPM.Task items
  37. define('tdmtTaskReq', 1); // Assigner -> Assignee
  38. define('tdmtTaskAcc', 2); // Assignee -> Assigner
  39. define('tdmtTaskDec', 3); // Assignee -> Assigner
  40. define('tdmtTaskUpd', 4); // Assignee -> Assigner
  41. define('tdmtTaskSELF', 5); // Assigner -> Assigner (?)
  42. /* The TaskHistory is used to show the last action on the task on both the assigner and the assignee's side.
  43. *
  44. * It is used in combination with 'AssignedTime' and 'tasklastdelegate' or 'tasklastuser' to show the information
  45. * at the top of the task request in the format 'Accepted by <user> on 01-01-2010 11:00'.
  46. */
  47. define('thNone', 0);
  48. define('thAccepted', 1); // Set by assignee
  49. define('thDeclined', 2); // Set by assignee
  50. define('thUpdated', 3); // Set by assignee
  51. define('thDueDateChanged', 4);
  52. define('thAssigned', 5); // Set by assigner
  53. /* The TaskState value is used to differentiate the version of a task in the assigner's folder and the version in the
  54. * assignee's folder. The buttons shown depend on this and the 'taskaccepted' boolean (for the assignee)
  55. */
  56. define('tdsNOM', 0); // Got a response to a deleted task, and re-created the task for the assigner
  57. define('tdsOWNNEW', 1); // Not assigned
  58. define('tdsOWN', 2); // Assignee version
  59. define('tdsACC', 3); // Assigner version
  60. define('tdsDEC', 4); // Assigner version, but assignee declined
  61. /* The delegationstate is used for the assigner to indicate state
  62. */
  63. define('olTaskNotDelegated', 0);
  64. define('olTaskDelegationUnknown', 1); // After sending req
  65. define('olTaskDelegationAccepted', 2); // After receiving accept
  66. define('olTaskDelegationDeclined', 3); // After receiving decline
  67. /* The task ownership indicates the role of the current user relative to the task.
  68. */
  69. define('olNewTask', 0);
  70. define('olDelegatedTask', 1); // Task has been assigned
  71. define('olOwnTask', 2); // Task owned
  72. /* taskmultrecips indicates whether the task request sent or received has multiple assignees or not.
  73. */
  74. define('tmrNone', 0);
  75. define('tmrSent', 1); // Task has been sent to multiple assignee
  76. define('tmrReceived', 2); // Task Request received has multiple assignee
  77. class TaskRequest {
  78. // All recipient properties
  79. var $recipprops = 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, PR_SEARCH_KEY);
  80. /*
  81. * Constructs a TaskRequest object for the specified message. This can be either the task request
  82. * message itself (in the inbox) or the task in the tasks folder, depending on the action to be performed.
  83. *
  84. * As a general rule, the object message passed is the object 'in view' when the user performs one of the
  85. * actions in this class.
  86. *
  87. * @param $store store MAPI Store in which $message resides. This is also the store where the tasks folder is assumed to be in
  88. * @param $message message MAPI Message to which the task request referes (can be an email or a task)
  89. * @param $session session MAPI Session which is used to open tasks folders for delegated task requests or responses
  90. */
  91. function __construct($store, $message, $session) {
  92. $this->store = $store;
  93. $this->message = $message;
  94. $this->session = $session;
  95. $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
  96. $properties["updatecount"] = "PT_LONG:PSETID_Task:0x8112";
  97. $properties["taskstate"] = "PT_LONG:PSETID_Task:0x8113";
  98. $properties["taskmultrecips"] = "PT_LONG:PSETID_Task:0x8120";
  99. $properties["taskupdates"] = "PT_BOOLEAN:PSETID_Task:0x811b";
  100. $properties["tasksoc"] = "PT_BOOLEAN:PSETID_Task:0x8119";
  101. $properties["taskhistory"] = "PT_LONG:PSETID_Task:0x811a";
  102. $properties["taskmode"] = "PT_LONG:PSETID_Common:0x8518";
  103. $properties["taskglobalobjid"] = "PT_BINARY:PSETID_Common:0x8519";
  104. $properties["complete"] = "PT_BOOLEAN:PSETID_Common:0x811c";
  105. $properties["assignedtime"] = "PT_SYSTIME:PSETID_Task:0x8115";
  106. $properties["taskfcreator"] = "PT_BOOLEAN:PSETID_Task:0x0x811e";
  107. $properties["tasklastuser"] = "PT_STRING8:PSETID_Task:0x8122";
  108. $properties["tasklastdelegate"] = "PT_STRING8:PSETID_Task:0x8125";
  109. $properties["taskaccepted"] = "PT_BOOLEAN:PSETID_Task:0x8108";
  110. $properties["delegationstate"] = "PT_LONG:PSETID_Task:0x812a";
  111. $properties["ownership"] = "PT_LONG:PSETID_Task:0x8129";
  112. $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
  113. $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
  114. $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
  115. $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
  116. $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
  117. $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
  118. $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
  119. $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
  120. $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
  121. $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
  122. $properties["companies"] = "PT_MV_STRING8:PSETID_Common:0x8539";
  123. $properties["mileage"] = "PT_STRING8:PSETID_Common:0x8534";
  124. $properties["billinginformation"] = "PT_STRING8:PSETID_Common:0x8535";
  125. $this->props = getPropIdsFromStrings($store, $properties);
  126. }
  127. // General functions
  128. /* Return TRUE if the item is a task request message
  129. */
  130. function isTaskRequest()
  131. {
  132. $props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
  133. if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.TaskRequest")
  134. return true;
  135. }
  136. /* Return TRUE if the item is a task response message
  137. */
  138. function isTaskRequestResponse() {
  139. $props = mapi_getprops($this->message, Array(PR_MESSAGE_CLASS));
  140. if (isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.TaskRequest.") === 0)
  141. return true;
  142. }
  143. /*
  144. * Gets the task associated with an IPM.TaskRequest message
  145. *
  146. * If the task does not exist yet, it is created, using the attachment object in the
  147. * task request item.
  148. */
  149. function getAssociatedTask($create)
  150. {
  151. $props = mapi_getprops($this->message, array(PR_MESSAGE_CLASS, $this->props['taskglobalobjid']));
  152. if($props[PR_MESSAGE_CLASS] == "IPM.Task")
  153. return $this->message; // Message itself is task, so return that
  154. $tfolder = $this->getDefaultTasksFolder();
  155. $globalobjid = $props[$this->props['taskglobalobjid']];
  156. // Find the task by looking for the taskglobalobjid
  157. $restriction = array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid));
  158. $contents = mapi_folder_getcontentstable($tfolder);
  159. $rows = mapi_table_queryallrows($contents, array(PR_ENTRYID), $restriction);
  160. if (!empty($rows)) {
  161. // If there are multiple, just use the first
  162. $entryid = $rows[0][PR_ENTRYID];
  163. $store = $this->getTaskFolderStore();
  164. return mapi_msgstore_openentry($store, $entryid);
  165. }
  166. // None found, create one if possible
  167. if(!$create)
  168. return false;
  169. $task = mapi_folder_createmessage($tfolder);
  170. $sub = $this->getEmbeddedTask($this->message);
  171. mapi_copyto($sub, array(), array(), $task);
  172. // Copy sender information from the e-mail
  173. $senderprops = mapi_getprops($this->message, array(PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY));
  174. mapi_setprops($task, $senderprops);
  175. $senderprops = mapi_getprops($this->message, array(PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENDER_ENTRYID, PR_SENDER_ADDRTYPE, PR_SENDER_SEARCH_KEY));
  176. mapi_setprops($task, $senderprops);
  177. return $task;
  178. }
  179. // Organizer functions (called by the organizer)
  180. /* Processes a task request response, which can be any of the following:
  181. * - Task accept (task history is marked as accepted)
  182. * - Task decline (task history is marked as declined)
  183. * - Task update (updates completion %, etc)
  184. */
  185. function processTaskResponse() {
  186. $messageprops = mapi_getprops($this->message, array(PR_PROCESSED));
  187. if(isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED])
  188. return true;
  189. // Get the task for this response
  190. $task = $this->getAssociatedTask(false);
  191. if(!$task) {
  192. // Got a response for a task that has been deleted, create a new one and mark it as such
  193. $task = $this->getAssociatedTask(true);
  194. // tdsNOM indicates a task request that had gone missing
  195. mapi_setprops($task, array($this->props['taskstate'] => tdsNOM ));
  196. }
  197. // Get the embedded task information and copy it into our task
  198. $sub = $this->getEmbeddedTask($this->message);
  199. mapi_copyto($sub, array(), array($this->props['taskstate'], $this->props['taskhistory'], $this->props['taskmode'], $this->props['taskfcreator']), $task);
  200. $props = mapi_getprops($this->message, array(PR_MESSAGE_CLASS));
  201. // Set correct taskmode and taskhistory depending on response type
  202. switch($props[PR_MESSAGE_CLASS]) {
  203. case 'IPM.TaskRequest.Accept':
  204. $taskhistory = thAccepted;
  205. $taskstate = tdsACC;
  206. $delegationstate = olTaskDelegationAccepted;
  207. break;
  208. case 'IPM.TaskRequest.Decline':
  209. $taskhistory = thDeclined;
  210. $taskstate = tdsDEC;
  211. $delegationstate = olTaskDelegationDeclined;
  212. break;
  213. case 'IPM.TaskRequest.Update':
  214. $taskhistory = thUpdated;
  215. $taskstate = tdsACC; // Doesn't actually change anything
  216. $delegationstate = olTaskDelegationAccepted;
  217. break;
  218. }
  219. // Update taskstate (what the task looks like) and task history (last action done by the assignee)
  220. mapi_setprops($task, array($this->props['taskhistory'] => $taskhistory, $this->props['taskstate'] => $taskstate, $this->props['delegationstate'] => $delegationstate, $this->props['ownership'] => olDelegatedTask));
  221. mapi_setprops($this->message, array(PR_PROCESSED => true));
  222. mapi_savechanges($task);
  223. return true;
  224. }
  225. /* Create a new message in the current user's outbox and submit it
  226. *
  227. * Takes the task passed in the constructor as the task to be sent; recipient should
  228. * be pre-existing. The task request will be sent to all recipients.
  229. */
  230. function sendTaskRequest($prefix) {
  231. // Generate a TaskGlobalObjectId
  232. $taskid = $this->createTGOID();
  233. $messageprops = mapi_getprops($this->message, array(PR_SUBJECT));
  234. // Set properties on Task Request
  235. mapi_setprops($this->message, array(
  236. $this->props['taskglobalobjid'] => $taskid, /* our new taskglobalobjid */
  237. $this->props['taskstate'] => tdsACC, /* state for our outgoing request */
  238. $this->props['taskmode'] => tdmtNothing, /* we're not sending a change */
  239. $this->props['updatecount'] => 2, /* version 2 (no idea) */
  240. $this->props['delegationstate'] => olTaskDelegationUnknown, /* no reply yet */
  241. $this->props['ownership'] => olDelegatedTask, /* Task has been assigned */
  242. $this->props['taskhistory'] => thAssigned, /* Task has been assigned */
  243. PR_ICON_INDEX => 1283 /* Task request icon*/
  244. ));
  245. $this->setLastUser();
  246. $this->setOwnerForAssignor();
  247. mapi_savechanges($this->message);
  248. // Create outgoing task request message
  249. $outgoing = $this->createOutgoingMessage();
  250. // No need to copy attachments as task will be attached as embedded message.
  251. mapi_copyto($this->message, array(), array(PR_MESSAGE_ATTACHMENTS), $outgoing);
  252. // Make it a task request, and put it in sent items after it is sent
  253. mapi_setprops($outgoing, array(
  254. PR_MESSAGE_CLASS => "IPM.TaskRequest", /* class is task request */
  255. $this->props['taskstate'] => tdsOWNNEW, /* for the recipient the task is new */
  256. $this->props['taskmode'] => tdmtTaskReq, /* for the recipient, it is a request */
  257. $this->props['updatecount'] => 1, /* version 2 is in the attachment */
  258. PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
  259. PR_ICON_INDEX => 0xFFFFFFFF, /* show assigned icon */
  260. ));
  261. // Set Body
  262. $body = $this->getBody();
  263. $stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
  264. mapi_stream_setsize($stream, strlen($body));
  265. mapi_stream_write($stream, $body);
  266. mapi_stream_commit($stream);
  267. $attach = mapi_message_createattach($outgoing);
  268. mapi_setprops($attach, array(PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT]));
  269. $sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_MODIFY | MAPI_CREATE);
  270. mapi_copyto($this->message, array(), array(), $sub);
  271. mapi_savechanges($sub);
  272. mapi_savechanges($attach);
  273. mapi_savechanges($outgoing);
  274. mapi_message_submitmessage($outgoing);
  275. return true;
  276. }
  277. // Assignee functions (called by the assignee)
  278. /* Update task version counter
  279. *
  280. * Must be called before each update to increase counter
  281. */
  282. function updateTaskRequest() {
  283. $messageprops = mapi_getprops($this->message, array($this->props['updatecount']));
  284. if (isset($messageprops))
  285. $messageprops[$this->props['updatecount']]++;
  286. else
  287. $messageprops[$this->props['updatecount']] = 1;
  288. mapi_setprops($this->message, $messageprops);
  289. }
  290. /* Process a task request
  291. *
  292. * Message passed should be an IPM.TaskRequest message. The task request is then processed to create
  293. * the task in the tasks folder if needed.
  294. */
  295. function processTaskRequest() {
  296. if(!$this->isTaskRequest())
  297. return false;
  298. $messageprops = mapi_getprops($this->message, array(PR_PROCESSED));
  299. if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED])
  300. return true;
  301. $task = $this->getAssociatedTask(true);
  302. $taskProps = mapi_getprops($task, array($this->props['taskmultrecips']));
  303. // 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
  304. $taskProps[$this->props["taskstate"]] = tdsOWN;
  305. $taskProps[$this->props["taskhistory"]] = thAssigned;
  306. $taskProps[$this->props["taskmode"]] = tdmtNothing;
  307. $taskProps[$this->props["taskaccepted"]] = false;
  308. $taskProps[$this->props["taskfcreator"]] = false;
  309. $taskProps[$this->props["ownership"]] = olOwnTask;
  310. $taskProps[$this->props["delegationstate"]] = olTaskNotDelegated;
  311. $taskProps[PR_ICON_INDEX] = 1282;
  312. // This task was assigned to multiple recips, so set this user as owner
  313. if (isset($taskProps[$this->props['taskmultrecips']]) && $taskProps[$this->props['taskmultrecips']] == tmrSent) {
  314. $loginUserData = $this->retrieveUserData();
  315. if ($loginUserData) {
  316. $taskProps[$this->props['owner']] = $loginUserData[PR_DISPLAY_NAME];
  317. $taskProps[$this->props['taskmultrecips']] = tmrReceived;
  318. }
  319. }
  320. mapi_setprops($task, $taskProps);
  321. $this->setAssignorInRecipients($task);
  322. mapi_savechanges($task);
  323. $taskprops = mapi_getprops($task, array(PR_ENTRYID));
  324. mapi_setprops($this->message, array(PR_PROCESSED => true));
  325. mapi_savechanges($this->message);
  326. return $taskprops[PR_ENTRYID];
  327. }
  328. /* Accept a task request and send the response.
  329. *
  330. * Message passed should be an IPM.Task (eg the task from getAssociatedTask())
  331. *
  332. * Copies the task to the user's task folder, sets it to accepted, and sends the acceptation
  333. * message back to the organizer. The caller is responsible for removing the message.
  334. *
  335. * @return entryid EntryID of the accepted task
  336. */
  337. function doAccept($prefix) {
  338. $messageprops = mapi_getprops($this->message, array($this->props['taskstate']));
  339. if(!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN)
  340. return false; // Can only accept assignee task
  341. $this->setLastUser();
  342. $this->updateTaskRequest();
  343. // Set as accepted
  344. mapi_setprops($this->message, array($this->props['taskhistory'] => thAccepted, $this->props['assignedtime'] => time(), $this->props['taskaccepted'] => true, $this->props['delegationstate'] => olTaskNotDelegated));
  345. mapi_savechanges($this->message);
  346. $this->sendResponse(tdmtTaskAcc, $prefix);
  347. //@TODO: delete received task request from Inbox
  348. return $this->deleteReceivedTR();
  349. }
  350. /* Decline a task request and send the response.
  351. *
  352. * Passed message must be a task request message, ie isTaskRequest() must return TRUE.
  353. *
  354. * Sends the decline message back to the organizer. The caller is responsible for removing the message.
  355. *
  356. * @return boolean TRUE on success, FALSE on failure
  357. */
  358. function doDecline($prefix) {
  359. $messageprops = mapi_getprops($this->message, array($this->props['taskstate']));
  360. if(!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN)
  361. return false; // Can only decline assignee task
  362. $this->setLastUser();
  363. $this->updateTaskRequest();
  364. // Set as declined
  365. mapi_setprops($this->message, array($this->props['taskhistory'] => thDeclined, $this->props['delegationstate'] => olTaskDelegationDeclined));
  366. mapi_savechanges($this->message);
  367. $this->sendResponse(tdmtTaskDec, $prefix);
  368. return $this->deleteReceivedTR();
  369. }
  370. /* Send an update of the task if requested, and send the Status-On-Completion report if complete and requested
  371. *
  372. * If no updates were requested from the organizer, this function does nothing.
  373. *
  374. * @return boolean TRUE if the update succeeded, FALSE otherwise.
  375. */
  376. function doUpdate($prefix, $prefixComplete) {
  377. $messageprops = mapi_getprops($this->message, array($this->props['taskstate'], PR_SUBJECT));
  378. if(!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN)
  379. return false; // Can only update assignee task
  380. $this->setLastUser();
  381. $this->updateTaskRequest();
  382. // Set as updated
  383. mapi_setprops($this->message, array($this->props['taskhistory'] => thUpdated));
  384. mapi_savechanges($this->message);
  385. $props = mapi_getprops($this->message, array($this->props['taskupdates'], $this->props['tasksoc'], $this->props['recurring'], $this->props['complete']));
  386. if ($props[$this->props['taskupdates']] && !(isset($props[$this->props['recurring']]) && $props[$this->props['recurring']]))
  387. $this->sendResponse(tdmtTaskUpd, $prefix);
  388. if($props[$this->props['tasksoc']] && $props[$this->props['complete']] ) {
  389. $outgoing = $this->createOutgoingMessage();
  390. mapi_setprops($outgoing, array(PR_SUBJECT => $prefixComplete . $messageprops[PR_SUBJECT]));
  391. $this->setRecipientsForResponse($outgoing, tdmtTaskUpd, true);
  392. $body = $this->getBody();
  393. $stream = mapi_openproperty($outgoing, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
  394. mapi_stream_setsize($stream, strlen($body));
  395. mapi_stream_write($stream, $body);
  396. mapi_stream_commit($stream);
  397. mapi_savechanges($outgoing);
  398. mapi_message_submitmessage($outgoing);
  399. }
  400. }
  401. // Internal functions
  402. /* Get the store associated with the task
  403. *
  404. * Normally this will just open the store that the processed message is in. However, if the message is opened
  405. * by a delegate, this function opens the store that the message was delegated from.
  406. */
  407. function getTaskFolderStore()
  408. {
  409. $ownerentryid = false;
  410. $rcvdprops = mapi_getprops($this->message, array(PR_RCVD_REPRESENTING_ENTRYID));
  411. if (isset($rcvdprops[PR_RCVD_REPRESENTING_ENTRYID]))
  412. $ownerentryid = $rcvdprops;
  413. if (!$ownerentryid)
  414. return $this->store;
  415. $ab = mapi_openaddressbook($session);
  416. if (!$ab)
  417. return false;
  418. $mailuser = mapi_ab_openentry($ab, $ownerentryid);
  419. if (!$mailuser)
  420. return false;
  421. $mailuserprops = mapi_getprops($mailuser, array(PR_EMAIL_ADDRESS));
  422. if (!isset($mailuserprops[PR_EMAIL_ADDRESS]))
  423. return false;
  424. $storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]);
  425. return mapi_openmsgstore($this->session, $storeid);
  426. }
  427. /* Open the default task folder for the current user, or the specified user if passed
  428. *
  429. * @param $ownerentryid (Optional)EntryID of user for which we are opening the task folder
  430. */
  431. function getDefaultTasksFolder()
  432. {
  433. $store = $this->getTaskFolderStore();
  434. $inbox = mapi_msgstore_getreceivefolder($store);
  435. $inboxprops = mapi_getprops($inbox, Array(PR_IPM_TASK_ENTRYID));
  436. if(!isset($inboxprops[PR_IPM_TASK_ENTRYID]))
  437. return false;
  438. return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_TASK_ENTRYID]);
  439. }
  440. function getSentReprProps($store)
  441. {
  442. $storeprops = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
  443. if(!isset($storeprops[PR_MAILBOX_OWNER_ENTRYID])) return false;
  444. $ab = mapi_openaddressbook($this->session);
  445. $mailuser = mapi_ab_openentry($ab, $storeprops[PR_MAILBOX_OWNER_ENTRYID]);
  446. $mailuserprops = mapi_getprops($mailuser, array(PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_DISPLAY_NAME, PR_SEARCH_KEY, PR_ENTRYID));
  447. $props = array();
  448. $props[PR_SENT_REPRESENTING_ADDRTYPE] = $mailuserprops[PR_ADDRTYPE];
  449. $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $mailuserprops[PR_EMAIL_ADDRESS];
  450. $props[PR_SENT_REPRESENTING_NAME] = $mailuserprops[PR_DISPLAY_NAME];
  451. $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $mailuserprops[PR_SEARCH_KEY];
  452. $props[PR_SENT_REPRESENTING_ENTRYID] = $mailuserprops[PR_ENTRYID];
  453. return $props;
  454. }
  455. /*
  456. * Creates an outgoing message based on the passed message - will set delegate information
  457. * and sentmail folder
  458. */
  459. function createOutgoingMessage()
  460. {
  461. // Open our default store for this user (that's the only store we can submit in)
  462. $store = $this->getDefaultStore();
  463. $storeprops = mapi_getprops($store, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID));
  464. $outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
  465. if(!$outbox) return false;
  466. $outgoing = mapi_folder_createmessage($outbox);
  467. if(!$outgoing) return false;
  468. // Set SENT_REPRESENTING in case we're sending as a delegate
  469. $ownerstore = $this->getTaskFolderStore();
  470. $sentreprprops = $this->getSentReprProps($ownerstore);
  471. mapi_setprops($outgoing, $sentreprprops);
  472. mapi_setprops($outgoing, array(PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID]));
  473. return $outgoing;
  474. }
  475. /*
  476. * Send a response message (from assignee back to organizer).
  477. *
  478. * @param $type int Type of response (tdmtTaskAcc, tdmtTaskDec, tdmtTaskUpd);
  479. * @return boolean TRUE on success
  480. */
  481. function sendResponse($type, $prefix)
  482. {
  483. // Create a message in our outbox
  484. $outgoing = $this->createOutgoingMessage();
  485. $messageprops = mapi_getprops($this->message, array(PR_SUBJECT));
  486. $attach = mapi_message_createattach($outgoing);
  487. mapi_setprops($attach, array(PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG, PR_DISPLAY_NAME => $messageprops[PR_SUBJECT], PR_ATTACHMENT_HIDDEN => true));
  488. $sub = mapi_attach_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY);
  489. mapi_copyto($this->message, array(), array(PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY), $outgoing);
  490. mapi_copyto($this->message, array(), array(), $sub);
  491. if (!$this->setRecipientsForResponse($outgoing, $type)) return false;
  492. switch($type) {
  493. case tdmtTaskAcc:
  494. $messageclass = "IPM.TaskRequest.Accept";
  495. break;
  496. case tdmtTaskDec:
  497. $messageclass = "IPM.TaskRequest.Decline";
  498. break;
  499. case tdmtTaskUpd:
  500. $messageclass = "IPM.TaskRequest.Update";
  501. break;
  502. };
  503. mapi_savechanges($sub);
  504. mapi_savechanges($attach);
  505. // Set Body
  506. $body = $this->getBody();
  507. $stream = mapi_openproperty($outgoing, PR_BODY, IID_Stream, 0, MAPI_CREATE | MAPI_MODIFY);
  508. mapi_stream_setsize($stream, strlen($body));
  509. mapi_stream_write($stream, $body);
  510. mapi_stream_commit($stream);
  511. // Set subject, taskmode, message class, icon index, response time
  512. mapi_setprops($outgoing, array(PR_SUBJECT => $prefix . $messageprops[PR_SUBJECT],
  513. $this->props['taskmode'] => $type,
  514. PR_MESSAGE_CLASS => $messageclass,
  515. PR_ICON_INDEX => 0xFFFFFFFF,
  516. $this->props['assignedtime'] => time()));
  517. mapi_savechanges($outgoing);
  518. mapi_message_submitmessage($outgoing);
  519. return true;
  520. }
  521. function getDefaultStore()
  522. {
  523. $table = mapi_getmsgstorestable($this->session);
  524. $rows = mapi_table_queryallrows($table, array(PR_DEFAULT_STORE, PR_ENTRYID));
  525. foreach ($rows as $row)
  526. if($row[PR_DEFAULT_STORE])
  527. return mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
  528. return false;
  529. }
  530. /* Creates a new TaskGlobalObjId
  531. *
  532. * Just 16 bytes of random data
  533. */
  534. function createTGOID()
  535. {
  536. $goid = "";
  537. for ($i = 0; $i < 16; $i++)
  538. $goid .= chr(rand(0, 255));
  539. return $goid;
  540. }
  541. function getEmbeddedTask($message) {
  542. $table = mapi_message_getattachmenttable($message);
  543. $rows = mapi_table_queryallrows($table, array(PR_ATTACH_NUM));
  544. // Assume only one attachment
  545. if(empty($rows))
  546. return false;
  547. $attach = mapi_message_openattach($message, $rows[0][PR_ATTACH_NUM]);
  548. $message = mapi_openproperty($attach, PR_ATTACH_DATA_OBJ, IID_IMessage, 0, 0);
  549. return $message;
  550. }
  551. function setLastUser() {
  552. $delegatestore = $this->getDefaultStore();
  553. $taskstore = $this->getTaskFolderStore();
  554. $delegateprops = mapi_getprops($delegatestore, array(PR_MAILBOX_OWNER_NAME));
  555. $taskprops = mapi_getprops($taskstore, array(PR_MAILBOX_OWNER_NAME));
  556. // The owner of the task
  557. $username = $delegateprops[PR_MAILBOX_OWNER_NAME];
  558. // This is me (the one calling the script)
  559. $delegate = $taskprops[PR_MAILBOX_OWNER_NAME];
  560. mapi_setprops($this->message, array($this->props["tasklastuser"] => $username, $this->props["tasklastdelegate"] => $delegate, $this->props['assignedtime'] => time()));
  561. }
  562. /** Assignee becomes the owner when a user/assignor assigns any task to someone. Also there can be more than one assignee.
  563. * This function sets assignee as owner in the assignor's copy of task.
  564. */
  565. function setOwnerForAssignor()
  566. {
  567. $recipTable = mapi_message_getrecipienttable($this->message);
  568. $recips = mapi_table_queryallrows($recipTable, array(PR_DISPLAY_NAME));
  569. if (!empty($recips)) {
  570. $owner = array();
  571. foreach ($recips as $value)
  572. $owner[] = $value[PR_DISPLAY_NAME];
  573. $props = array($this->props['owner'] => implode("; ", $owner));
  574. mapi_setprops($this->message, $props);
  575. }
  576. }
  577. /** Sets assignor as recipients in assignee's copy of task.
  578. *
  579. * If assignor has requested task updates then the assignor is added as recipient type MAPI_CC.
  580. *
  581. * Also if assignor has request SOC then the assignor is also add as recipient type MAPI_BCC
  582. *
  583. * @param $task message MAPI message which assignee's copy of task
  584. */
  585. function setAssignorInRecipients($task)
  586. {
  587. $recipTable = mapi_message_getrecipienttable($task);
  588. // Delete all MAPI_TO recipients
  589. $recips = mapi_table_queryallrows($recipTable, array(PR_ROWID), array(RES_PROPERTY,
  590. array( RELOP => RELOP_EQ,
  591. ULPROPTAG => PR_RECIPIENT_TYPE,
  592. VALUE => MAPI_TO
  593. )));
  594. foreach($recips as $recip)
  595. mapi_message_modifyrecipients($task, MODRECIP_REMOVE, array($recip));
  596. $recips = array();
  597. $taskReqProps = mapi_getprops($this->message, array(PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE));
  598. $associatedTaskProps = mapi_getprops($task, array($this->props['taskupdates'], $this->props['tasksoc'], $this->props['taskmultrecips']));
  599. // Build assignor info
  600. $assignor = array( PR_ENTRYID => $taskReqProps[PR_SENT_REPRESENTING_ENTRYID],
  601. PR_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
  602. PR_EMAIL_ADDRESS => $taskReqProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS],
  603. PR_RECIPIENT_DISPLAY_NAME => $taskReqProps[PR_SENT_REPRESENTING_NAME],
  604. PR_ADDRTYPE => empty($taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $taskReqProps[PR_SENT_REPRESENTING_ADDRTYPE],
  605. PR_RECIPIENT_FLAGS => recipSendable
  606. );
  607. // Assignor has requested task updates, so set him/her as MAPI_CC in recipienttable.
  608. if ((isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['taskupdates']])
  609. && !(isset($associatedTaskProps[$this->props['taskmultrecips']]) && $associatedTaskProps[$this->props['taskmultrecips']] == tmrReceived)) {
  610. $assignor[PR_RECIPIENT_TYPE] = MAPI_CC;
  611. $recips[] = $assignor;
  612. }
  613. // Assignor wants to receive an email report when task is mark as 'Complete', so in recipients as MAPI_BCC
  614. if (isset($associatedTaskProps[$this->props['taskupdates']]) && $associatedTaskProps[$this->props['tasksoc']]) {
  615. $assignor[PR_RECIPIENT_TYPE] = MAPI_BCC;
  616. $recips[] = $assignor;
  617. }
  618. if (!empty($recips))
  619. mapi_message_modifyrecipients($task, MODRECIP_ADD, $recips);
  620. }
  621. /** Returns user information who has task request
  622. */
  623. function retrieveUserData()
  624. {
  625. // get user entryid
  626. $storeProps = mapi_getprops($this->store, array(PR_USER_ENTRYID));
  627. if (!$storeProps[PR_USER_ENTRYID]) return false;
  628. $ab = mapi_openaddressbook($this->session);
  629. // open the user entry
  630. $user = mapi_ab_openentry($ab, $storeProps[PR_USER_ENTRYID]);
  631. if (!$user) return false;
  632. // receive userdata
  633. $userProps = mapi_getprops($user, array(PR_DISPLAY_NAME));
  634. if (!$userProps[PR_DISPLAY_NAME]) return false;
  635. return $userProps;
  636. }
  637. /** Deletes incoming task request from Inbox
  638. *
  639. * @returns array returns PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the deleted task request
  640. */
  641. function deleteReceivedTR()
  642. {
  643. $store = $this->getTaskFolderStore();
  644. $inbox = mapi_msgstore_getreceivefolder($store);
  645. $storeProps = mapi_getprops($store, array(PR_IPM_WASTEBASKET_ENTRYID));
  646. $props = mapi_getprops($this->message, array($this->props['taskglobalobjid']));
  647. $globalobjid = $props[$this->props['taskglobalobjid']];
  648. // Find the task by looking for the taskglobalobjid
  649. $restriction = array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => $this->props['taskglobalobjid'], VALUE => $globalobjid));
  650. $contents = mapi_folder_getcontentstable($inbox);
  651. $rows = mapi_table_queryallrows($contents, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID), $restriction);
  652. $taskrequest = false;
  653. if(!empty($rows)) {
  654. // If there are multiple, just use the first
  655. $entryid = $rows[0][PR_ENTRYID];
  656. $wastebasket = mapi_msgstore_openentry($store, $storeProps[PR_IPM_WASTEBASKET_ENTRYID]);
  657. mapi_folder_copymessages($inbox, Array($entryid), $wastebasket, MESSAGE_MOVE);
  658. return array(PR_ENTRYID => $entryid, PR_PARENT_ENTRYID => $rows[0][PR_PARENT_ENTRYID], PR_STORE_ENTRYID => $rows[0][PR_STORE_ENTRYID]);
  659. }
  660. return false;
  661. }
  662. /** Converts already sent task request to normal task
  663. */
  664. function createUnassignedCopy()
  665. {
  666. mapi_deleteprops($this->message, array($this->props['taskglobalobjid']));
  667. mapi_setprops($this->message, array($this->props['updatecount'] => 1));
  668. // Remove all recipents
  669. $this->deleteAllRecipients($this->message);
  670. }
  671. /** Sets recipients for the outgoing message according to type of the response.
  672. *
  673. * If it is a task update, then only recipient type MAPI_CC are taken from the task message.
  674. *
  675. * If it is accept/decline response, then PR_SENT_REPRESENTATING_XXXX are taken as recipient.
  676. *
  677. *@param $outgoing MAPI_message outgoing mapi message
  678. *@param $responseType String response type
  679. *@param $sendSOC Boolean true if sending complete response else false.
  680. */
  681. function setRecipientsForResponse($outgoing, $responseType, $sendSOC = false)
  682. {
  683. // Clear recipients from outgoing msg
  684. $this->deleteAllRecipients($outgoing);
  685. // If it is a task update then get MAPI_CC recipients which are assignors who has asked for task update.
  686. if ($responseType == tdmtTaskUpd) {
  687. $recipTable = mapi_message_getrecipienttable($this->message);
  688. $recips = mapi_table_queryallrows($recipTable, $this->recipprops, array(RES_PROPERTY,
  689. array( RELOP => RELOP_EQ,
  690. ULPROPTAG => PR_RECIPIENT_TYPE,
  691. VALUE => ($sendSOC ? MAPI_BCC : MAPI_CC)
  692. )
  693. ));
  694. // No recipients found, return error
  695. if (empty($recips))
  696. return false;
  697. foreach($recips as $recip) {
  698. $recip[PR_RECIPIENT_TYPE] = MAPI_TO; // Change recipient type to MAPI_TO
  699. mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, array($recip));
  700. }
  701. return true;
  702. }
  703. $orgprops = mapi_getprops($this->message, array(PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_ENTRYID, PR_SUBJECT));
  704. $recip = array(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);
  705. mapi_message_modifyrecipients($outgoing, MODRECIP_ADD, array($recip));
  706. return true;
  707. }
  708. /** Adds task details to message body and returns body.
  709. *
  710. *@return string contructed body with task details.
  711. */
  712. function getBody()
  713. {
  714. //@TODO: Fix translations
  715. $msgProps = mapi_getprops($this->message);
  716. $body = "";
  717. if (isset($msgProps[PR_SUBJECT])) $body .= "\n" . dgettext("kopano","Subject") . ":\t". $msgProps[PR_SUBJECT];
  718. if (isset($msgProps[$this->props['startdate']])) $body .= "\n" . dgettext("kopano","Start Date") . ":\t". strftime(dgettext("kopano","%A, %B %d, %Y"),$msgProps[$this->props['startdate']]);
  719. if (isset($msgProps[$this->props['duedate']])) $body .= "\n" . dgettext("kopano","Due Date") . ":\t". strftime(dgettext("kopano","%A, %B %d, %Y"),$msgProps[$this->props['duedate']]);
  720. $body .= "\n";
  721. if (isset($msgProps[$this->props['status']])) {
  722. $body .= "\n" . dgettext("kopano","Status") . ":\t";
  723. if ($msgProps[$this->props['status']] == 0) $body .= dgettext("kopano","Not Started");
  724. else if ($msgProps[$this->props['status']] == 1) $body .= dgettext("kopano","In Progress");
  725. else if ($msgProps[$this->props['status']] == 2) $body .= dgettext("kopano","Complete");
  726. else if ($msgProps[$this->props['status']] == 3) $body .= dgettext("kopano","Wait for other person");
  727. else if ($msgProps[$this->props['status']] == 4) $body .= dgettext("kopano","Deferred");
  728. }
  729. if (isset($msgProps[$this->props['percent_complete']])) {
  730. $body .= "\n" . dgettext("kopano","Percent Complete") . ":\t". ($msgProps[$this->props['percent_complete']] * 100).'%';
  731. if ($msgProps[$this->props['percent_complete']] == 1 && isset($msgProps[$this->props['datecompleted']]))
  732. $body .= "\n" . dgettext("kopano","Date Completed") . ":\t". strftime("%A, %B %d, %Y",$msgProps[$this->props['datecompleted']]);
  733. }
  734. $body .= "\n";
  735. if (isset($msgProps[$this->props['totalwork']])) $body .= "\n" . dgettext("kopano","Total Work") . ":\t". ($msgProps[$this->props['totalwork']]/60) ." " . dgettext("kopano","hours");
  736. if (isset($msgProps[$this->props['actualwork']])) $body .= "\n" . dgettext("kopano","Actual Work") . ":\t". ($msgProps[$this->props['actualwork']]/60) ." " . dgettext("kopano","hours");
  737. $body .="\n";
  738. if (isset($msgProps[$this->props['owner']])) $body .= "\n" . dgettext("kopano","Owner") . ":\t". $msgProps[$this->props['owner']];
  739. $body .="\n";
  740. if (isset($msgProps[$this->props['categories']]) && !empty($msgProps[$this->props['categories']])) $body .= "\nCategories:\t". implode(', ', $msgProps[$this->props['categories']]);
  741. if (isset($msgProps[$this->props['companies']]) && !empty($msgProps[$this->props['companies']])) $body .= "\nCompany:\t". implode(', ', $msgProps[$this->props['companies']]);
  742. if (isset($msgProps[$this->props['billinginformation']])) $body .= "\n" . dgettext("kopano","Billing Information") . ":\t". $msgProps[$this->props['billinginformation']];
  743. if (isset($msgProps[$this->props['mileage']])) $body .= "\n" . dgettext("kopano","Mileage") . ":\t". $msgProps[$this->props['mileage']];
  744. $body .="\n";
  745. $content = mapi_message_openproperty($this->message, PR_BODY);
  746. $body .= "\n". trim($content, "\0");
  747. return $body;
  748. }
  749. /** Reclaims ownership of a decline task
  750. *
  751. * Deletes taskrequest properties and recipients from the task message.
  752. */
  753. function reclaimownership()
  754. {
  755. // Delete task request properties
  756. mapi_deleteprops($this->message, array($this->props['taskglobalobjid'],
  757. $this->props['tasklastuser'],
  758. $this->props['tasklastdelegate']));
  759. mapi_setprops($this->message, array($this->props['updatecount'] => 2,
  760. $this->props['taskfcreator'] => true));
  761. // Delete recipients
  762. $this->deleteAllRecipients($this->message);
  763. }
  764. /** Deletes all recipients from given message object
  765. *
  766. *@param $message MAPI message from which recipients are to be removed.
  767. */
  768. function deleteAllRecipients($message)
  769. {
  770. $recipTable = mapi_message_getrecipienttable($message);
  771. $recipRows = mapi_table_queryallrows($recipTable, array(PR_ROWID));
  772. foreach($recipRows as $recipient)
  773. mapi_message_modifyrecipients($message, MODRECIP_REMOVE, array($recipient));
  774. }
  775. function sendCompleteUpdate($prefix, $action, $prefixComplete)
  776. {
  777. $messageprops = mapi_getprops($this->message, array($this->props['taskstate']));
  778. if(!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN)
  779. return false; // Can only decline assignee task
  780. mapi_setprops($this->message, array($this->props['complete'] => true,
  781. $this->props['datecompleted'] => $action["dateCompleted"],
  782. $this->props['status'] => 2,
  783. $this->props['percent_complete'] => 1));
  784. $this->doUpdate($prefix, $prefixComplete);
  785. }
  786. }
  787. ?>