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

UploadAttachment::importVCFFile()   B

Complexity

Conditions 11
Paths 29

Size

Total Lines 52
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 39
nc 29
nop 2
dl 0
loc 52
rs 7.3166
c 0
b 0
f 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
// required to handle php errors
3
require_once(__DIR__ . '/exceptions/class.ZarafaErrorException.php');
4
require_once(__DIR__ . '/exceptions/class.ZarafaException.php');
5
6
/**
7
* Upload Attachment
8
* This file is used to upload.
9
*/
10
class UploadAttachment
11
{
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
	/**
66
	 * Constructor
67
	 */
68
	public function __construct()
69
	{
70
		$this->dialogAttachments = false;
71
		$this->storeId = false;
72
		$this->destinationFolder = false;
73
		$this->destinationFolderId = false;
74
		$this->store = false;
75
		$this->import = false;
76
		$this->attachment_state = false;
77
		$this->allowUpdateCounter = true;
78
		$this->ignoreExtractAttachid = false;
79
	}
80
81
	/**
82
	 * Function will initialize data for this class object. It will also sanitize data
83
	 * for possible XSS attack because data is received in $_REQUEST
84
	 * @param Array $data parameters received with the request.
85
	 */
86
	public function init($data)
87
	{
88
		if(isset($data['dialog_attachments'])) {
89
			$this->dialogAttachments = sanitizeValue($data['dialog_attachments'], '', ID_REGEX);
90
		}
91
92
		if(isset($data['store'])) {
93
			$this->storeId = sanitizeValue($data['store'], '', STRING_REGEX);
94
		}
95
96
		if(isset($data['destination_folder'])) {
97
			$this->destinationFolderId = sanitizeValue($data['destination_folder'], '', STRING_REGEX);
98
		}
99
100
		if($this->storeId){
101
			$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

101
			$this->store = $GLOBALS['mapisession']->openMessageStore(hex2bin(/** @scrutinizer ignore-type */ $this->storeId));
Loading history...
102
		}
103
104
		if(isset($data['import'])) {
105
			$this->import = sanitizeValue($data['import'], '', STRING_REGEX);
106
		}
107
108
		if(isset($data['ignore_extract_attachid'])) {
109
			$this->ignoreExtractAttachid = sanitizeValue($data['ignore_extract_attachid'], '', STRING_REGEX);
110
		}
111
112
		if ($this->attachment_state === false) {
113
			$this->attachment_state = new AttachmentState();
114
		}
115
	}
116
117
	/**
118
	 * Function get Files received in request, extract necessary information
119
	 * and holds the same using an instance of AttachmentState class.
120
	 */
121
	function processFiles()
122
	{
123
		if(isset($_FILES['attachments']['name']) && is_array($_FILES['attachments']['name'])) {
124
			$importStatus = false;
125
			$returnfiles = array();
126
127
			// Parse all information from the updated files,
128
			// validate the contents and add it to the $FILES array.
129
			foreach($_FILES['attachments']['name'] as $key => $name) {
130
				// validate the FILE object to see if the size doesn't exceed
131
				// the configured MAX_FILE_SIZE
132
				$fileSize = $_FILES['attachments']['size'][$key];
133
				if (isset($fileSize) && !(isset($_POST['MAX_FILE_SIZE']) && $fileSize > $_POST['MAX_FILE_SIZE'])) {
134
					// Parse the filename, strip it from
135
					// any illegal characters.
136
					$filename = mb_basename(stripslashes($_FILES['attachments']['name'][$key]));
137
138
					// set sourcetype as default if sourcetype is unset.
139
					$sourcetype = isset($_POST['sourcetype']) ? $_POST['sourcetype'] : 'default';
140
141
					/**
142
					 * content-type sent by browser for eml and ics attachments will be
143
					 * message/rfc822 and text/calendar respectively, but these content types are
144
					 * used for message-in-message embedded objects, so we have to send it as
145
					 * application/octet-stream.
146
					 */
147
					$fileType = $_FILES['attachments']['type'][$key];
148
					if ($fileType == 'message/rfc822' || $fileType == 'text/calendar') {
149
						$fileType = 'application/octet-stream';
150
					}
151
152
					// Don't go to extract attachID as passing it from client end is not
153
					// possible with IE/Edge.
154
					if(!isIE11() && !isEdge() && $this->ignoreExtractAttachid === false) {
155
						$attachID = substr($filename, -8);
156
						$filename = substr($filename, 0, -8);
157
					} else {
158
						$attachID = uniqid();
159
					}
160
161
					// Move the uploaded file into the attachment state
162
					$attachTempName = $this->attachment_state->addUploadedAttachmentFile($_REQUEST['dialog_attachments'], $filename, $_FILES['attachments']['tmp_name'][$key], array(
163
						'name'       => $filename,
164
						'size'       => $fileSize,
165
						'type'       => $fileType,
166
						'sourcetype' => $sourcetype,
167
						'attach_id'  => $attachID
168
					));
169
170
					// Allow hooking in to handle import in plugins
171
					$GLOBALS['PluginManager']->triggerHook('server.upload_attachment.upload', array(
172
						'tmpname'  => $this->attachment_state->getAttachmentPath($attachTempName),
173
						'name' => $filename,
174
						'size' => $fileSize,
175
						'sourcetype' => $sourcetype,
176
						'returnfiles' =>& $returnfiles,
177
						'attach_id'   => $attachID
178
					));
179
180
					// import given files
181
					if ($this->import) {
182
						$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

182
						/** @scrutinizer ignore-call */ 
183
      $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...
183
					} else if($sourcetype === 'contactphoto' || $sourcetype === 'default') {
184
						$fileData = Array(
185
							'props' => Array(
186
								'attach_num' => -1,
187
								'tmpname' => $attachTempName,
188
								'attach_id' => $attachID,
189
								'name' => $filename,
190
								'size' => $fileSize
191
							)
192
						);
193
194
						if ($sourcetype === 'contactphoto') {
195
							$fileData['props']['attachment_contactphoto'] = true;
196
						}
197
198
						$returnfiles[] = $fileData;
199
					} else {
200
						// Backwards compatibility for Plugins (S/MIME)
201
						$lastKey = count($returnfiles) - 1;
202
						if ($lastKey >= 0) {
203
							$returnfiles[$lastKey]['props']['attach_id'] = $attachID;
204
						}
205
					}
206
				}
207
			}
208
209
			if ($this->import) {
210
				if ($importStatus !== false) {
211
					$this->sendImportResponse($importStatus);
212
				} else {
213
					throw new ZarafaException(_("File is not imported successfully"));
214
				}
215
			} else {
216
				$return = Array(
217
					'success' => true,
218
					'zarafa' => Array(
219
						sanitizeGetValue('module', '', STRING_REGEX) => Array(
220
							sanitizeGetValue('moduleid', '', STRING_REGEX) => Array(
221
								'update' => Array(
222
									'item'=> $returnfiles
223
								)
224
							)
225
						)
226
					)
227
				);
228
229
				echo json_encode($return);
230
			}
231
		}
232
	}
233
234
	/**
235
	 * Function reads content of the given file and call either importICSFile or importEMLFile
236
	 * function based on the file type.
237
	 *
238
	 * @param String $attachTempName A temporary file name of server location where it actually saved/available.
239
	 * @param String $filename An actual file name.
240
	 * @return Boolean true if the import is successful, false otherwise.
241
	 */
242
	function importFiles($attachTempName, $filename)
243
	{
244
		$filepath = $this->attachment_state->getAttachmentPath($attachTempName);
245
		$handle = fopen($filepath, "r");
246
		$attachmentStream = '';
247
		while (!feof($handle))
248
		{
249
			$attachmentStream .= fread($handle, BLOCK_SIZE);
0 ignored issues
show
Bug introduced by
The constant BLOCK_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
250
		}
251
252
		fclose($handle);
253
		unlink($filepath);
254
255
		$extension = pathinfo($filename, PATHINFO_EXTENSION);
256
257
		// Set the module id of the notifier according to the file type
258
		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

258
		switch (strtoupper(/** @scrutinizer ignore-type */ $extension)) {
Loading history...
259
			case 'EML':
260
				$this->notifierModule = 'maillistnotifier';
261
				return $this->importEMLFile($attachmentStream, $filename);
262
				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...
263
			case 'ICS':
264
			case 'VCS':
265
				$this->notifierModule = 'appointmentlistnotifier';
266
				return $this->importICSFile($attachmentStream, $filename);
267
				break;
268
			case 'VCF':
269
				$this->notifierModule = 'contactlistnotifier';
270
				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...
271
				break;
272
		}
273
	}
274
275
	/**
276
	 * Function reads content of the given file and convert the same into
277
	 * a webapp contact or multiple contacts into respective destination folder.
278
	 *
279
	 * @param String $attachmentStream The attachment as a stream.
280
	 * @param String $filename An actual file name.
281
	 * @return Array The new contact to be imported.
282
	 */
283
	function importVCFFile($attachmentStream, $filename)
284
	{
285
		$this->destinationFolder = $this->getDestinationFolder();
286
		try {
287
			processVCFStream($attachmentStream);
288
			// Convert vCard 1.0 to a MAPI contact.
289
			$contacts = $this->convertVCFContactsToMapi($this->destinationFolder, $attachmentStream);
290
		} catch (ZarafaException $e){
291
			$e->setTitle(_("Import error"));
292
			throw $e;
293
		} catch(Exception $e) {
294
			$destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_DISPLAY_NAME, PR_MDB_PROVIDER));
295
			$fullyQualifiedFolderName = $destinationFolderProps[PR_DISPLAY_NAME];
296
			if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
297
				$publicStore = $GLOBALS["mapisession"]->getPublicMessageStore();
298
				$publicStoreName = mapi_getprops($publicStore, array(PR_DISPLAY_NAME));
299
				$fullyQualifiedFolderName .= " - " . $publicStoreName[PR_DISPLAY_NAME];
300
			} else if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) {
301
				$otherStore = $GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId);
