Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

includes/modules/class.createmailitemmodule.php (1 issue)

Severity
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
It is not recommended to use PHP's closing tag ?> in files other than templates.

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.

Loading history...
406