Test Failed
Push — master ( c4f486...829997 )
by
unknown
35:45 queued 20:22
created

UploadAttachment::processContactData()   B

Complexity

Conditions 9
Paths 50

Size

Total Lines 76
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 9
eloc 55
c 2
b 0
f 0
nc 50
nop 2
dl 0
loc 76
rs 7.4262

How to fix   Long Method   

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
// required to handle php errors
4
require_once __DIR__ . '/exceptions/class.ZarafaErrorException.php';
5
require_once __DIR__ . '/exceptions/class.ZarafaException.php';
6
7
/**
8
 * Upload Attachment
9
 * This file is used to upload.
10
 */
11
class UploadAttachment {
12
	/**
13
	 * A random string that will be generated with every MAPIMessage instance to uniquely identify attachments that
14
	 * belongs to this MAPIMessage, this is mainly used to get recently uploaded attachments for MAPIMessage.
15
	 */
16
	protected $dialogAttachments;
17
18
	/**
19
	 * Entryid of the MAPIStore which holds the message to which we need to import.
20
	 */
21
	protected $storeId;
22
23
	/**
24
	 * Resource of the MAPIStore which holds the message to which we need to import.
25
	 */
26
	protected $store;
27
28
	/**
29
	 * Entryid of the MAPIFolder which holds the message.
30
	 */
31
	protected $destinationFolderId;
32
33
	/**
34
	 * Resource of the MAPIFolder which holds the message which we need to import from file.
35
	 */
36
	protected $destinationFolder;
37
38
	/**
39
	 * A boolean value, set to false by default, to define if the attachment needs to be imported into folder as webapp item.
40
	 */
41
	protected $import;
42
43
	/**
44
	 * Object of AttachmentState class.
45
	 */
46
	protected $attachment_state;
47
48
	/**
49
	 * A boolean value, set to true by default which update the counter of the folder.
50
	 */
51
	protected $allowUpdateCounter;
52
53
	/**
54
	 * A boolean value, set to false by default which extract the attach id concatenated with attachment name
55
	 * from the client side.
56
	 */
57
	protected $ignoreExtractAttachid;
58
59
	/**
60
	 * A string value which stores the module name of the notifier according to the file type.
61
	 */
62
	protected $notifierModule;
63
64
	/**
65
	 * Constructor.
66
	 */
67
	public function __construct() {
68
		$this->dialogAttachments = false;
69
		$this->storeId = false;
70
		$this->destinationFolder = false;
71
		$this->destinationFolderId = false;
72
		$this->store = false;
73
		$this->import = false;
74
		$this->attachment_state = false;
75
		$this->allowUpdateCounter = true;
76
		$this->ignoreExtractAttachid = false;
77
	}
78
79
	/**
80
	 * Function will initialize data for this class object. It will also sanitize data
81
	 * for possible XSS attack because data is received in $_REQUEST.
82
	 *
83
	 * @param array $data parameters received with the request
84
	 */
85
	public function init($data) {
86
		if (isset($data['dialog_attachments'])) {
87
			$this->dialogAttachments = sanitizeValue($data['dialog_attachments'], '', ID_REGEX);
88
		}
89
90
		if (isset($data['store'])) {
91
			$this->storeId = sanitizeValue($data['store'], '', STRING_REGEX);
92
		}
93
94
		if (isset($data['destination_folder'])) {
95
			$this->destinationFolderId = sanitizeValue($data['destination_folder'], '', STRING_REGEX);
96
		}
97
98
		if ($this->storeId) {
99
			$this->store = $GLOBALS['mapisession']->openMessageStore(hex2bin($this->storeId));
0 ignored issues
show
Bug introduced by
$this->storeId of type true is incompatible with the type string expected by parameter $string of hex2bin(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

99
			$this->store = $GLOBALS['mapisession']->openMessageStore(hex2bin(/** @scrutinizer ignore-type */ $this->storeId));
Loading history...
100
		}
101
102
		if (isset($data['import'])) {
103
			$this->import = sanitizeValue($data['import'], '', STRING_REGEX);
104
		}
105
106
		if (isset($data['ignore_extract_attachid'])) {
107
			$this->ignoreExtractAttachid = sanitizeValue($data['ignore_extract_attachid'], '', STRING_REGEX);
108
		}
109
110
		if ($this->attachment_state === false) {
111
			$this->attachment_state = new AttachmentState();
112
		}
113
	}
114
115
	/**
116
	 * Function get Files received in request, extract necessary information
117
	 * and holds the same using an instance of AttachmentState class.
118
	 */
119
	public function processFiles() {
120
		if (isset($_FILES['attachments']['name']) && is_array($_FILES['attachments']['name'])) {
121
			$importStatus = false;
122
			$returnfiles = [];
123
124
			// Parse all information from the updated files,
125
			// validate the contents and add it to the $FILES array.
126
			foreach ($_FILES['attachments']['name'] as $key => $name) {
127
				// validate the FILE object to see if the size doesn't exceed
128
				// the configured MAX_FILE_SIZE
129
				$fileSize = $_FILES['attachments']['size'][$key];
130
				if (isset($_FILES['attachments']['error'][$key]) && $_FILES['attachments']['error'][$key] > 0) {
131
					$errorTitle = _("File is not imported successfully");
132
133
					switch ($_FILES['attachments']['error'][$key]) {
134
						case UPLOAD_ERR_INI_SIZE:
135
							$errorTitle = _('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
136
							break;
137
138
						case UPLOAD_ERR_FORM_SIZE:
139
							$errorTitle = _('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
140
							break;
141
142
						case UPLOAD_ERR_PARTIAL:
143
							$errorTitle = _('The uploaded file was only partially uploaded.');
144
							break;
145
146
						case UPLOAD_ERR_NO_FILE:
147
							$errorTitle = _('No file was uploaded. .');
148
							break;
149
150
						case UPLOAD_ERR_NO_TMP_DIR:
151
							$errorTitle = _('Missing a temporary folder.');
152
							break;
153
154
						case UPLOAD_ERR_CANT_WRITE:
155
							$errorTitle = _('Failed to write file to disk.');
156
							break;
157
158
						case UPLOAD_ERR_EXTENSION:
159
							$errorTitle = _('A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help.');
160
							break;
161
					}
162
163
					throw new ZarafaException($errorTitle);
164
				}
165
				if (isset($fileSize) && !(isset($_POST['MAX_FILE_SIZE']) && $fileSize > $_POST['MAX_FILE_SIZE'])) {
166
					// Parse the filename, strip it from
167
					// any illegal characters.
168
					$filename = mb_basename(stripslashes($_FILES['attachments']['name'][$key]));
169
170
					// set sourcetype as default if sourcetype is unset.
171
					$sourcetype = isset($_POST['sourcetype']) ? $_POST['sourcetype'] : 'default';
172
173
					/**
174
					 * content-type sent by browser for eml and ics attachments will be
175
					 * message/rfc822 and text/calendar respectively, but these content types are
176
					 * used for message-in-message embedded objects, so we have to send it as
177
					 * application/octet-stream.
178
					 */
179
					$fileType = $_FILES['attachments']['type'][$key];
180
					if ($fileType == 'message/rfc822' || $fileType == 'text/calendar') {
181
						$fileType = 'application/octet-stream';
182
					}
183
184
					// Don't go to extract attachID as passing it from client end is not
185
					// possible with IE/Edge.
186
					if (!isEdge() && $this->ignoreExtractAttachid === false) {
187
						$attachID = substr($filename, -8);
188
						$filename = substr($filename, 0, -8);
189
					}
190
					else {
191
						$attachID = uniqid();
192
					}
193
194
					// Move the uploaded file into the attachment state
195
					$attachTempName = $this->attachment_state->addUploadedAttachmentFile($_REQUEST['dialog_attachments'], $filename, $_FILES['attachments']['tmp_name'][$key], [
196
						'name' => $filename,
197
						'size' => $fileSize,
198
						'type' => $fileType,
199
						'sourcetype' => $sourcetype,
200
						'attach_id' => $attachID,
201
					]);
202
203
					// Allow hooking in to handle import in plugins
204
					$GLOBALS['PluginManager']->triggerHook('server.upload_attachment.upload', [
205
						'tmpname' => $this->attachment_state->getAttachmentPath($attachTempName),
206
						'name' => $filename,
207
						'size' => $fileSize,
208
						'sourcetype' => $sourcetype,
209
						'returnfiles' => &$returnfiles,
210
						'attach_id' => $attachID,
211
					]);
212
213
					// import given files
214
					if ($this->import) {
215
						$importStatus = $this->importFiles($attachTempName, $filename, isset($_POST['has_icsvcs_file']) ? $_POST['has_icsvcs_file'] : false);
0 ignored issues
show
Unused Code introduced by
The call to UploadAttachment::importFiles() has too many arguments starting with IssetNode ? $_POST['has_icsvcs_file'] : false. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

215
						/** @scrutinizer ignore-call */ 
216
      $importStatus = $this->importFiles($attachTempName, $filename, isset($_POST['has_icsvcs_file']) ? $_POST['has_icsvcs_file'] : false);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
216
					}
217
					elseif ($sourcetype === 'contactphoto' || $sourcetype === 'default') {
218
						$fileData = [
219
							'props' => [
220
								'attach_num' => -1,
221
								'tmpname' => $attachTempName,
222
								'attach_id' => $attachID,
223
								'name' => $filename,
224
								'size' => $fileSize,
225
							],
226
						];
227
228
						if ($sourcetype === 'contactphoto') {
229
							$fileData['props']['attachment_contactphoto'] = true;
230
						}
231
232
						$returnfiles[] = $fileData;
233
					}
234
					else {
235
						// Backwards compatibility for Plugins (S/MIME)
236
						$lastKey = count($returnfiles) - 1;
237
						if ($lastKey >= 0) {
238
							$returnfiles[$lastKey]['props']['attach_id'] = $attachID;
239
						}
240
					}
241
				}
242
			}
243
244
			if ($this->import) {
245
				if ($importStatus !== false) {
246
					$this->sendImportResponse($importStatus);
247
				}
248
				else {
249
					throw new ZarafaException(_("File is not imported successfully"));
250
				}
251
			}
252
			else {
253
				$return = [
254
					'success' => true,
255
					'zarafa' => [
256
						sanitizeGetValue('module', '', STRING_REGEX) => [
257
							sanitizeGetValue('moduleid', '', STRING_REGEX) => [
258
								'update' => [
259
									'item' => $returnfiles,
260
								],
261
							],
262
						],
263
					],
264
				];
265
266
				echo json_encode($return);
267
			}
268
		}
269
	}
270
271
	/**
272
	 * Function reads content of the given file and call either importICSFile or importEMLFile
273
	 * function based on the file type.
274
	 *
275
	 * @param string $attachTempName a temporary file name of server location where it actually saved/available
276
	 * @param string $filename       an actual file name
277
	 *
278
	 * @return bool true if the import is successful, false otherwise
279
	 */
280
	public function importFiles($attachTempName, $filename) {
281
		$filepath = $this->attachment_state->getAttachmentPath($attachTempName);
282
		$handle = fopen($filepath, "r");
283
		$attachmentStream = '';
284
		while (!feof($handle)) {
285
			$attachmentStream .= fread($handle, BLOCK_SIZE);
286
		}
287
288
		fclose($handle);
289
		unlink($filepath);
290
291
		$extension = pathinfo($filename, PATHINFO_EXTENSION);
292
293
		// Set the module id of the notifier according to the file type
294
		switch (strtoupper($extension)) {
0 ignored issues
show
Bug introduced by
It seems like $extension can also be of type array; however, parameter $string of strtoupper() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

294
		switch (strtoupper(/** @scrutinizer ignore-type */ $extension)) {
Loading history...
295
			case 'EML':
296
				$this->notifierModule = 'maillistnotifier';
297
298
				return $this->importEMLFile($attachmentStream, $filename);
299
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
300
301
			case 'ICS':
302
			case 'VCS':
303
				$this->notifierModule = 'appointmentlistnotifier';
304
305
				return $this->importICSFile($attachmentStream, $filename);
306
				break;
307
308
			case 'VCF':
309
				$this->notifierModule = 'contactlistnotifier';
310
311
				return $this->importVCFFile($attachmentStream, $filename);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->importVCFF...hmentStream, $filename) returns the type array which is incompatible with the documented return type boolean.
Loading history...
312
				break;
313
		}
314
315
		return false;
316
317
	}
318
319
	/**
320
	 * Function reads content of the given file and convert the same into
321
	 * a webapp contact or multiple contacts into respective destination folder.
322
	 *
323
	 * @param string $attachmentStream the attachment as a stream
324
	 * @param string $filename         an actual file name
325
	 *
326
	 * @return array the new contact to be imported
327
	 */
328
	public function importVCFFile($attachmentStream, $filename) {
329
		$this->destinationFolder = $this->getDestinationFolder();
330
331
		try {
332
			processVCFStream($attachmentStream);
333
			// Convert vCard 1.0 to a MAPI contact.
334
			$contacts = $this->convertVCFContactsToMapi($this->destinationFolder, $attachmentStream);
335
		}
336
		catch (ZarafaException $e) {
337
			$e->setTitle(_("Import error"));
338
339
			throw $e;
340
		}
341
		catch (Exception $e) {
342
			$destinationFolderProps = mapi_getprops($this->destinationFolder, [PR_DISPLAY_NAME, PR_MDB_PROVIDER]);
343
			$fullyQualifiedFolderName = $destinationFolderProps[PR_DISPLAY_NAME];
344
			if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
345
				$publicStore = $GLOBALS["mapisession"]->getPublicMessageStore();
346
				$publicStoreName = mapi_getprops($publicStore, [PR_DISPLAY_NAME]);
347
				$fullyQualifiedFolderName .= " - " . $publicStoreName[PR_DISPLAY_NAME];
348
			}
349
			elseif ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) {
350
				$otherStore = $GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId);
351
				$sharedStoreOwnerName = mapi_getprops($otherStore, [PR_MAILBOX_OWNER_NAME]);
352
				$fullyQualifiedFolderName .= " - " . $sharedStoreOwnerName[PR_MAILBOX_OWNER_NAME];
353
			}
354
355
			$message = sprintf(_("Unable to import '%s' to '%s'. "), $filename, $fullyQualifiedFolderName);
356
			if ($e->getCode() === MAPI_E_TABLE_EMPTY) {
357
				$message .= _("There is no contact found in this file.");
358
			}
359
			elseif ($e->getCode() === MAPI_E_CORRUPT_DATA) {
360
				$message .= _("The file is corrupt.");
361
			}
362
			elseif ($e->getCode() === MAPI_E_INVALID_PARAMETER) {
363
				$message .= _("The file is invalid.");
364
			}
365
			else {
366
				$message = sprintf(_("Unable to import '%s'. "), $filename) . $e->getMessage();
367
			}
368
369
			$e = new ZarafaException($message);
370
			$e->setTitle(_("Import error"));
371
372
			throw $e;
373
		}
374
375
		if (is_array($contacts) && !empty($contacts)) {
376
			$newcontact = [];
377
			foreach ($contacts as $contact) {
378
				// As vcf file does not contains fileas, business_address etc properties, we need to set it manually.
379
				// something similar is mentioned in this ticket KC-1509.
380
				$this->processContactData($GLOBALS["mapisession"]->getDefaultMessageStore(), $contact);
381
				mapi_message_savechanges($contact);
382
				$vcf = bin2hex(mapi_getprops($contact, [PR_ENTRYID])[PR_ENTRYID]);
383
				array_push($newcontact, $vcf);
384
			}
385
386
			return $newcontact;
387
		}
388
389
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
390
	}
391
392
	/**
393
	 * Function checks whether the file to be converted contains a single vCard
394
	 * or multiple vCard entries. It calls the appropriate method and converts the vcf.
395
	 *
396
	 * @param object $destinationFolder the folder which holds the message which we need to import from file
397
	 * @param string $attachmentStream  the attachment as a stream
398
	 *
399
	 * @return array $contacts the array of contact(s) to be imported
400
	 */
401
	public function convertVCFContactsToMapi($destinationFolder, $attachmentStream) {
402
		$contacts = [];
403
404
		// If function 'mapi_vcftomapi2' exists, we can use that for both single and multiple vcf files,
405
		// but if it doesn't exist, we use the old function 'mapi_vcftomapi' for single vcf file.
406
		if (function_exists('mapi_vcftomapi2')) {
407
			$contacts = mapi_vcftomapi2($destinationFolder, $attachmentStream);
408
		}
409
		elseif ($_POST['is_single_import']) {
410
			$newMessage = mapi_folder_createmessage($this->destinationFolder);
411
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
412
			$ok = mapi_vcftomapi($GLOBALS['mapisession']->getSession(), $store, $newMessage, $attachmentStream);
413
			if ($ok !== false) {
414
				$contacts = is_array($newMessage) ? $newMessage : [$newMessage];
415
			}
416
		}
417
		else {
418
			// Throw error related to multiple vcf as the function is not available for importing multiple vcf file.
419
			throw new ZarafaException(_("grommunio Web does not support importing multiple VCF with this version."));
420
		}
421
422
		return $contacts;
423
	}
424
425
	/**
426
	 * Helper function which generate the information like 'fileAs','display name' and
427
	 * 'business address' using existing information.
428
	 *
429
	 * @param object $store      Message Store Object
430
	 * @param object $newMessage The newly imported contact from .vcf file.
431
	 */
432
	public function processContactData($store, $newMessage) {
433
		$properties = [];
434
		$properties["subject"] = PR_SUBJECT;
435
		$properties["fileas"] = "PT_STRING8:PSETID_Address:0x8005";
436
		$properties["display_name"] = PR_DISPLAY_NAME;
437
		$properties["address_book_mv"] = "PT_MV_LONG:PSETID_Address:0x8028";
438
		$properties["address_book_long"] = "PT_LONG:PSETID_Address:0x8029";
439
		$properties["business_address"] = "PT_STRING8:PSETID_Address:0x801b";
440
		$properties["email_address_entryid_1"] = "PT_BINARY:PSETID_Address:0x8085";
441
		$properties["email_address_display_name_1"] = "PT_STRING8:PSETID_Address:" . PidLidEmail1DisplayName;
442
		$properties["business_address_street"] = "PT_STRING8:PSETID_Address:" . PidLidWorkAddressStreet;
443
		$properties["business_address_city"] = "PT_STRING8:PSETID_Address:" . PidLidWorkAddressCity;
444
		$properties["business_address_state"] = "PT_STRING8:PSETID_Address:" . PidLidWorkAddressState;
445
		$properties["business_address_postal_code"] = "PT_STRING8:PSETID_Address:" . PidLidWorkAddressPostalCode;
446
		$properties["business_address_country"] = "PT_STRING8:PSETID_Address:" . PidLidWorkAddressCountry;
447
448
		$properties = getPropIdsFromStrings($store, $properties);
449
450
		$contactProps = mapi_getprops($newMessage, $properties);
451
452
		$props = [];
453
454
		// Addresses field value.
455
		$businessAddress = ($contactProps[$properties["business_address_street"]] ?? '' ) . "\n";
456
		$businessAddress .= ($contactProps[$properties["business_address_city"]] ?? '') . " ";
457
		$businessAddress .= ($contactProps[$properties["business_address_state"]] ?? '') . " ";
458
		$businessAddress .= ($contactProps[$properties["business_address_postal_code"]] ?? '') . "\n";
459
		$businessAddress .= ($contactProps[$properties["business_address_country"]] ?? '') . "\n";
460
461
		if (strlen(trim($businessAddress)) > 0) {
462
			$props[$properties["business_address"]] = $businessAddress;
463
		}
464
465
		// File as field value generator.
466
		if (isset($contactProps[PR_DISPLAY_NAME])) {
467
			$displayName = $contactProps[PR_DISPLAY_NAME] ?? " ";
468
			$displayName = str_replace("\xA0", " ", $displayName);
469
			$str = explode(" ", $displayName);
470
			$prefix = [_('Dr.'), _('Miss'), _('Mr.'), _('Mrs.'), _('Ms.'), _('Prof.')];
471
			$suffix = ['I', 'II', 'III', _('Jr.'), _('Sr.')];
472
473
			foreach ($str as $index => $value) {
474
				$value = preg_replace('/[^.A-Za-z0-9\-]/', '', $value);
475
				if (array_search($value, $prefix, true) !== false) {
476
					$props[PR_DISPLAY_NAME_PREFIX] = $value;
477
					unset($str[$index]);
478
				}
479
				elseif (array_search($value, $suffix, true) !== false) {
480
					$props[PR_GENERATION] = $value;
481
					unset($str[$index]);
482
				}
483
			}
484
485
			$surname = array_slice($str, count($str) - 1);
486
			$remainder = array_slice($str, 0, count($str) - 1);
487
			$fileAs = $surname[0] . ', ';
488
			if (!empty($remainder)) {
489
				$fileAs .= join(" ", $remainder);
490
				if (count($remainder) > 1) {
491
					$middleName = $remainder[count($remainder) - 1];
492
					$props[PR_MIDDLE_NAME] = $middleName;
493
				}
494
			}
495
496
			// Email fieldset information.
497
			if (isset($contactProps[$properties["email_address_display_name_1"]])) {
498
				$emailAddressDisplayNameOne = $fileAs . " ";
499
				$emailAddressDisplayNameOne .= $contactProps[$properties["email_address_display_name_1"]];
500
				$props[$properties["email_address_display_name_1"]] = $emailAddressDisplayNameOne;
501
				$props[$properties["address_book_long"]] = 1;
502
				$props[$properties["address_book_mv"]] = [0 => 0];
503
			}
504
505
			$props[$properties["fileas"]] = $fileAs;
506
			$props[PR_DISPLAY_NAME] = $displayName;
507
			mapi_setprops($newMessage, $props);
508
		}
509
	}
510
511
	/**
512
	 * Function reads content of the given file and convert the same into
513
	 * a webapp appointment into respective destination folder.
514
	 *
515
	 * @param string $attachmentStream the attachment as a stream
516
	 * @param string $filename         an actual file name
517
	 *
518
	 * @return bool true if the import is successful, false otherwise
519
	 */
520
	public function importICSFile($attachmentStream, $filename) {
521
		$this->destinationFolder = $this->getDestinationFolder();
522
523
		try {
524
			$events = $this->convertICSToMapi($attachmentStream);
525
		}
526
		catch (ZarafaException $e) {
527
			$e->setTitle(_("Import error"));
528
529
			throw $e;
530
		}
531
		catch (Exception $e) {
532
			$destinationFolderProps = mapi_getprops($this->destinationFolder, [PR_DISPLAY_NAME, PR_MDB_PROVIDER]);
533
			$fullyQualifiedFolderName = $destinationFolderProps[PR_DISPLAY_NAME];
534
			// Condition true if folder is belongs to Public store.
535
			if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
536
				$publicStore = $GLOBALS["mapisession"]->getPublicMessageStore();
537
				$publicStoreName = mapi_getprops($publicStore, [PR_DISPLAY_NAME]);
538
				$fullyQualifiedFolderName .= " - " . $publicStoreName[PR_DISPLAY_NAME];
539
			}
540
			elseif ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) {
541
				// Condition true if folder is belongs to delegate store.
542
				$otherStore = $GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId);
543
				$sharedStoreOwnerName = mapi_getprops($otherStore, [PR_MAILBOX_OWNER_NAME]);
544
				$fullyQualifiedFolderName .= " - " . $sharedStoreOwnerName[PR_MAILBOX_OWNER_NAME];
545
			}