302
				$sharedStoreOwnerName = mapi_getprops($otherStore, array(PR_MAILBOX_OWNER_NAME));
303
				$fullyQualifiedFolderName .= " - " . $sharedStoreOwnerName[PR_MAILBOX_OWNER_NAME];
304
			}
305
306
			$message = sprintf(_("Unable to import '%s' to '%s'. "), $filename, $fullyQualifiedFolderName);
307
			if ($e->getCode() === MAPI_E_TABLE_EMPTY) {
308
				$message .= _("There is no contact found in this file.");
309
			} else if ($e->getCode() === MAPI_E_CORRUPT_DATA) {
310
				$message .= _("The file is corrupt.");
311
			} else if ($e->getCode() === MAPI_E_INVALID_PARAMETER) {
312
				$message .= _("The file is invalid.");
313
			} else {
314
				$message = sprintf(_("Unable to import '%s'. "), $filename) . _("Please contact your system administrator if the problem persists.");
315
			}
316
317
			$e = new ZarafaException($message);
318
			$e->setTitle(_("Import error"));
319
			throw $e;
320
		}
321
322
		if (is_array($contacts) && !empty($contacts)) {
323
			$newcontact = Array();
324
			foreach ($contacts as $contact) {
325
				// As vcf file does not contains fileas, business_address etc properties, we need to set it manually.
326
				// something similar is mentioned in this ticket KC-1509.
327
				$this->processContactData($GLOBALS["mapisession"]->getDefaultMessageStore(), $contact);
328
				mapi_message_savechanges($contact);
329
				$vcf = bin2hex(mapi_getprops($contact, array(PR_ENTRYID))[PR_ENTRYID]);
330
				array_push($newcontact, $vcf);
331
			}
332
			return $newcontact;
333
		}
