grommunio /
grommunio-web
| 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)); |
||
| 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); |
||
| 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); |
||
| 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)) { |
||
| 259 | case 'EML': |
||
| 260 | $this->notifierModule = 'maillistnotifier'; |
||
| 261 | return $this->importEMLFile($attachmentStream, $filename); |
||
| 262 | break; |
||
| 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); |
||
| 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; |
||
| 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); |
||
| 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]); |
||
| 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; |
||
| 597 | try { |
||
| 598 | $destinationFolder = mapi_msgstore_openentry($this->store, hex2bin($this->destinationFolderId)); |
||
| 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( |
||
| 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), |
||
| 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(); |
||
| 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
|
|||
| 854 |
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.