546
547
			$message = sprintf(_("Unable to import '%s' to '%s'. "), $filename, $fullyQualifiedFolderName);
548
			if ($e->getCode() === MAPI_E_TABLE_EMPTY) {
549
				$message .= _("There is no appointment found in this file.");
550
			}
551
			elseif ($e->getCode() === MAPI_E_CORRUPT_DATA) {
552
				$message .= _("The file is corrupt.");
553
			}
554
			elseif ($e->getCode() === MAPI_E_INVALID_PARAMETER) {
555
				$message .= _("The file is invalid.");
556
			}
557
			else {
558
				$message = sprintf(_("Unable to import '%s'. "), $filename) . $e->getMessage();
559
			}
560
561
			$e = new ZarafaException($message);
562
			$e->setTitle(_("Import error"));
563
564
			throw $e;
565
		}
566
567
		if (is_array($events) && !empty($events)) {
568
			$newEvents = [];
569
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
570
			$propTags = array(
571
				"commonstart" => "PT_SYSTIME:PSETID_Common:0x8516",
572
				"commonend" => "PT_SYSTIME:PSETID_Common:0x8517",
573
				"message_class" => PR_MESSAGE_CLASS,
574
				"startdate" => "PT_SYSTIME:PSETID_Appointment:0x820d",
575
				"duedate" => "PT_SYSTIME:PSETID_Appointment:0x820e"
576
			);
577
			$properties = getPropIdsFromStrings($store, $propTags);
578
			foreach ($events as $event) {
579
				$newMessageProps = mapi_getprops($event, $properties);
580
				// Need to set the common start and end date if it is not set because
581
				// common start and end dates are required fields.
582
583
				if (!isset($newMessageProps[$properties["commonstart"]], $newMessageProps[$properties["commonend"]])) {
584
					mapi_setprops($event, array(
585
						$properties["commonstart"] => $newMessageProps[$properties["startdate"]],
586
						$properties["commonend"] => $newMessageProps[$properties["duedate"]]
587
					));
588
				}
589
590
				// Save newly imported event
591
				mapi_message_savechanges($event);
592
593
				$newMessageProps = mapi_getprops($event, [PR_MESSAGE_CLASS]);
594
				if (isset($newMessageProps[PR_MESSAGE_CLASS]) && $newMessageProps[PR_MESSAGE_CLASS] !== 'IPM.Appointment') {
595
					// Convert the Meeting request record to proper appointment record so we can
596
					// properly show the appointment in calendar.
597
					$req = new Meetingrequest($store, $event, $GLOBALS['mapisession']->getSession(), ENABLE_DIRECT_BOOKING);
0 ignored issues
show
Bug introduced by
The type Meetingrequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
598
					$req->doAccept(true, false, false, false, false, false, false, false, false, true);
599
				}
600
601
				$this->allowUpdateCounter = false;
602
				$entryid = bin2hex(mapi_getprops($event, [PR_ENTRYID])[PR_ENTRYID]);
603
				array_push($newEvents, $entryid);
604
			}
605
606
			return $newEvents;
607
		}