334
		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...
335
	}
336
337
	/**
338
	 * Function checks whether the file to be converted contains a single vCard
339
	 * or multiple vCard entries. It calls the appropriate method and converts the vcf.
340
	 *
341
	 * @param Object $destinationFolder The folder which holds the message which we need to import from file.
342
	 * @param String $attachmentStream The attachment as a stream.
343
	 * @return Array $contacts The array of contact(s) to be imported.
344
	 */
345
	function convertVCFContactsToMapi($destinationFolder, $attachmentStream)
346
	{
347
		$contacts = array();
348
349
		// If function 'mapi_vcftomapi2' exists, we can use that for both single and multiple vcf files,
350
		// but if it doesn't exist, we use the old function 'mapi_vcftomapi' for single vcf file.
351
		if (function_exists('mapi_vcftomapi2')) {
352
			$contacts = mapi_vcftomapi2($destinationFolder, $attachmentStream);
353
		} else if ($_POST['is_single_import']) {
354
			$newMessage = mapi_folder_createmessage($this->destinationFolder);
355
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
356
			$ok = mapi_vcftomapi($GLOBALS['mapisession']->getSession(), $store, $newMessage, $attachmentStream);
357
			if ($ok !== false) {
358
				$contacts = is_array($newMessage) ? $newMessage : [$newMessage];
359
			}
360
		} else {
361
			// Throw error related to multiple vcf as the function is not available for importing multiple vcf file.
362
			throw new ZarafaException(_("grommunio Web does not support importing multiple VCF with this version."));
363
		}
364
		return $contacts;
365
	}
