grommunio /
grommunio-web
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Create Mail ItemModule |
||
| 4 | * Module which openes, creates, saves and deletes an item. It |
||
| 5 | * extends the Module class. |
||
| 6 | */ |
||
| 7 | class CreateMailItemModule extends ItemModule |
||
| 8 | { |
||
| 9 | /** |
||
| 10 | * Constructor |
||
| 11 | * @param int $id unique id. |
||
| 12 | * @param array $data list of all actions. |
||
| 13 | */ |
||
| 14 | function __construct($id, $data) |
||
| 15 | { |
||
| 16 | parent::__construct($id, $data); |
||
| 17 | |||
| 18 | $this->properties = $GLOBALS['properties']->getMailProperties(); |
||
| 19 | } |
||
| 20 | |||
| 21 | /** |
||
| 22 | * Function which saves and/or sends an item. |
||
| 23 | * @param object $store MAPI Message Store Object |
||
| 24 | * @param string $parententryid parent entryid of the message |
||
| 25 | * @param string $entryid entryid of the message |
||
| 26 | * @param array $action the action data, sent by the client |
||
| 27 | * @return boolean true on success or false on failure |
||
| 28 | */ |
||
| 29 | function save($store, $parententryid, $entryid, $action) |
||
| 30 | { |
||
| 31 | $result = false; |
||
| 32 | $send = false; |
||
| 33 | $saveChanges = true; |
||
| 34 | |||
| 35 | if(!$store) { |
||
| 36 | $store = $GLOBALS['mapisession']->getDefaultMessageStore(); |
||
| 37 | } |
||
| 38 | if(!$parententryid) { |
||
| 39 | if(isset($action['props']) && isset($action['props']['message_class'])) { |
||
| 40 | $parententryid = $this->getDefaultFolderEntryID($store, $action['props']['message_class']); |
||
| 41 | } else { |
||
| 42 | $parententryid = $this->getDefaultFolderEntryID($store, ''); |
||
| 43 | } |
||
| 44 | } |
||
| 45 | |||
| 46 | if($store) { |
||
| 47 | // Reference to an array which will be filled with PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the message |
||
| 48 | $messageProps = array(); |
||
| 49 | $attachments = !empty($action['attachments']) ? $action['attachments'] : array(); |
||
| 50 | $recipients = !empty($action['recipients']) ? $action['recipients'] : array(); |
||
| 51 | |||
| 52 | // Set message flags first, because this has to be possible even if the user does not have write permissions |
||
| 53 | if(isset($action['props']) && isset($action['props']['message_flags']) && $entryid) { |
||
| 54 | $msg_action = isset($action['message_action']) ? $action['message_action'] : false; |
||
| 55 | $result = $GLOBALS['operations']->setMessageFlag($store, $entryid, $action['props']['message_flags'], $msg_action, $messageProps); |
||
| 56 | |||
| 57 | unset($action['props']['message_flags']); |
||
| 58 | } |
||
| 59 | |||
| 60 | if(isset($action['message_action']) && isset($action['message_action']['send'])) { |
||
| 61 | $send = $action['message_action']['send']; |
||
| 62 | } |
||
| 63 | |||
| 64 | // if we are sending mail then no need to check if anything is modified or not just send the mail |
||
| 65 | if(!$send) { |
||
| 66 | // If there is any property changed then save |
||
| 67 | $saveChanges = !empty($action['props']); |
||
| 68 | |||
| 69 | // Check if we are dealing with drafts and recipients or attachments information is modified |
||
| 70 | if(!$saveChanges) { |
||
| 71 | // check for changes in attachments |
||
| 72 | if(isset($attachments['dialog_attachments'])) { |
||
| 73 | $attachment_state = new AttachmentState(); |
||
| 74 | $attachment_state->open(); |
||
| 75 | $saveChanges = $attachment_state->isChangesPending($attachments['dialog_attachments']); |
||
| 76 | $attachment_state->close(); |
||
| 77 | } |
||
| 78 | |||
| 79 | // check for changes in recipients info |
||
| 80 | $saveChanges = $saveChanges || !empty($recipients); |
||
| 81 | } |
||
| 82 | } |
||
| 83 | |||
| 84 | // check we should send/save mail |
||
| 85 | if($saveChanges) { |
||
| 86 | $copyAttachments = false; |
||
| 87 | $copyFromStore = false; |
||
| 88 | $copyFromMessage = false; |
||
| 89 | $copyInlineAttachmentsOnly = false; |
||
| 90 | |||
| 91 | if(isset($action['message_action']) && isset($action['message_action']['action_type'])) { |
||
| 92 | $actions = array('reply', 'replyall', 'forward', 'edit_as_new'); |
||
| 93 | if (array_search($action['message_action']['action_type'], $actions) !== false) { |
||
| 94 | /** |
||
| 95 | * we need to copy the original attachments when it is a forwarded message, or an "edit as new" message |
||
| 96 | * OR |
||
| 97 | * we need to copy ONLY original inline(HIDDEN) attachments when it is reply/replyall message |
||
| 98 | */ |
||
| 99 | $copyFromMessage = hex2bin($action['message_action']['source_entryid']); |
||
| 100 | $copyFromStore = hex2bin($action['message_action']['source_store_entryid']); |
||
| 101 | $copyFromAttachNum = !empty($action['message_action']['source_attach_num']) ? $action['message_action']['source_attach_num'] : false; |
||
| 102 | $copyAttachments = true; |
||
| 103 | |||
| 104 | // get resources of store and message |
||
| 105 | $copyFromStore = $GLOBALS['mapisession']->openMessageStore($copyFromStore); |
||
| 106 | $copyFromMessage = $GLOBALS['operations']->openMessage($copyFromStore, $copyFromMessage, $copyFromAttachNum); |
||
| 107 | if ($copyFromStore && $send) { |
||
| 108 | $store = $copyFromStore; |
||
| 109 | } |
||
| 110 | |||
| 111 | // Decode smime signed messages on this message |
||
| 112 | parse_smime($copyFromStore, $copyFromMessage); |
||
| 113 | |||
| 114 | if ($action['message_action']['action_type'] === 'reply' || $action['message_action']['action_type'] === 'replyall') { |
||
| 115 | $copyInlineAttachmentsOnly = true; |
||
| 116 | } |
||
| 117 | } |
||
| 118 | } |
||
| 119 | |||
| 120 | if($send) { |
||
| 121 | // Allowing to hook in just before the data sent away to be sent to the client |
||
| 122 | $succes = true; |
||
| 123 | $GLOBALS['PluginManager']->triggerHook('server.module.createmailitemmodule.beforesend', array( |
||
| 124 | 'moduleObject' => $this, |
||
| 125 | 'store' => $store, |
||
| 126 | 'entryid' => $entryid, |
||
| 127 | 'action' =>$action, |
||
| 128 | 'success' => &$succes, |
||
| 129 | 'properties' => $this->properties, |
||
| 130 | 'messageProps' => $messageProps, |
||
| 131 | 'parententryid' => $parententryid, |
||
| 132 | )); |
||
| 133 | // Break out, hook should use sendFeedback to return a response to the client. |
||
| 134 | if(!$succes) { |
||
| 135 | return; |
||
| 136 | } |
||
| 137 | |||
| 138 | if ( !(isset($action['message_action']['action_type']) && $action['message_action']['action_type']==='edit_as_new') ){ |
||
| 139 | $this->setReplyForwardInfo($action); |
||
| 140 | } |
||
| 141 | |||
| 142 | $savedUnsavedRecipients = array(); |
||
| 143 | |||
| 144 | /** |
||
| 145 | * If message was saved then open that message and retrieve |
||
| 146 | * all recipients from message and prepare array under "saved" key |
||
| 147 | */ |
||
| 148 | if ($entryid) { |
||
| 149 | $message = $GLOBALS['operations']->openMessage($store, $entryid); |
||
| 150 | $savedRecipients = $GLOBALS['operations']->getRecipientsInfo($message); |
||
| 151 | foreach ($savedRecipients as $recipient) { |
||
| 152 | $savedUnsavedRecipients["saved"][] = $recipient['props']; |
||
| 153 | } |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * If message some unsaved recipients then prepare array under the "unsaved" |
||
| 158 | * key. |
||
| 159 | */ |
||
| 160 | if(!empty($recipients) && !empty($recipients["add"])) { |
||
| 161 | foreach ($recipients["add"] as $recipient) { |
||
| 162 | $savedUnsavedRecipients["unsaved"][] = $recipient; |
||
| 163 | } |
||
| 164 | } |
||
| 165 | |||
| 166 | $remove = array(); |
||
| 167 | if (!empty($recipients) && !empty($recipients["remove"])) { |
||
| 168 | $remove = $recipients["remove"]; |
||
| 169 | } |
||
| 170 | |||
| 171 | $members = $GLOBALS['operations']->convertLocalDistlistMembersToRecipients($savedUnsavedRecipients, $remove); |
||
| 172 | |||
| 173 | $action["recipients"]["add"] = $members["add"]; |
||
| 174 | |||
| 175 | if (!empty($remove)) { |
||
| 176 | $action["recipients"]["remove"] = array_merge($action["recipients"]["remove"], $members["remove"]); |
||
| 177 | } else { |
||
| 178 | $action["recipients"]["remove"] = $members["remove"]; |
||
| 179 | } |
||
| 180 | |||
| 181 | $error = $GLOBALS['operations']->submitMessage($store, $entryid, Conversion::mapXML2MAPI($this->properties, $action['props']), $messageProps, isset($action['recipients']) ? $action['recipients'] : array(), isset($action['attachments']) ? $action['attachments'] : array(), $copyFromMessage, $copyAttachments, false, $copyInlineAttachmentsOnly, isset($action['props']['isHTML']) ? !$action['props']['isHTML'] : false ); |
||
| 182 | |||
| 183 | // If draft is sent from the drafts folder, delete notification |
||
| 184 | if(!$error) { |
||
| 185 | $result = true; |
||
| 186 | $GLOBALS['operations']->parseDistListAndAddToRecipientHistory($savedUnsavedRecipients, $remove); |
||
| 187 | |||
| 188 | if(isset($entryid) && !empty($entryid)) { |
||
| 189 | $props = array(); |
||
| 190 | $props[PR_ENTRYID] = $entryid; |
||
| 191 | $props[PR_PARENT_ENTRYID] = $parententryid; |
||
| 192 | |||
| 193 | $storeprops = mapi_getprops($store, array(PR_ENTRYID)); |
||
| 194 | $props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID]; |
||
| 195 | |||
| 196 | $GLOBALS['bus']->addData($this->getResponseData()); |
||
| 197 | $GLOBALS['bus']->notify(bin2hex($parententryid), TABLE_DELETE, $props); |
||
| 198 | } |
||
| 199 | $this->sendFeedback($result ? true : false, array(), false); |
||
| 200 | } else { |
||
| 201 | if($error === 'MAPI_E_NO_ACCESS') { |
||
| 202 | // Handling error: not able to handle this type of object |
||
| 203 | $data = array(); |
||
| 204 | $data["type"] = 1; // MAPI |
||
| 205 | $data["info"] = array(); |
||
| 206 | $data["info"]['title'] = _("Insufficient permissions"); |
||
| 207 | $data["info"]['display_message'] = _("You don't have the permission to complete this action"); |
||
| 208 | $this->addActionData("error", $data); |
||
| 209 | } |
||
| 210 | } |
||
| 211 | } else { |
||
| 212 | $propertiesToDelete = array(); |
||
| 213 | $mapiProps = Conversion::mapXML2MAPI($this->properties, $action['props']); |
||
| 214 | |||
| 215 | /* |
||
| 216 | * PR_SENT_REPRESENTING_ENTRYID and PR_SENT_REPRESENTING_SEARCH_KEY properties needs to be deleted while user removes |
||
| 217 | * any previously configured recipient from FROM field. |
||
| 218 | * This property was simply ignored by Conversion::mapXML2MAPI function |
||
| 219 | * as it is configured with empty string in request. |
||
| 220 | */ |
||
| 221 | if (isset($action['props']['sent_representing_entryid']) && empty($action['props']['sent_representing_entryid'])) { |
||
| 222 | array_push($propertiesToDelete, PR_SENT_REPRESENTING_ENTRYID,PR_SENT_REPRESENTING_SEARCH_KEY); |
||
| 223 | } |
||
| 224 | |||
| 225 | $result = $GLOBALS['operations']->saveMessage($store, $entryid, $parententryid, $mapiProps, $messageProps, isset($action['recipients']) ? $action['recipients'] : array(), isset($action['attachments']) ? $action['attachments'] : array(), $propertiesToDelete, $copyFromMessage, $copyAttachments, false, $copyInlineAttachmentsOnly); |
||
| 226 | |||
| 227 | // Update the client with the (new) entryid and parententryid to allow the draft message to be removed when submitting. |
||
| 228 | // this will also update rowids of attachments which is required when deleting attachments |
||
| 229 | $props = array(); |
||
| 230 | $props = mapi_getprops($result, array(PR_ENTRYID)); |
||
| 231 | $savedMsg = $GLOBALS['operations']->openMessage($store, $props[PR_ENTRYID]); |
||
| 232 | |||
| 233 | $attachNum = !empty($action['attach_num']) ? $action['attach_num'] : false; |
||
| 234 | |||
| 235 | // If embedded message is being saved currently than we need to obtain all the |
||
| 236 | // properties of 'embedded' message instead of simple message and send it in response |
||
| 237 | if($attachNum) { |
||
| 238 | $message = $GLOBALS['operations']->openMessage($store, $props[PR_ENTRYID], $attachNum); |
||
| 239 | |||
| 240 | if(empty($message)) { |
||
| 241 | return; |
||
| 242 | } |
||
| 243 | |||
| 244 | $data['item'] = $GLOBALS['operations']->getEmbeddedMessageProps($store, $message, $this->properties, $savedMsg, $attachNum); |
||
| 245 | } else { |
||
| 246 | $data = $GLOBALS['operations']->getMessageProps($store, $savedMsg, $this->properties); |
||
| 247 | } |
||
| 248 | |||
| 249 | /* |
||
| 250 | * html filter modifies body of the message when opening the message |
||
| 251 | * but we have just saved the message and even if there are changes in body because of html filter |
||
| 252 | * we shouldn't send updated body to client otherwise it will mark it as changed |
||
| 253 | */ |
||
| 254 | unset($data['props']['body']); |
||
| 255 | unset($data['props']['html_body']); |
||
| 256 | unset($data['props']['isHTML']); |
||
| 257 | |||
| 258 | $GLOBALS['PluginManager']->triggerHook('server.module.createmailitemmodule.aftersave', array( |
||
| 259 | 'data' => &$data, |
||
| 260 | 'entryid' => $props[PR_ENTRYID], |
||
| 261 | 'action' =>$action, |
||
| 262 | 'properties' => $this->properties, |
||
| 263 | 'messageProps' => $messageProps, |
||
| 264 | 'parententryid' => $parententryid, |
||
| 265 | )); |
||
| 266 | |||
| 267 | $this->addActionData('update', array('item' => $data)); |
||
| 268 | } |
||
| 269 | } |
||
| 270 | |||
| 271 | // Feedback for successful save (without send) |
||
| 272 | if($result && !$send) { |
||
| 273 | $GLOBALS['bus']->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps); |
||
| 274 | } |
||
| 275 | |||
| 276 | // Feedback for send |
||
| 277 | if($send) { |
||
| 278 | $this->addActionData('update', array('item' => Conversion::mapMAPI2XML($this->properties, $messageProps))); |
||
| 279 | } |
||
| 280 | |||
| 281 | $this->sendFeedback($result ? true : false, array(), true); |
||
| 282 | } |
||
| 283 | } |
||
| 284 | |||
| 285 | |||
| 286 | /** |
||
| 287 | * Function which is used to get the source message information, which contains the information of |
||
| 288 | * reply/forward and entry id of original mail, where we have to set the reply/forward arrow when |
||
| 289 | * draft(saved mail) is send. |
||
| 290 | * @param array $action the action data, sent by the client |
||
| 291 | * @return array|Boolean false when entryid and source message entryid is missing otherwise array with |
||
| 292 | * source store entryid and source message entryid if message has. |
||
| 293 | */ |
||
| 294 | function getSourceMsgInfo($action) |
||
| 295 | { |
||
| 296 | $metaData = array(); |
||
| 297 | if(isset($action["props"]["source_message_info"]) && !empty($action["props"]["source_message_info"])) { |
||
| 298 | if(isset($action["props"]['sent_representing_entryid']) && !empty($action["props"]['sent_representing_entryid'])){ |
||
| 299 | $storeEntryid = hex2bin($action['message_action']['source_store_entryid']); |
||
| 300 | } else { |
||
| 301 | $storeEntryid = hex2bin($action['store_entryid']); |
||
| 302 | } |
||
| 303 | $metaData['source_message_info'] = $action["props"]["source_message_info"]; |
||
| 304 | $metaData['storeEntryid'] = $storeEntryid; |
||
| 305 | return $metaData; |
||
| 306 | } else if(isset($action["entryid"]) && !empty($action["entryid"])) { |
||
| 307 | $storeEntryid = hex2bin($action['store_entryid']); |
||
| 308 | $store = $GLOBALS['mapisession']->openMessageStore($storeEntryid); |
||
| 309 | |||
| 310 | $entryid = hex2bin($action['entryid']); |
||
| 311 | $message = $GLOBALS['operations']->openMessage($store, $entryid); |
||
| 312 | $messageProps = mapi_getprops($message); |
||
| 313 | |||
| 314 | $props = Conversion::mapMAPI2XML($this->properties, $messageProps); |
||
| 315 | |||
| 316 | $sourceMsgInfo = !empty($props['props']['source_message_info']) ? $props['props']['source_message_info'] : false; |
||
| 317 | |||
| 318 | if(isset($props["props"]['sent_representing_entryid']) && !empty($props["props"]['sent_representing_entryid'])){ |
||
| 319 | $storeEntryid = $this->getSourceStoreEntryId($props); |
||
| 320 | } |
||
| 321 | |||
| 322 | $metaData['source_message_info'] = $sourceMsgInfo; |
||
| 323 | $metaData['storeEntryid'] = $storeEntryid; |
||
| 324 | return $metaData; |
||
| 325 | } |
||
| 326 | |||
| 327 | return false; |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Function is used to get the shared or delegate store entryid where |
||
| 332 | * source message was stored on which we have to set replay/forward arrow |
||
| 333 | * when draft(saved mail) is send. |
||
| 334 | * @param Array $props the $props data, which get from saved mail. |
||
| 335 | * @return string source store entryid. |
||
| 336 | */ |
||
| 337 | function getSourceStoreEntryId($props) { |
||
| 338 | $sentRepresentingEntryid = $props['props']['sent_representing_entryid']; |
||
| 339 | $user = mapi_ab_openentry($GLOBALS['mapisession']->getAddressbook(), hex2bin($sentRepresentingEntryid)); |
||
| 340 | $userProps = mapi_getprops($user, array(PR_EMAIL_ADDRESS)); |
||
| 341 | |||
| 342 | return $GLOBALS['mapisession']->getStoreEntryIdOfUser(strtolower($userProps[PR_EMAIL_ADDRESS])); |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Function is used to set the reply/forward arrow on original mail. |
||
| 347 | * @param object $store MAPI Message Store Object |
||
| 348 | * @param array $action the action data, sent by the client |
||
| 349 | */ |
||
| 350 | function setReplyForwardInfo($action) |
||
| 351 | { |
||
| 352 | $message = false; |
||
| 353 | $sourceMsgInfo = $this->getSourceMsgInfo($action); |
||
| 354 | if(isset($sourceMsgInfo['source_message_info']) && $sourceMsgInfo['source_message_info']) { |
||
| 355 | /** |
||
| 356 | * $sourceMsgInfo['source_message_info'] contains the hex value, where first 24byte contains action type |
||
| 357 | * and next 48byte contains entryid of original mail. so we have to extract the action type |
||
| 358 | * from this hex value. |
||
| 359 | * |
||
| 360 | * Example : 01000E000C00000005010000660000000200000030000000 + record entryid |
||
| 361 | * Here 66 represents the REPLY action type. same way 67 and 68 is represent |
||
| 362 | * REPLY ALL and FORWARD respectively. |
||
| 363 | */ |
||
| 364 | $mailActionType = substr($sourceMsgInfo['source_message_info'], 24, 2); |
||
| 365 | // get the entry id of origanal mail's. |
||
| 366 | $originalEntryid = substr($sourceMsgInfo['source_message_info'], 48); |
||
| 367 | $entryid = hex2bin($originalEntryid); |
||
| 368 | |||
| 369 | $store = $GLOBALS['mapisession']->openMessageStore($sourceMsgInfo['storeEntryid']); |
||
| 370 | try { |
||
| 371 | // if original mail of reply/forward mail is deleted from inbox then, |
||
| 372 | // it will throw an exception so to handle it we need to write this block in try catch. |
||
| 373 | $message = $GLOBALS['operations']->openMessage($store, $entryid); |
||
| 374 | }catch(MAPIException $e) { |
||
| 375 | $e->setHandled(); |
||
| 376 | } |
||
| 377 | |||
| 378 | if($message) { |
||
| 379 | $messageProps = mapi_getprops($message); |
||
| 380 | $props = Conversion::mapMAPI2XML($this->properties, $messageProps); |
||
| 381 | switch($mailActionType) { |
||
| 382 | |||
| 383 | case '66': // Reply |
||
| 384 | case '67': // Reply All |
||
| 385 | $props['icon_index'] = 261; |
||
| 386 | break; |
||
| 387 | case '68':// Forward |
||
| 388 | $props['icon_index'] = 262; |
||
| 389 | break; |
||
| 390 | } |
||
| 391 | $props['last_verb_executed'] = hexdec($mailActionType); |
||
| 392 | $props['last_verb_execution_time'] = time(); |
||
| 393 | $mapiProps = Conversion::mapXML2MAPI($this->properties, $props); |
||
| 394 | $messageActionProps = array(); |
||
| 395 | $messageActionResult = $GLOBALS['operations']->saveMessage($store, $mapiProps[PR_ENTRYID], $mapiProps[PR_PARENT_ENTRYID], $mapiProps, $messageActionProps); |
||
| 396 | if($messageActionResult) { |
||
| 397 | if(isset($messageActionProps[PR_PARENT_ENTRYID])) { |
||
| 398 | $GLOBALS['bus']->notify(bin2hex($messageActionProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageActionProps); |
||
| 399 | } |
||
| 400 | } |
||
| 401 | } |
||
| 402 | } |
||
| 403 | } |
||
| 404 | } |
||
| 405 | ?> |
||
|
0 ignored issues
–
show
|
|||
| 406 |
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.
A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.