608
609
		return false;
610
	}
611
612
	/**
613
	 * Function checks whether the file to be converted contains a single event ics
614
	 * or multiple ics event entries.
615
	 *
616
	 * @param string $attachmentStream the attachment as a stream
617
	 *
618
	 * @return array $events the array of calendar items to be imported
619
	 */
620
	public function convertICSToMapi($attachmentStream) {
621
		$events = [];
622
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
623
624
		// If function 'mapi_icaltomapi2' exists, we can use that for both single and multiple ics files,
625
		// but if it doesn't exist, we use the old function 'mapi_icaltomapi' for single ics file.
626
		if (function_exists('mapi_icaltomapi2')) {
627
			$events = mapi_icaltomapi2($addrBook, $this->destinationFolder, $attachmentStream);
628
		}
629
		elseif ($_POST['is_single_import']) {
630
			$newMessage = mapi_folder_createmessage($this->destinationFolder);
631
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
632
			$ok = mapi_icaltomapi($GLOBALS['mapisession']->getSession(), $store, $addrBook, $newMessage, $attachmentStream, false);
633
634
			if ($ok !== false) {
635
				array_push($events, $newMessage);
636
			}
637
		}
638
		else {
639
			// Throw error related to multiple ics as the function is not available for importing multiple ics file.
640
			throw new ZarafaException(_("grommunio Web does not support importing multiple ICS with this version."));
641
		}
642
643
		return $events;
644
	}