366
367
	/**
368
	 * Helper function which generate the information like 'fileAs','display name' and
369
	 * 'business address' using existing information.
370
	 *
371
	 * @param object $store Message Store Object
372
	 * @param Object $newMessage The newly imported contact from .vcf file.
373
	 */
374
	function processContactData($store, $newMessage)
375
	{
376
		$properties = array();
377
		$properties["subject"] = PR_SUBJECT;
378
		$properties["fileas"] = "PT_STRING8:PSETID_Address:0x8005";
379
		$properties["display_name"] = PR_DISPLAY_NAME;
380
		$properties["address_book_mv"] = "PT_MV_LONG:PSETID_Address:0x8028";
381
		$properties["address_book_long"] = "PT_LONG:PSETID_Address:0x8029";
382
		$properties["business_address"] = "PT_STRING8:PSETID_Address:0x801b";
383
		$properties["email_address_entryid_1"] = "PT_BINARY:PSETID_Address:0x8085";
384
		$properties["email_address_display_name_1"] = "PT_STRING8:PSETID_Address:0x8080";
385
		$properties["business_address_street"] = "PT_STRING8:PSETID_Address:0x8045";
386
		$properties["business_address_city"] = "PT_STRING8:PSETID_Address:0x8046";
387
		$properties["business_address_state"] = "PT_STRING8:PSETID_Address:0x8047";
388
		$properties["business_address_postal_code"] = "PT_STRING8:PSETID_Address:0x8048";
389
		$properties["business_address_country"] = "PT_STRING8:PSETID_Address:0x8049";
390
391
		$properties = getPropIdsFromStrings($store, $properties);
392
393
		$contactProps = mapi_getprops($newMessage, $properties);
394
395
		$props = array();
396
397
		// Addresses field value.
398
		if (isset($contactProps[$properties["business_address_city"]]) && !empty($contactProps[$properties["business_address_city"]])) {
399
			$businessAddressCity = utf8_decode($contactProps[$properties["business_address_city"]]);
400
401
			$businessAddress = $contactProps[$properties["business_address_street"]] . "\n";
402
			$businessAddress .= $businessAddressCity . " ";
403
			$businessAddress .= $contactProps[$properties["business_address_state"]] . " " . $contactProps[$properties["business_address_postal_code"]] . "\n";
404
			$businessAddress .= $contactProps[$properties["business_address_country"]]. "\n";
405
406
			$props[$properties["business_address_city"]] = $businessAddressCity;
407
			$props[$properties["business_address"]] = $businessAddress;
408
		}
409
410
		// File as field value generator.
411
		if (isset($contactProps[PR_DISPLAY_NAME])) {
412
			$displayName = isset($contactProps[PR_DISPLAY_NAME]) ? utf8_decode($contactProps[PR_DISPLAY_NAME]) : " ";
413
			$displayName = str_replace("\xA0"," ", $displayName);
414
			$str = explode(" ", $displayName);
415
			$prefix = [_('Dr.'), _('Miss'), _('Mr.'), _('Mrs.'), _('Ms.'),_('Prof.')];
416
			$suffix = ['I', 'II', 'III', _('Jr.'), _('Sr.')];
417
418
			foreach ($str as $index => $value) {
419
				$value = preg_replace('/[^.A-Za-z0-9\-]/', '', $value);
420
				if (array_search($value, $prefix,true) !== false) {
421
					$props[PR_DISPLAY_NAME_PREFIX] = $value;
422
					unset($str[$index]);
423
				} else if (array_search($value, $suffix, true) !== false) {
424
					$props[PR_GENERATION] = $value;
425
					unset($str[$index]);
426
				}
427
			}
428
429
			$surname = array_slice($str, count($str) - 1);
430
			$remainder = array_slice($str, 0, count($str) - 1);
431
			$fileAs = $surname[0] . ', ';
432
			if (!empty($remainder)) {
433
				$fileAs .= join(" ", $remainder);
434
				if (count($remainder) > 1) {
435
					$middleName = $remainder[count($remainder) - 1];
436
					$props[PR_MIDDLE_NAME] = $middleName;
437
				}
438
			}
439
440
			// Email fieldset information.
441
			if (isset($contactProps[$properties["email_address_display_name_1"]])) {
442
				$emailAddressDisplayNameOne = $fileAs . " ";
443
				$emailAddressDisplayNameOne .= $contactProps[$properties["email_address_display_name_1"]];
444
				$props[$properties["email_address_display_name_1"]] = $emailAddressDisplayNameOne;
445
				$props[$properties["address_book_long"]] = 1;
446
				$props[$properties["address_book_mv"]] = array(0 => 0);
447
			}
448
449
			$props[$properties["fileas"]] = $fileAs;
450
			$props[PR_DISPLAY_NAME] = $displayName;
451
			mapi_setprops($newMessage, $props);
452
		}
453
	}
