CreateMailItemModule::save()   F
last analyzed

Complexity

Conditions 58
Paths > 20000

Size

Total Lines 284
Code Lines 155

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 58
eloc 155
c 2
b 2
f 0
nc 18162438
nop 4
dl 0
loc 284
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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