645
646
	/**
647
	 * Function reads content of the given file and convert the same into
648
	 * a webapp email into respective destination folder.
649
	 *
650
	 * @param string $attachTempName   a temporary file name of server location where it actually saved/available
651
	 * @param string $filename         an actual file name
652
	 * @param mixed  $attachmentStream
653
	 *
654
	 * @return bool true if the import is successful, false otherwise
655
	 */
656
	public function importEMLFile($attachmentStream, $filename) {
657
		if (isBrokenEml($attachmentStream)) {
658
			throw new ZarafaException(sprintf(_("Unable to import '%s'. "), $filename) . _("The EML is not valid"));
659
		}
660
661
		$this->destinationFolder = $this->getDestinationFolder();
662
663
		$newMessage = mapi_folder_createmessage($this->destinationFolder);
664
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
665
		// Convert an RFC822-formatted e-mail to a MAPI Message
666
		$ok = mapi_inetmapi_imtomapi($GLOBALS['mapisession']->getSession(), $this->store, $addrBook, $newMessage, $attachmentStream, []);
667
668
		if ($ok === true) {
669
			mapi_message_savechanges($newMessage);
670
671
			return bin2hex(mapi_getprops($newMessage, [PR_ENTRYID])[PR_ENTRYID]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return bin2hex(mapi_getp..._ENTRYID))[PR_ENTRYID]) returns the type string which is incompatible with the documented return type boolean.
Loading history...
672
		}