454
455
    /**
456
     * Function reads content of the given file and convert the same into
457
     * a webapp appointment into respective destination folder.
458
	 *
459
     * @param String $attachmentStream The attachment as a stream.
460
     * @param String $filename An actual file name.
461
     * @return Boolean true if the import is successful, false otherwise.
462
     */
463
	function importICSFile($attachmentStream, $filename)
464
	{
465
		$this->destinationFolder = $this->getDestinationFolder();
466
467
		try {
468
			$events = $this->convertICSToMapi($attachmentStream);
469
		} catch (ZarafaException $e){
470
			$e->setTitle(_("Import error"));
471
			throw $e;
472
		} catch(Exception $e) {
473
			$destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_DISPLAY_NAME, PR_MDB_PROVIDER));
474
			$fullyQualifiedFolderName = $destinationFolderProps[PR_DISPLAY_NAME];
475
			// Condition true if folder is belongs to Public store.
476
			if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
477
				$publicStore = $GLOBALS["mapisession"]->getPublicMessageStore();
478
				$publicStoreName = mapi_getprops($publicStore, array(PR_DISPLAY_NAME));
479
				$fullyQualifiedFolderName .= " - " . $publicStoreName[PR_DISPLAY_NAME];
480
			} else if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) {
481
				// Condition true if folder is belongs to delegate store.
482
				$otherStore = $GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId);
483
				$sharedStoreOwnerName = mapi_getprops($otherStore, array(PR_MAILBOX_OWNER_NAME));
484
				$fullyQualifiedFolderName .= " - " . $sharedStoreOwnerName[PR_MAILBOX_OWNER_NAME];
485
			}
486
487
			$message = sprintf(_("Unable to import '%s' to '%s'. "), $filename, $fullyQualifiedFolderName);
488
			if ($e->getCode() === MAPI_E_TABLE_EMPTY) {
489
				$message .= _("There is no appointment found in this file.");
490
			} else if ($e->getCode() === MAPI_E_CORRUPT_DATA) {
491
				$message .= _("The file is corrupt.");
492
			} else if ($e->getCode() === MAPI_E_INVALID_PARAMETER) {
493
				$message .= _("The file is invalid.");
494
			} else {
495
				$message = sprintf(_("Unable to import '%s'. "), $filename) . _("Please contact your system administrator if the problem persists.");
496
			}
497
498
			$e = new ZarafaException($message);
499
			$e->setTitle(_("Import error"));
500
			throw $e;
501
		}
502
503
		if (is_array($events) && !empty($events)) {
504
			$newEvents = Array();
505
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
506
			foreach ($events as $event) {
507
				// Save newly imported event
508
				mapi_message_savechanges($event);
509
510
				$newMessageProps = mapi_getprops($event, array(PR_MESSAGE_CLASS));
511
				if (isset($newMessageProps[PR_MESSAGE_CLASS]) && $newMessageProps[PR_MESSAGE_CLASS] !== 'IPM.Appointment') {
512
					// Convert the Meeting request record to proper appointment record so we can
513
					// properly show the appointment in calendar.
514
					$req = new Meetingrequest($store, $event, $GLOBALS['mapisession']->getSession(), ENABLE_DIRECT_BOOKING);
515
					$req->doAccept(true, false, false, false,false,false, false, false,false, true);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $newProposedEndTime of Meetingrequest::doAccept(). ( Ignorable by Annotation )

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

515
					$req->doAccept(true, false, false, false,/** @scrutinizer ignore-type */ false,false, false, false,false, true);
Loading history...
Bug introduced by
false of type false is incompatible with the type string expected by parameter $basedate of Meetingrequest::doAccept(). ( Ignorable by Annotation )

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

515
					$req->doAccept(true, false, false, false,false,false, false, false,/** @scrutinizer ignore-type */ false, true);
Loading history...
Bug introduced by
false of type false is incompatible with the type string expected by parameter $newProposedStartTime of Meetingrequest::doAccept(). ( Ignorable by Annotation )

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

515
					$req->doAccept(true, false, false, /** @scrutinizer ignore-type */ false,false,false, false, false,false, true);
Loading history...
516
				}
517
518
				$this->allowUpdateCounter = false;
519
				$entryid = bin2hex(mapi_getprops($event, array(PR_ENTRYID))[PR_ENTRYID]);
520
				array_push($newEvents, $entryid);
521
			}
522
			return $newEvents;
523
		}