673
674
		return false;
675
	}
676
677
	/**
678
	 * Function used get the destination folder in which
679
	 * item gets imported.
680
	 *
681
	 * @return object folder object in which item gets imported
682
	 */
683
	public function getDestinationFolder() {
684
		$destinationFolder = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $destinationFolder is dead and can be removed.
Loading history...
685
686
		try {
687
			$destinationFolder = mapi_msgstore_openentry($this->store, hex2bin($this->destinationFolderId));
0 ignored issues
show
Bug introduced by
$this->destinationFolderId of type boolean is incompatible with the type string expected by parameter $string of hex2bin(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

687
			$destinationFolder = mapi_msgstore_openentry($this->store, hex2bin(/** @scrutinizer ignore-type */ $this->destinationFolderId));
Loading history...
688
		}
689
		catch (Exception $e) {
690
			// Try to find the folder from shared stores in case if it is not found in current user's store
691
			$destinationFolder = mapi_msgstore_openentry($GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId), hex2bin($this->destinationFolderId));
692
		}
693
694
		return $destinationFolder;
695
	}
696
697
	/**
698
	 * Function deletes uploaded attachment files and send
699
	 * proper response back to client.
700
	 */
701
	public function deleteUploadedFiles() {
702
		$num = sanitizePostValue('attach_num', false, NUMERIC_REGEX);
703
		$attachID = sanitizePostValue('attach_id', false, STRING_REGEX);
704
705
		if ($num === false) {
706
			// string is passed in attachNum so get it
707
			// Parse the filename, strip it from any illegal characters
708
			$num = mb_basename(stripslashes(sanitizePostValue('attach_num', '', FILENAME_REGEX)));
709
710
			// Delete the file instance and unregister the file
711
			$this->attachment_state->deleteUploadedAttachmentFile($_REQUEST['dialog_attachments'], $num, $attachID);
712
		}
713
		else {
714
			// Set the correct array structure
715
			$this->attachment_state->addDeletedAttachment($_REQUEST['dialog_attachments'], $num);
716
		}
717
718
		$return = [
719
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
720
			'success' => true,
721
			'zarafa' => [
722
				sanitizeGetValue('module', '', STRING_REGEX) => [
723
					sanitizeGetValue('moduleid', '', STRING_REGEX) => [
724
						'delete' => [
725
							'success' => true,
726
						],
727
					],
728
				],
729
			],
730
		];
731
732
		echo json_encode($return);
733
	}
734
735
	/**
736
	 * Function adds embedded/icsfile attachment and send
737
	 * proper response back to client.
738
	 */
739
	public function addingEmbeddedAttachments() {
740
		$attachID = $_POST['attach_id'];
741
		$attachTampName = $this->attachment_state->addEmbeddedAttachment($_REQUEST['dialog_attachments'], [
742
			'entryid' => sanitizePostValue('entryid', '', ID_REGEX),
743
			'store_entryid' => sanitizePostValue('store_entryid', '', ID_REGEX),
744
			'sourcetype' => intval($_POST['attach_method'], 10) === ATTACH_EMBEDDED_MSG ? 'embedded' : 'icsfile',
745
			'attach_id' => $attachID,
746
		]);
747
748
		$returnfiles[] = [
0 ignored issues
show
Comprehensibility Best Practice introduced by
$returnfiles was never initialized. Although not strictly required by PHP, it is generally a good practice to add $returnfiles = array(); before regardless.
Loading history...
749
			'props' => [
750
				'attach_num' => -1,
751
				'tmpname' => $attachTampName,
752
				'attach_id' => $attachID,
753
				// we are not using this data here, so we can safely use $_POST directly without any sanitization
754
				// this is only needed to identify response for a particular attachment record on client side
755
				'name' => $_POST['name'],
756
			],
757
		];
758
759
		$return = [
760
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
761
			'success' => true,
762
			'zarafa' => [
763
				sanitizeGetValue('module', '', STRING_REGEX) => [
764
					sanitizeGetValue('moduleid', '', STRING_REGEX) => [
765
						'update' => [
766
							'item' => $returnfiles,
767
						],
768
					],
769
				],
770
			],
771
		];
772
773
		echo json_encode($return);
774
	}