524
525
		return false;
526
	}
527
528
	/**
529
	 * Function checks whether the file to be converted contains a single event ics
530
	 * or multiple ics event entries.
531
	 *
532
	 * @param String $attachmentStream The attachment as a stream.
533
	 * @return Array $events The array of calendar items to be imported.
534
	 */
535
	function convertICSToMapi($attachmentStream)
536
	{
537
		$events = array();
538
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
539
540
		// If function 'mapi_icaltomapi2' exists, we can use that for both single and multiple ics files,
541
		// but if it doesn't exist, we use the old function 'mapi_icaltomapi' for single ics file.
542
		if (function_exists('mapi_icaltomapi2')) {
543
			$events = mapi_icaltomapi2($addrBook, $this->destinationFolder, $attachmentStream);
544
		} else if ($_POST['is_single_import']) {
545
			$newMessage = mapi_folder_createmessage($this->destinationFolder);
546
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
547
			$ok = mapi_icaltomapi($GLOBALS['mapisession']->getSession(), $store, $addrBook, $newMessage, $attachmentStream, false);
548
549
			if ($ok !== false) {
550
				array_push($events, $newMessage);
551
			}
552
		} else {
553
			// Throw error related to multiple ics as the function is not available for importing multiple ics file.
554
			throw new ZarafaException(_("grommunio Web does not support importing multiple ICS with this version."));
555
		}
556
		return $events;
557
	}
558
559
	/**
560
	 * Function reads content of the given file and convert the same into
561
	 * a webapp email into respective destination folder.
562
	 *
563
	 * @param String $attachTempName A temporary file name of server location where it actually saved/available.
564
	 * @param String $filename An actual file name.
565
	 * @return Boolean true if the import is successful, false otherwise.
566
	 */
567
	function importEMLFile($attachmentStream, $filename)