775
776
	/**
777
	 * Function adds attachment in case of OOo.
778
	 */
779
	public function uploadWhenWendViaOOO() {
780
		$providedFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $_GET['attachment_id'];
781
782
		// check whether the doc is already moved
783
		if (file_exists($providedFile)) {
784
			$filename = mb_basename(stripslashes($_GET['name']));
785
786
			// Move the uploaded file to the session
787
			$this->attachment_state->addProvidedAttachmentFile($_REQUEST['attachment_id'], $filename, $providedFile, [
788
				'name' => $filename,
789
				'size' => filesize($providedFile),
790
				'type' => mime_content_type($providedFile),
791
				'sourcetype' => 'default',
792
			]);
793
		}
794
		else {
795
			// Check if no files are uploaded with this attachmentid
796
			$this->attachment_state->clearAttachmentFiles($_GET['attachment_id']);
797
		}
798
	}
799
800
	/**
801
	 * Helper function to send proper response for import request only. It sets the appropriate
802
	 * notifier to be sent in the response according to the type of the file to be imported.
803
	 *
804
	 * @param mixed $importStatus
805
	 */
806
	public function sendImportResponse($importStatus) {
807
		$storeProps = mapi_getprops($this->store, [PR_ENTRYID]);
808
		$destinationFolderProps = mapi_getprops($this->destinationFolder, [PR_PARENT_ENTRYID, PR_CONTENT_UNREAD]);
809
		$notifierModuleId = $this->notifierModule . '1';
810
811
		$return = [
812
			'success' => true,
813
			'zarafa' => [
814
				sanitizeGetValue('module', '', STRING_REGEX) => [
815
					sanitizeGetValue('moduleid', '', STRING_REGEX) => [
816
						'import' => [
817
							'success' => true,
818
							'items' => $importStatus,
819
						],
820
					],
821
				],
822
				'hierarchynotifier' => [
823
					'hierarchynotifier1' => [
824
						'folders' => [
825
							'item' => [
826
								0 => [
827
									'entryid' => $this->destinationFolderId,
828
									'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]),
829
									'store_entryid' => bin2hex($storeProps[PR_ENTRYID]),
830
									'props' => [
831
										'content_unread' => $this->allowUpdateCounter ? $destinationFolderProps[PR_CONTENT_UNREAD] + 1 : 0,
832
									],
833
								],
834
							],
835
						],
836
					],
837
				],
838
				$this->notifierModule => [
839
					$notifierModuleId => [
840
						'newobject' => [
841
							'item' => [
842
								0 => [
843
									'content_count' => 2,
844
									'content_unread' => 0,
845
									'display_name' => 'subsubin',
846
									'entryid' => $this->destinationFolderId,
847
									'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]),
848
									'store_entryid' => bin2hex($storeProps[PR_ENTRYID]),
849
								],
850
							],
851
						],
852
					],
853
				],
854
			],
855
		];
856
		echo json_encode($return);
857
	}