568
	{
569
		if (isBrokenEml($attachmentStream)) {
570
			throw new ZarafaException(sprintf(_("Unable to import '%s'"), $filename) . ". ". _("The EML is not valid"));
571
		}
572
573
		$this->destinationFolder = $this->getDestinationFolder();
574
575
		$newMessage = mapi_folder_createmessage($this->destinationFolder);
576
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
577
		// Convert an RFC822-formatted e-mail to a MAPI Message
578
		$ok = mapi_inetmapi_imtomapi($GLOBALS['mapisession']->getSession(), $this->store, $addrBook, $newMessage, $attachmentStream, array());
579
580
		if($ok === true) {
581
			mapi_message_savechanges($newMessage);
582
			return bin2hex(mapi_getprops($newMessage, array(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...
583
		}
584
585
		return false;
586
	}
587
588
    /**
589
	 * Function used get the destination folder in which
590
	 * item gets imported.
591
	 *
592
     * @return Object folder object in which item gets imported.
593
     */
594
	function getDestinationFolder()
595
	{
596
        $destinationFolder = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $destinationFolder is dead and can be removed.
Loading history...
597
		try {
598
			$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

598
			$destinationFolder = mapi_msgstore_openentry($this->store, hex2bin(/** @scrutinizer ignore-type */ $this->destinationFolderId));
Loading history...
599
		} catch(Exception $e) {
600
			// Try to find the folder from shared stores in case if it is not found in current user's store
601
			$destinationFolder = mapi_msgstore_openentry($GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId), hex2bin($this->destinationFolderId));
602
		}
603
		return $destinationFolder;
604
	}
605
606
	/**
607
	 * Function deletes uploaded attachment files and send
608
	 * proper response back to client.
609
	 */
610
	function deleteUploadedFiles()
611
	{
612
		$num = sanitizePostValue('attach_num', false, NUMERIC_REGEX);
613
		$attachID = sanitizePostValue('attach_id', false, STRING_REGEX);
614
615
		if($num === false) {
616
			// string is passed in attachNum so get it
617
			// Parse the filename, strip it from any illegal characters
618
			$num = mb_basename(stripslashes(sanitizePostValue('attach_num', '', FILENAME_REGEX)));
619
620
			// Delete the file instance and unregister the file
621
			$this->attachment_state->deleteUploadedAttachmentFile($_REQUEST['dialog_attachments'], $num, $attachID);
622
		} else {
623
			// Set the correct array structure
624
			$this->attachment_state->addDeletedAttachment($_REQUEST['dialog_attachments'], $num);
625
		}
626
627
		$return = Array(
628
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
629
			'success' => true,
630
			'zarafa' => Array(
631
				sanitizeGetValue('module', '', STRING_REGEX) => Array(
632
					sanitizeGetValue('moduleid', '', STRING_REGEX) => Array(
633
						'delete' => Array(
634
							'success'=> true
635
						)
636
					)
637
				)
638
			)
639
		);
640
641
		echo json_encode($return);
642
	}
643
644
	/**
645
	 * Function adds embedded/icsfile attachment and send
646
	 * proper response back to client.
647
	 */
648
	function addingEmbeddedAttachments()
649
	{
650
		$attachID = $_POST['attach_id'];
651
		$attachTampName = $this->attachment_state->addEmbeddedAttachment($_REQUEST['dialog_attachments'], array(
652
			'entryid' => sanitizePostValue('entryid', '', ID_REGEX),
653
			'store_entryid' => sanitizePostValue('store_entryid', '', ID_REGEX),
654
			'sourcetype' => intval($_POST['attach_method'], 10) === ATTACH_EMBEDDED_MSG ? 'embedded' : 'icsfile',
655
			'attach_id' => $attachID,
656
		));
657
658
		$returnfiles[] = Array(
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...
659
			'props' => Array(
660
				'attach_num' => -1,
661
				'tmpname' => $attachTampName,
662
				'attach_id' => $attachID,
663
				// we are not using this data here, so we can safely use $_POST directly without any sanitization
664
				// this is only needed to identify response for a particular attachment record on client side
665
				'name' => $_POST['name']
666
			)
667
		);
668
669
		$return = Array(
670
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
671
			'success' => true,
672
			'zarafa' => Array(
673
				sanitizeGetValue('module', '', STRING_REGEX) => Array(
674
					sanitizeGetValue('moduleid', '', STRING_REGEX) => Array(
675
						'update' => Array(
676
							'item'=> $returnfiles
677
						)
678
					)
679
				)
680
			)
681
		);
682
683
		echo json_encode($return);
684
	}
685
686
	/**
687
	 * Function adds attachment in case of OOo.
688
	 */
689
	function uploadWhenWendViaOOO()
690
	{
691
		$providedFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $_GET['attachment_id'];
692
693
		// check whether the doc is already moved
694
		if (file_exists($providedFile)) {
695
			$filename = mb_basename(stripslashes($_GET['name']));
696
697
			// Move the uploaded file to the session
698
			$this->attachment_state->addProvidedAttachmentFile($_REQUEST['attachment_id'], $filename, $providedFile, array(
699
				'name'       => $filename,
700
				'size'       => filesize($tmpname),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tmpname seems to be never defined.
Loading history...
701
				'type'       => mime_content_type($tmpname),
702
				'sourcetype' => 'default'
703
			));
704
		} else {
705
			// Check if no files are uploaded with this attachmentid
706
			$this->attachment_state->clearAttachmentFiles($_GET['attachment_id']);
707
		}
708
	}
709
710
	/**
711
	 * Helper function to send proper response for import request only. It sets the appropriate
712
	 * notifier to be sent in the response according to the type of the file to be imported.
713
	 */
714
	function sendImportResponse($importStatus)
715
	{
716
		$storeProps = mapi_getprops($this->store, array(PR_ENTRYID));
717
		$destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_PARENT_ENTRYID, PR_CONTENT_UNREAD));
718
		$notifierModuleId = $this->notifierModule . '1';
719
720
		$return = Array(
721
			'success' => true,
722
			'zarafa' => Array(
723
				sanitizeGetValue('module', '', STRING_REGEX) => Array(
724
					sanitizeGetValue('moduleid', '', STRING_REGEX) => Array(
725
						'import' => Array(
726
							'success'=> true,
727
							'items' => $importStatus
728
						)
729
					)
730
				),
731
				'hierarchynotifier' => Array(
732
					'hierarchynotifier1' => Array(
733
						'folders' => Array(
734
							'item' => Array(
735
								0 => Array(
736
									'entryid' => $this->destinationFolderId,
737
									'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]),
738
									'store_entryid' => bin2hex($storeProps[PR_ENTRYID]),
739
									'props' => Array(
740
										'content_unread' => $this->allowUpdateCounter ? $destinationFolderProps[PR_CONTENT_UNREAD] + 1 : 0
741
									)
742
								)
743
							)
744
						)
745
					)
746
				),
747
				$this->notifierModule => Array(
748
					$notifierModuleId => Array(
749
						'newobject' => Array(
750
							'item' => Array(
751
								0 => Array(
752
									'content_count' => 2,
753
									'content_unread' => 0,
754
									'display_name' => 'subsubin',
755
									'entryid' => $this->destinationFolderId,
756
									'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]),
757
									'store_entryid' => bin2hex($storeProps[PR_ENTRYID])
758
								)
759
							)
760
						)
761
					)
762
				)
763
			)
764
		);
765
		echo json_encode($return);
766
	}