858
859
	/**
860
	 * Function will encode all the necessary information about the exception
861
	 * into JSON format and send the response back to client.
862
	 *
863
	 * @param object $exception exception object
864
	 * @param string $title     title which used to show as title of exception dialog
865
	 */
866
	public function handleUploadException($exception, $title = null) {
867
		$return = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $return is dead and can be removed.
Loading history...
868
869
		// MAPI_E_NOT_FOUND exception contains generalize exception message.
870
		// Set proper exception message as display message should be user understandable.
871
		if ($exception->getCode() == MAPI_E_NOT_FOUND) {
872
			$exception->setDisplayMessage(_('Could not find message, either it has been moved or deleted.'));
873
		}
874
875
		// Set the headers
876
		header('Expires: 0'); // set expiration time
877
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
878
879
		// Set Content Disposition header
880
		header('Content-Disposition: inline');
881
		// Set content type header
882
		header('Content-Type: text/plain');
883
884
		$return = [
885
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
886
			'success' => false,
887
			'zarafa' => [
888
				'error' => [
889
					'type' => ERROR_GENERAL,
890
					'info' => [
891
						'file' => $exception->getFileLine(),
892
						'title' => $title,
893
						'display_message' => $exception->getDisplayMessage(),
894
						'original_message' => $exception->getMessage(),
895
					],
896
				],
897
			],
898
		];
899
900
		echo json_encode($return);
901
	}
902
903
	/**
904
	 * Generic function to check received data and take necessary action.
905
	 */
906
	public function upload() {
907
		$this->attachment_state->open();
908
909
		// Check if dialog_attachments is set
910
		if (isset($_REQUEST['dialog_attachments'])) {
911
			// Check if attachments have been uploaded
912
			if (isset($_FILES['attachments']) && is_array($_FILES['attachments'])) {
913
				// import given files into respective webapp folder
914
				$this->processFiles();
915
			}
916
			elseif (isset($_POST['deleteattachment'])) {
917
				$this->deleteUploadedFiles();
918
			}
919
			elseif (isset($_POST['entryid'])) {	// Check for adding of embedded attachments
920
				$this->addingEmbeddedAttachments();
921
			}
922
		}
923
		elseif ($_GET && isset($_GET['attachment_id'])) { // this is to upload the file to server when the doc is send via OOo
924
			$this->uploadWhenWendViaOOO();
925
		}
926
927
		$this->attachment_state->close();
928
	}
929
}
930
931
// create instance of class
932
$uploadInstance = new UploadAttachment();
933
934
try {
935
	// initialize variables
936
	$uploadInstance->init($_REQUEST);
937
938
	// upload files
939
	$uploadInstance->upload();
940
}
941
catch (ZarafaException $e) {
942
	$uploadInstance->handleUploadException($e, $e->getTitle());
943
}
944
catch (Exception $e) {
945
	$uploadInstance->handleUploadException($e);
946
}
947