767
768
	/**
769
	 * Function will encode all the necessary information about the exception
770
	 * into JSON format and send the response back to client.
771
	 *
772
	 * @param object $exception Exception object.
773
	 * @param String $title Title which used to show as title of exception dialog.
774
	 */
775
	function handleUploadException($exception, $title = null)
776
	{
777
		$return = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $return is dead and can be removed.
Loading history...
778
779
		// MAPI_E_NOT_FOUND exception contains generalize exception message.
780
		// Set proper exception message as display message should be user understandable.
781
		if($exception->getCode() == MAPI_E_NOT_FOUND) {
782
			$exception->setDisplayMessage(_('Could not find message, either it has been moved or deleted.'));
783
		}
784
785
		// Set the headers
786
		header('Expires: 0'); // set expiration time
787
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
788
789
		// Set Content Disposition header
790
		header('Content-Disposition: inline');
791
		// Set content type header
792
		header('Content-Type: text/plain');
793
794
		$return = Array(
795
			// 'success' property is needed for Extjs Ext.form.Action.Submit#success handler
796
			'success' => false,
797
			'zarafa' => Array(
798
				'error' => array(
799
					'type' => ERROR_GENERAL,
800
					'info' => array(
801
						'file' => $exception->getFileLine(),
802
						'title' => $title,
803
						'display_message' => $exception->getDisplayMessage(),
804
						'original_message' => $exception->getMessage()
805
					)
806
				)
807
			)
808
		);
809
810
		echo json_encode($return);
811
	}
812
813
	/**
814
	 * Generic function to check received data and take necessary action.
815
	 */
816
	public function upload()
817
	{
818
		$this->attachment_state->open();
819
820
		// Check if dialog_attachments is set
821
		if(isset($_REQUEST['dialog_attachments'])) {
822
			// Check if attachments have been uploaded
823
			if(isset($_FILES['attachments']) && is_array($_FILES['attachments'])) {
824
				// import given files into respective webapp folder
825
				$this->processFiles();
826
			} else if (isset($_POST['deleteattachment'])) {
827
				$this->deleteUploadedFiles();
828
			} else if (isset($_POST['entryid'])) {	// Check for adding of embedded attachments
829
				$this->addingEmbeddedAttachments();
830
			}
831
		} else if($_GET && isset($_GET['attachment_id'])) { // this is to upload the file to server when the doc is send via OOo
832
			$this->uploadWhenWendViaOOO();
833
		}
834
835
		$this->attachment_state->close();
836
	}
837
}
838
839
// create instance of class
840
$uploadInstance = new UploadAttachment();
841
842
try {
843
	// initialize variables
844
	$uploadInstance->init($_REQUEST);
845
846
	// upload files
847
	$uploadInstance->upload();
848
}catch (ZarafaException $e) {
849
	$uploadInstance->handleUploadException($e, $e->getTitle());
850
} catch (Exception $e) {
851
	$uploadInstance->handleUploadException($e);
852
}
853
?>
0 ignored issues
show
Best Practice introduced by
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...
854