grommunio /
grommunio-web
| 1 | <?php |
||
| 2 | // required to handle php errors |
||
| 3 | require_once(__DIR__ . '/exceptions/class.ZarafaErrorException.php'); |
||
| 4 | require_once(__DIR__ . '/download_base.php'); |
||
| 5 | |||
| 6 | /** |
||
| 7 | * DownloadAttachment |
||
| 8 | * |
||
| 9 | * A class to manage downloading of attachments from message, additionally |
||
| 10 | * this class can be used to download inline images from message as well. |
||
| 11 | * |
||
| 12 | * Main reason to create this class is to not pollute the global namespace. |
||
| 13 | */ |
||
| 14 | class DownloadAttachment extends DownloadBase |
||
| 15 | { |
||
| 16 | /** |
||
| 17 | * Content disposition type for the attachment that will be sent with header with the attachment data |
||
| 18 | * Possible values are 'inline' and 'attachment'. When content-type is application/octet-stream and |
||
| 19 | * content disposition type is 'attachment' then browser will show dialog to save attachment as instead of |
||
| 20 | * directly displaying content inline. |
||
| 21 | */ |
||
| 22 | private $contentDispositionType; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Attachment number of the attachment that should be downloaded. For normal attachments this will contain |
||
| 26 | * a single element array with numeric value as sequential attachment number, for attachments that are not saved |
||
| 27 | * in AttachmentTable of MAPIMessage yet (recently uploaded attachments) this will give single element array |
||
| 28 | * having value as a string in form of 'filename randomstring'. When accessing embedded messages this array can contain |
||
| 29 | * multiple elements indicating attachment numbers at each level, So value [0, 1] will indicate we want to download |
||
| 30 | * second attachment of first embedded message. |
||
| 31 | */ |
||
| 32 | private $attachNum; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Attachment Content Id is used to download inline images of the MAPIMessage, When requesting inline images only |
||
| 36 | * content id is passed but if we are accessing inline image from embedded message then besides content id, |
||
| 37 | * attachment number is also passed to access embedded message. |
||
| 38 | */ |
||
| 39 | private $attachCid; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * A string that will be initialized with grommunio Web-specific and common-for-all file name for ZIP file. |
||
| 43 | */ |
||
| 44 | private $zipFileName; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * A random string that will be generated with every MAPIMessage instance to uniquely identify attachments that |
||
| 48 | * belongs to this MAPIMessage, this is mainly used to get recently uploaded attachments for MAPIMessage. |
||
| 49 | */ |
||
| 50 | private $dialogAttachments; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * A boolean value, set to false by default, to define if the message, of which the attachments are required to be wrapped in ZIP, |
||
| 54 | * is a sub message of other webapp item or not. |
||
| 55 | */ |
||
| 56 | private $isSubMessage; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * Entryid of the MAPIFolder to which the given attachment needs to be imported as webapp item. |
||
| 60 | */ |
||
| 61 | private $destinationFolderId; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Resource of the MAPIFolder to which the given attachment needs to be imported as webapp item. |
||
| 65 | */ |
||
| 66 | private $destinationFolder; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * A boolean value, set to false by default, to define if the attachment needs to be imported into folder as webapp item. |
||
| 70 | */ |
||
| 71 | private $import; |
||
| 72 | |||
| 73 | /** |
||
| 74 | * A boolean value, set to false by default, to define if the embedded attachment needs to be imported into folder. |
||
| 75 | */ |
||
| 76 | private $isEmbedded; |
||
| 77 | |||
| 78 | /** |
||
| 79 | * Resource of the shared MAPIStore into which attachments needs to be imported. |
||
| 80 | */ |
||
| 81 | private $otherStore; |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Constructor |
||
| 85 | */ |
||
| 86 | public function __construct() |
||
| 87 | { |
||
| 88 | $this->contentDispositionType = 'attachment'; |
||
| 89 | $this->attachNum = array(); |
||
| 90 | $this->attachCid = false; |
||
| 91 | $this->zipFileName = _('Attachments').'%s.zip'; |
||
| 92 | $this->messageSubject = ''; |
||
| 93 | $this->isSubMessage = false; |
||
| 94 | $this->destinationFolderId = false; |
||
| 95 | $this->destinationFolder = false; |
||
| 96 | $this->import = false; |
||
| 97 | $this->isEmbedded = false; |
||
| 98 | $this->otherStore = false; |
||
| 99 | |||
| 100 | parent:: __construct(); |
||
| 101 | } |
||
| 102 | |||
| 103 | /** |
||
| 104 | * Function will initialize data for this class object. it will also sanitize data |
||
| 105 | * for possible XSS attack because data is received in $_GET |
||
| 106 | */ |
||
| 107 | public function init($data) |
||
| 108 | { |
||
| 109 | if(isset($data['store'])) { |
||
| 110 | $this->store = sanitizeValue($data['store'], '', ID_REGEX); |
||
| 111 | } |
||
| 112 | |||
| 113 | if(isset($data['entryid'])) { |
||
| 114 | $this->entryId = sanitizeValue($data['entryid'], '', ID_REGEX); |
||
| 115 | } |
||
| 116 | |||
| 117 | if(isset($data['contentDispositionType'])) { |
||
| 118 | $this->contentDispositionType = sanitizeValue($data['contentDispositionType'], 'attachment', STRING_REGEX); |
||
| 119 | } |
||
| 120 | |||
| 121 | if(!empty($data['attachNum'])) { |
||
| 122 | /** |
||
| 123 | * if you are opening an already saved attachment then $data["attachNum"] |
||
| 124 | * will contain array of numeric index for that attachment (like 0 or 1 or 2) |
||
| 125 | * |
||
| 126 | * if you are opening a recently uploaded attachment then $data["attachNum"] |
||
| 127 | * will be a one element array and it will contain a string in "filename.randomstring" format |
||
| 128 | * like README.txtu6K6AH |
||
| 129 | */ |
||
| 130 | foreach($data['attachNum'] as $attachNum) { |
||
| 131 | $num = sanitizeValue($attachNum, false, NUMERIC_REGEX); |
||
| 132 | |||
| 133 | if($num === false) { |
||
| 134 | // string is passed in attachNum so get it |
||
| 135 | $num = sanitizeValue($attachNum, '', FILENAME_REGEX); |
||
| 136 | |||
| 137 | if(!empty($num)) { |
||
| 138 | array_push($this->attachNum, $num); |
||
| 139 | } |
||
| 140 | } else { |
||
| 141 | array_push($this->attachNum, (int) $num); |
||
| 142 | } |
||
| 143 | } |
||
| 144 | } |
||
| 145 | |||
| 146 | if(isset($data['attachCid'])) { |
||
| 147 | $this->attachCid = rawurldecode($data['attachCid']); |
||
| 148 | } |
||
| 149 | |||
| 150 | if(isset($data['AllAsZip'])) { |
||
| 151 | $this->allAsZip = sanitizeValue($data['AllAsZip'], '', STRING_REGEX); |
||
| 152 | } |
||
| 153 | |||
| 154 | if(isset($data['subject'])) { |
||
| 155 | // Remove characters that we cannot use in a filename |
||
| 156 | $data['subject'] = preg_replace('/[^a-z0-9 ()]/mi', '_', $data['subject']); |
||
| 157 | $this->messageSubject = sanitizeValue($data['subject'], '', FILENAME_REGEX); |
||
| 158 | } |
||
| 159 | |||
| 160 | if($this->allAsZip && isset($data['isSubMessage'])){ |
||
| 161 | $this->isSubMessage = sanitizeValue($data['isSubMessage'], '', STRING_REGEX); |
||
| 162 | } |
||
| 163 | |||
| 164 | if(isset($data['dialog_attachments'])) { |
||
| 165 | $this->dialogAttachments = sanitizeValue($data['dialog_attachments'], '', STRING_REGEX); |
||
| 166 | } |
||
| 167 | |||
| 168 | if($this->store && $this->entryId) { |
||
| 169 | $this->store = $GLOBALS['mapisession']->openMessageStore(hex2bin($this->store)); |
||
| 170 | $this->message = mapi_msgstore_openentry($this->store, hex2bin($this->entryId)); |
||
| 171 | |||
| 172 | // Decode smime signed messages on this message |
||
| 173 | parse_smime($this->store, $this->message); |
||
| 174 | } |
||
| 175 | |||
| 176 | if(isset($data['destination_folder'])) { |
||
| 177 | $this->destinationFolderId = sanitizeValue($data['destination_folder'], '', ID_REGEX); |
||
| 178 | |||
| 179 | if ($this->destinationFolder === false){ |
||
| 180 | try { |
||
| 181 | $this->destinationFolder = mapi_msgstore_openentry($this->store, hex2bin($this->destinationFolderId)); |
||
| 182 | } catch(Exception $e) { |
||
| 183 | // Try to find the folder from shared stores in case if it is not found in current user's store |
||
| 184 | $this->otherStore = $GLOBALS['operations']->getOtherStoreFromEntryid($this->destinationFolderId); |
||
| 185 | if ($this->otherStore !== false) { |
||
| 186 | $this->destinationFolder = mapi_msgstore_openentry($this->otherStore, hex2bin($this->destinationFolderId)); |
||
| 187 | } else { |
||
| 188 | $this->destinationFolder = mapi_msgstore_openentry($GLOBALS["mapisession"]->getPublicMessageStore(), hex2bin($this->destinationFolderId)); |
||
| 189 | if (!$this->destinationFolder) { |
||
| 190 | throw new ZarafaException(_("Destination folder not found.")); |
||
| 191 | } |
||
| 192 | } |
||
| 193 | } |
||
| 194 | } |
||
| 195 | } |
||
| 196 | |||
| 197 | if(isset($data['import'])) { |
||
| 198 | $this->import = sanitizeValue($data['import'], '', STRING_REGEX); |
||
| 199 | } |
||
| 200 | |||
| 201 | if(isset($data['is_embedded'])) { |
||
| 202 | $this->isEmbedded = sanitizeValue($data['is_embedded'], '', STRING_REGEX); |
||
| 203 | } |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Returns inline image attachment based on specified attachCid, To get inline image attachment |
||
| 208 | * we need to compare passed attachCid with PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_LOCATION or |
||
| 209 | * PR_ATTACH_FILENAME and if that matches then we can get that attachment. |
||
| 210 | * @param MAPIAttach $attachment (optional) embedded message attachment from where we need to get the inline image |
||
| 211 | * @return MAPIAttach attachment that is requested and will be sent to client |
||
| 212 | */ |
||
| 213 | public function getAttachmentByAttachCid($attachment = false) |
||
| 214 | { |
||
| 215 | // If the inline image was in a submessage, we have to open that first |
||
| 216 | if($attachment !== false) { |
||
| 217 | $this->message = mapi_attach_openobj($attachment); |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * restriction to find inline image attachment with matching cid passed |
||
| 222 | */ |
||
| 223 | $restriction = Array(RES_OR, |
||
| 224 | Array( |
||
| 225 | Array(RES_CONTENT, |
||
| 226 | Array( |
||
| 227 | FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE, |
||
| 228 | ULPROPTAG => PR_ATTACH_CONTENT_ID, |
||
| 229 | VALUE => array(PR_ATTACH_CONTENT_ID => $this->attachCid) |
||
| 230 | ) |
||
| 231 | ), |
||
| 232 | Array(RES_CONTENT, |
||
| 233 | Array( |
||
| 234 | FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE, |
||
| 235 | ULPROPTAG => PR_ATTACH_CONTENT_LOCATION, |
||
| 236 | VALUE => array(PR_ATTACH_CONTENT_LOCATION => $this->attachCid) |
||
| 237 | ) |
||
| 238 | ), |
||
| 239 | Array(RES_CONTENT, |
||
| 240 | Array( |
||
| 241 | FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE, |
||
| 242 | ULPROPTAG => PR_ATTACH_FILENAME, |
||
| 243 | VALUE => array(PR_ATTACH_FILENAME => $this->attachCid) |
||
| 244 | ) |
||
| 245 | ) |
||
| 246 | ) |
||
| 247 | ); |
||
| 248 | |||
| 249 | // Get the attachment table |
||
| 250 | $attachTable = mapi_message_getattachmenttable($this->message); |
||
| 251 | mapi_table_restrict($attachTable, $restriction, TBL_BATCH); |
||
| 252 | $attachments = mapi_table_queryallrows($attachTable, Array(PR_ATTACH_NUM)); |
||
| 253 | |||
| 254 | if(count($attachments) > 0) { |
||
| 255 | // there should be only one attachment |
||
| 256 | $attachment = mapi_message_openattach($this->message, $attachments[0][PR_ATTACH_NUM]); |
||
| 257 | } |
||
| 258 | |||
| 259 | return $attachment; |
||
| 260 | } |
||
| 261 | |||
| 262 | /** |
||
| 263 | * Returns attachment based on specified attachNum, additionally it will also get embedded message |
||
| 264 | * if we want to get the inline image attachment. |
||
| 265 | * @return MAPIAttach embedded message attachment or attachment that is requested |
||
| 266 | */ |
||
| 267 | public function getAttachmentByAttachNum() |
||
| 268 | { |
||
| 269 | $attachment = false; |
||
| 270 | |||
| 271 | $len = count($this->attachNum); |
||
| 272 | |||
| 273 | // Loop through the attachNums, message in message in message ... |
||
| 274 | for($index = 0; $index < $len - 1; $index++) { |
||
| 275 | // Open the attachment |
||
| 276 | $tempattach = mapi_message_openattach($this->message, $this->attachNum[$index]); |
||
| 277 | if($tempattach) { |
||
| 278 | // Open the object in the attachment |
||
| 279 | $this->message = mapi_attach_openobj($tempattach); |
||
| 280 | } |
||
| 281 | } |
||
| 282 | |||
| 283 | // open the attachment |
||
| 284 | $attachment = mapi_message_openattach($this->message, $this->attachNum[$len - 1]); |
||
| 285 | |||
| 286 | return $attachment; |
||
| 287 | } |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Function will open passed attachment and generate response for that attachment to send it to client. |
||
| 291 | * This should only be used to download attachment that is already saved in MAPIMessage. |
||
| 292 | * @param MAPIAttach $attachment attachment which will be dumped to client side |
||
| 293 | * @param Boolean $inline inline attachment or not |
||
| 294 | * @return Response response to sent to client including attachment data |
||
| 295 | */ |
||
| 296 | public function downloadSavedAttachment($attachment, $inline = False) |
||
| 297 | { |
||
| 298 | // Check if the attachment is opened |
||
| 299 | if($attachment) { |
||
| 300 | // Get the props of the attachment |
||
| 301 | $props = mapi_attach_getprops($attachment, array(PR_ATTACH_FILENAME, PR_ATTACH_LONG_FILENAME, PR_ATTACH_MIME_TAG, PR_DISPLAY_NAME, PR_ATTACH_METHOD, PR_ATTACH_CONTENT_ID)); |
||
| 302 | // Content Type |
||
| 303 | $contentType = 'application/octet-stream'; |
||
| 304 | // Filename |
||
| 305 | $filename = 'ERROR'; |
||
| 306 | |||
| 307 | // Set filename |
||
| 308 | if ($inline) { |
||
| 309 | /* |
||
| 310 | * Inline attachments are set to "inline.txt" by Gromox, see inetmapi/VMIMEToMAPI.cpp and search for inline.txt. |
||
| 311 | * Gromox would have to extract the alt/title tag from the img tag when converting it to MAPI. Since it |
||
| 312 | * does not handle this, set the filename to CONTENT_ID plus mime tag. |
||
| 313 | */ |
||
| 314 | $tags = explode('/', $props[PR_ATTACH_MIME_TAG]); |
||
| 315 | // IE 11 is weird, when a user renames the file it's not saved as in image, when |
||
| 316 | // the filename is "test.jpeg", but it works when it's "test.jpg". |
||
| 317 | $filename = $props[PR_ATTACH_CONTENT_ID] . '.' . str_replace('jpeg', 'jpg', $tags[1]); |
||
| 318 | } else if(isset($props[PR_ATTACH_LONG_FILENAME])) { |
||
| 319 | $filename = $props[PR_ATTACH_LONG_FILENAME]; |
||
| 320 | } else if(isset($props[PR_ATTACH_FILENAME])) { |
||
| 321 | $filename = $props[PR_ATTACH_FILENAME]; |
||
| 322 | } else if(isset($props[PR_DISPLAY_NAME])) { |
||
| 323 | $filename = $props[PR_DISPLAY_NAME]; |
||
| 324 | } |
||
| 325 | |||
| 326 | // Set content type if available, otherwise it will be default to application/octet-stream |
||
| 327 | if(isset($props[PR_ATTACH_MIME_TAG])) { |
||
| 328 | $contentType = $props[PR_ATTACH_MIME_TAG]; |
||
| 329 | } |
||
| 330 | |||
| 331 | $contentIsSentAsUTF8 = false; |
||
| 332 | // For ODF files we must change the content type because otherwise |
||
| 333 | // IE<11 cannot properly read it in the xmlhttprequest object |
||
| 334 | // NOTE: We only need to check for IE<=10, so no need to check for TRIDENT (IE11) |
||
| 335 | preg_match('/MSIE (.*?);/', $_SERVER['HTTP_USER_AGENT'], $matches); |
||
| 336 | if (count($matches)>1){ |
||
| 337 | if ( strpos($contentType, 'application/vnd.oasis.opendocument.') !== false ){ |
||
| 338 | $contentType = 'text/plain; charset=UTF-8'; |
||
| 339 | $contentIsSentAsUTF8 = true; |
||
| 340 | } |
||
| 341 | } |
||
| 342 | |||
| 343 | // Set the headers |
||
| 344 | header('Pragma: public'); |
||
| 345 | header('Expires: 0'); // set expiration time |
||
| 346 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); |
||
| 347 | header('Content-Disposition: ' . $this->contentDispositionType . '; filename="' . addslashes(browserDependingHTTPHeaderEncode($filename)) . '"'); |
||
| 348 | header('Content-Type: ' . $contentType); |
||
| 349 | header('Content-Transfer-Encoding: binary'); |
||
| 350 | |||
| 351 | // Open a stream to get the attachment data |
||
| 352 | $stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0); |
||
| 353 | $stat = mapi_stream_stat($stream); |
||
| 354 | // File length |
||
| 355 | header('Content-Length: ' . $stat['cb']); |
||
| 356 | |||
| 357 | // Read the attachment content from the stream |
||
| 358 | $body = ''; |
||
| 359 | for($i = 0; $i < $stat['cb']; $i += BLOCK_SIZE) { |
||
| 360 | $body .= mapi_stream_read($stream, BLOCK_SIZE); |
||
| 361 | } |
||
| 362 | |||
| 363 | // Convert the content to UTF-8 if we want to send it like that |
||
| 364 | if ( $contentIsSentAsUTF8 ){ |
||
| 365 | $body = mb_convert_encoding($body, 'UTF-8'); |
||
| 366 | } |
||
| 367 | echo $body; |
||
| 368 | } |
||
| 369 | } |
||
| 370 | |||
| 371 | /** |
||
| 372 | * Helper function to configure header information which is required to send response as a ZIP archive |
||
| 373 | * containing all the attachments. |
||
| 374 | * @param String $randomZipName A random zip archive name. |
||
| 375 | */ |
||
| 376 | public function sendZipResponse($randomZipName) |
||
| 377 | { |
||
| 378 | $subject = isset($this->messageSubject) ? ' '.$this->messageSubject : ''; |
||
| 379 | |||
| 380 | // Set the headers |
||
| 381 | header('Pragma: public'); |
||
| 382 | header('Expires: 0'); // set expiration time |
||
| 383 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); |
||
| 384 | header('Content-Disposition: ' . $this->contentDispositionType . '; filename="' . addslashes(browserDependingHTTPHeaderEncode(sprintf($this->zipFileName, $subject))) . '"'); |
||
| 385 | header('Content-Transfer-Encoding: binary'); |
||
| 386 | header('Content-Type: application/zip'); |
||
| 387 | header('Content-Length: ' . filesize($randomZipName)); |
||
| 388 | |||
| 389 | // Send the actual response as ZIP file |
||
| 390 | readfile($randomZipName); |
||
| 391 | |||
| 392 | // Remove the zip file to avoid unnecessary disk-space consumption |
||
| 393 | unlink($randomZipName); |
||
| 394 | } |
||
| 395 | |||
| 396 | /** |
||
| 397 | * Function will open all attachments of message and prepare a ZIP file response for that attachment to send it to client. |
||
| 398 | * This should only be used to download attachment that is already saved in MAPIMessage. |
||
| 399 | * @param AttachmentState $attachment_state Object of AttachmentState class. |
||
| 400 | * @param ZipArchive $zip ZipArchive object. |
||
| 401 | */ |
||
| 402 | public function addAttachmentsToZipArchive($attachment_state, $zip) |
||
| 403 | { |
||
| 404 | // Get all the attachments from message |
||
| 405 | $attachmentTable = mapi_message_getattachmenttable($this->message); |
||
| 406 | $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD)); |
||
| 407 | |||
| 408 | foreach($attachments as $attachmentRow) { |
||
| 409 | if($attachmentRow[PR_ATTACH_METHOD] !== ATTACH_EMBEDDED_MSG) { |
||
| 410 | $attachment = mapi_message_openattach($this->message, $attachmentRow[PR_ATTACH_NUM]); |
||
| 411 | |||
| 412 | // Prevent inclusion of inline attachments and contact photos into ZIP |
||
| 413 | if(!$attachment_state->isInlineAttachment($attachment) && !$attachment_state->isContactPhoto($attachment)){ |
||
| 414 | $props = mapi_attach_getprops($attachment, array(PR_ATTACH_LONG_FILENAME)); |
||
| 415 | |||
| 416 | // Open a stream to get the attachment data |
||
| 417 | $stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0); |
||
| 418 | $stat = mapi_stream_stat($stream); |
||
| 419 | |||
| 420 | // Get the stream |
||
| 421 | $datastring = ''; |
||
| 422 | for($i = 0; $i < $stat['cb']; $i += BLOCK_SIZE) { |
||
| 423 | $datastring .= mapi_stream_read($stream, BLOCK_SIZE); |
||
| 424 | } |
||
| 425 | |||
| 426 | // Add file into zip by stream |
||
| 427 | $fileDownloadName = $this->handleDuplicateFileNames($props[PR_ATTACH_LONG_FILENAME]); |
||
| 428 | $zip->addFromString($fileDownloadName, $datastring); |
||
| 429 | } |
||
| 430 | } |
||
| 431 | } |
||
| 432 | |||
| 433 | // Go for adding unsaved attachments in ZIP, if any. |
||
| 434 | // This situation arise while user upload attachments in draft. |
||
| 435 | $attachmentFiles = $attachment_state->getAttachmentFiles($this->dialogAttachments); |
||
| 436 | if($attachmentFiles){ |
||
| 437 | $this->addUnsavedAttachmentsToZipArchive($attachment_state, $zip); |
||
| 438 | } |
||
| 439 | } |
||
| 440 | |||
| 441 | /** |
||
| 442 | * Function will send attachment data to client side. |
||
| 443 | * This should only be used to download attachment that is recently uploaded and not saved in MAPIMessage. |
||
| 444 | * @return Response response to sent to client including attachment data |
||
| 445 | */ |
||
| 446 | public function downloadUnsavedAttachment() |
||
| 447 | { |
||
| 448 | // return recently uploaded file |
||
| 449 | $attachment_state = new AttachmentState(); |
||
| 450 | $attachment_state->open(); |
||
| 451 | |||
| 452 | // there will be only one value in attachNum so directly access 0th element of it |
||
| 453 | $tmpname = $attachment_state->getAttachmentPath($this->attachNum[0]); |
||
| 454 | $fileinfo = $attachment_state->getAttachmentFile($this->dialogAttachments, $this->attachNum[0]); |
||
| 455 | |||
| 456 | // Check if the file still exists |
||
| 457 | if (is_file($tmpname)) { |
||
| 458 | // Set the headers |
||
| 459 | header('Pragma: public'); |
||
| 460 | header('Expires: 0'); // set expiration time |
||
| 461 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); |
||
| 462 | header('Content-Disposition: ' . $this->contentDispositionType . '; filename="' . addslashes(browserDependingHTTPHeaderEncode($fileinfo['name'])) . '"'); |
||
| 463 | header('Content-Transfer-Encoding: binary'); |
||
| 464 | header('Content-Type: application/octet-stream'); |
||
| 465 | header('Content-Length: ' . filesize($tmpname)); |
||
| 466 | |||
| 467 | // Open the uploaded file and print it |
||
| 468 | $file = fopen($tmpname, 'r'); |
||
| 469 | fpassthru($file); |
||
| 470 | fclose($file); |
||
| 471 | } else if($fileinfo['sourcetype'] === 'icsfile') { |
||
| 472 | // When "Send to" option used with calendar item. which create the new mail with |
||
| 473 | // ics file as an attachment and now user try to download the ics attachment before saving |
||
| 474 | // mail at that time this code is used to download the ics file successfully. |
||
| 475 | $messageStore = $GLOBALS['mapisession']->openMessageStore(hex2bin($fileinfo['store_entryid'])); |
||
| 476 | $message = mapi_msgstore_openentry($messageStore , hex2bin($fileinfo['entryid'])); |
||
| 477 | |||
| 478 | // Get address book for current session |
||
| 479 | $addrBook = $GLOBALS['mapisession']->getAddressbook(); |
||
| 480 | |||
| 481 | // get message properties. |
||
| 482 | $messageProps = mapi_getprops($message, array(PR_SUBJECT)); |
||
| 483 | |||
| 484 | // Read the appointment as RFC2445-formatted ics stream. |
||
| 485 | $appointmentStream = mapi_mapitoical($GLOBALS['mapisession']->getSession(), $addrBook, $message, array()); |
||
| 486 | |||
| 487 | $filename = (!empty($messageProps[PR_SUBJECT])) ? $messageProps[PR_SUBJECT] : _('Untitled'); |
||
| 488 | $filename .= '.ics'; |
||
| 489 | // Set the headers |
||
| 490 | header('Pragma: public'); |
||
| 491 | header('Expires: 0'); // set expiration time |
||
| 492 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); |
||
| 493 | header('Content-Transfer-Encoding: binary'); |
||
| 494 | |||
| 495 | // Set Content Disposition header |
||
| 496 | header('Content-Disposition: ' . $this->contentDispositionType . '; filename="' . addslashes(browserDependingHTTPHeaderEncode($filename)) . '"'); |
||
| 497 | // Set content type header |
||
| 498 | header('Content-Type: application/octet-stream'); |
||
| 499 | |||
| 500 | // Set the file length |
||
| 501 | header('Content-Length: ' . strlen($appointmentStream)); |
||
| 502 | |||
| 503 | $split = str_split($appointmentStream, BLOCK_SIZE); |
||
| 504 | foreach ($split as $s) echo $s; |
||
| 505 | } |
||
| 506 | $attachment_state->close(); |
||
| 507 | } |
||
| 508 | |||
| 509 | /** |
||
| 510 | * Function will send all the attachments to client side wrapped in a ZIP file. |
||
| 511 | * This should only be used to download all the attachments that are recently uploaded and not saved in MAPIMessage. |
||
| 512 | * @param AttachmentState $attachment_state Object of AttachmentState class. |
||
| 513 | * @param ZipArchive $zip ZipArchive object. |
||
| 514 | */ |
||
| 515 | public function addUnsavedAttachmentsToZipArchive($attachment_state, $zip) |
||
| 516 | { |
||
| 517 | //Get recently uploaded attachment files |
||
| 518 | $attachmentFiles = $attachment_state->getAttachmentFiles($this->dialogAttachments); |
||
| 519 | |||
| 520 | foreach ($attachmentFiles as $fileName => $fileInfo) { |
||
| 521 | $filePath = $attachment_state->getAttachmentPath($fileName); |
||
| 522 | // Avoid including contact photo and embedded messages in ZIP |
||
| 523 | if ($fileInfo['sourcetype'] !== 'embedded' && $fileInfo['sourcetype'] !== 'contactphoto') { |
||
| 524 | $fileDownloadName = $this->handleDuplicateFileNames( $fileInfo['name']); |
||
| 525 | $zip->addFile($filePath, $fileDownloadName); |
||
| 526 | } |
||
| 527 | } |
||
| 528 | } |
||
| 529 | |||
| 530 | /** |
||
| 531 | * Function will get the attachement and import it to the given MAPIFolder as webapp item. |
||
| 532 | */ |
||
| 533 | public function importAttachment() |
||
| 534 | { |
||
| 535 | $addrBook = $GLOBALS['mapisession']->getAddressbook(); |
||
| 536 | |||
| 537 | $newMessage = mapi_folder_createmessage($this->destinationFolder); |
||
| 538 | $attachment = $this->getAttachmentByAttachNum(); |
||
| 539 | $attachmentProps = mapi_attach_getprops($attachment, array(PR_ATTACH_LONG_FILENAME)); |
||
| 540 | $attachmentStream = streamProperty($attachment, PR_ATTACH_DATA_BIN); |
||
| 541 | |||
| 542 | switch(pathinfo($attachmentProps[PR_ATTACH_LONG_FILENAME], PATHINFO_EXTENSION)) |
||
| 543 | { |
||
| 544 | case 'eml': |
||
| 545 | if (isBrokenEml($attachmentStream)) { |
||
| 546 | throw new ZarafaException(_("Eml is corrupted")); |
||
| 547 | } else { |
||
| 548 | try { |
||
| 549 | // Convert an RFC822-formatted e-mail to a MAPI Message |
||
| 550 | $ok = mapi_inetmapi_imtomapi($GLOBALS['mapisession']->getSession(), $this->store, $addrBook, $newMessage, $attachmentStream, array()); |
||
| 551 | } catch(Exception $e) { |
||
| 552 | throw new ZarafaException(_("The eml Attachment is not imported successfully")); |
||
| 553 | } |
||
| 554 | } |
||
| 555 | break; |
||
| 556 | |||
| 557 | case 'vcf': |
||
| 558 | try { |
||
| 559 | // Convert an RFC6350-formatted vCard to a MAPI Contact |
||
| 560 | $ok = mapi_vcftomapi($GLOBALS['mapisession']->getSession(), $this->store, $newMessage, $attachmentStream); |
||
| 561 | } catch(Exception $e) { |
||
| 562 | throw new ZarafaException(_("The vcf attachment is not imported successfully")); |
||
| 563 | } |
||
| 564 | break; |
||
| 565 | case 'vcs': |
||
| 566 | case 'ics': |
||
| 567 | try { |
||
| 568 | // Convert vCalendar 1.0 or iCalendar to a MAPI Appointment |
||
| 569 | $ok = mapi_icaltomapi($GLOBALS['mapisession']->getSession(), $this->store, $addrBook, $newMessage, $attachmentStream, false); |
||
| 570 | } catch(Exception $e) { |
||
| 571 | $destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_DISPLAY_NAME, PR_MDB_PROVIDER)); |
||
| 572 | $fullyQualifiedFolderName = $destinationFolderProps[PR_DISPLAY_NAME]; |
||
| 573 | if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) { |
||
| 574 | $publicStore = $GLOBALS["mapisession"]->getPublicMessageStore(); |
||
| 575 | $publicStoreName = mapi_getprops($publicStore, array(PR_DISPLAY_NAME)); |
||
| 576 | $fullyQualifiedFolderName .= " - " . $publicStoreName[PR_DISPLAY_NAME]; |
||
| 577 | } else if ($destinationFolderProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) { |
||
| 578 | $sharedStoreOwnerName = mapi_getprops($this->otherStore, array(PR_MAILBOX_OWNER_NAME)); |
||
| 579 | $fullyQualifiedFolderName .= " - " . $sharedStoreOwnerName[PR_MAILBOX_OWNER_NAME]; |
||
| 580 | } |
||
| 581 | |||
| 582 | $message = sprintf(_("Unable to import '%s' to '%s'. "), $attachmentProps[PR_ATTACH_LONG_FILENAME], $fullyQualifiedFolderName); |
||
| 583 | if ($e->getCode() === MAPI_E_TABLE_EMPTY) { |
||
| 584 | $message .= _("There is no appointment found in this file."); |
||
| 585 | } else if ($e->getCode() === MAPI_E_CORRUPT_DATA) { |
||
| 586 | $message .= _("The file is corrupt."); |
||
| 587 | } else if ($e->getCode() === MAPI_E_INVALID_PARAMETER) { |
||
| 588 | $message .= _("The file is invalid."); |
||
| 589 | } else { |
||
| 590 | $message = sprintf(_("Unable to import '%s'. "), $attachmentProps[PR_ATTACH_LONG_FILENAME]) . _("Please contact your system administrator if the problem persists."); |
||
| 591 | } |
||
| 592 | |||
| 593 | $e = new ZarafaException($message); |
||
| 594 | $e->setTitle(_("Import error")); |
||
| 595 | throw $e; |
||
| 596 | } |
||
| 597 | break; |
||
| 598 | } |
||
| 599 | |||
| 600 | if($ok === true) { |
||
| 601 | mapi_savechanges($newMessage); |
||
| 602 | |||
| 603 | // Check that record is not appointment record. we have to only convert the |
||
| 604 | // Meeting request record to appointment record. |
||
| 605 | $newMessageProps = mapi_getprops($newMessage, array(PR_MESSAGE_CLASS)); |
||
| 606 | if (isset($newMessageProps[PR_MESSAGE_CLASS]) && $newMessageProps[PR_MESSAGE_CLASS] !== 'IPM.Appointment') { |
||
| 607 | // Convert the Meeting request record to proper appointment record so we can |
||
| 608 | // properly show the appointment in calendar. |
||
| 609 | $req = new Meetingrequest($this->store, $newMessage, $GLOBALS['mapisession']->getSession(), ENABLE_DIRECT_BOOKING); |
||
| 610 | $req->doAccept(true, false, false, false,false,false, false, false,false, true); |
||
| 611 | } |
||
| 612 | $storeProps = mapi_getprops($this->store, array(PR_ENTRYID)); |
||
| 613 | $destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_PARENT_ENTRYID, PR_CONTENT_UNREAD)); |
||
| 614 | |||
| 615 | $return = Array( |
||
| 616 | // 'success' property is needed for Extjs Ext.form.Action.Submit#success handler |
||
| 617 | 'success' => true, |
||
| 618 | 'zarafa' => Array( |
||
| 619 | sanitizeGetValue('module', '', STRING_REGEX) => Array( |
||
| 620 | sanitizeGetValue('moduleid', '', STRING_REGEX) => Array( |
||
| 621 | 'update' => Array( |
||
| 622 | 'success'=> true |
||
| 623 | ) |
||
| 624 | ) |
||
| 625 | ) |
||
| 626 | ) |
||
| 627 | ); |
||
| 628 | |||
| 629 | // send hierarchy notification only in case of 'eml' |
||
| 630 | if (pathinfo($attachmentProps[PR_ATTACH_LONG_FILENAME], PATHINFO_EXTENSION) === 'eml') { |
||
| 631 | $hierarchynotifier = Array( |
||
| 632 | 'hierarchynotifier1' => Array( |
||
| 633 | 'folders' => Array( |
||
| 634 | 'item' => Array( |
||
| 635 | 0 => Array( |
||
| 636 | 'entryid' => $this->destinationFolderId, |
||
| 637 | 'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]), |
||
| 638 | 'store_entryid' => bin2hex($storeProps[PR_ENTRYID]), |
||
| 639 | 'props' => Array( |
||
| 640 | 'content_unread' => $destinationFolderProps[PR_CONTENT_UNREAD] + 1 |
||
| 641 | ) |
||
| 642 | ) |
||
| 643 | ) |
||
| 644 | ) |
||
| 645 | ) |
||
| 646 | ); |
||
| 647 | |||
| 648 | $return['zarafa']['hierarchynotifier'] = $hierarchynotifier; |
||
| 649 | } |
||
| 650 | |||
| 651 | echo json_encode($return); |
||
| 652 | } else { |
||
| 653 | throw new ZarafaException(_("Attachment is not imported successfully")); |
||
| 654 | } |
||
| 655 | } |
||
| 656 | |||
| 657 | /** |
||
| 658 | * Function will get the embedded attachment and import it to the given MAPIFolder as webapp item. |
||
| 659 | */ |
||
| 660 | public function importEmbeddedAttachment() |
||
| 661 | { |
||
| 662 | // get message props of sub message |
||
| 663 | $copyFromMessage = $GLOBALS['operations']->openMessage($this->store, hex2bin($this->entryId), $this->attachNum, true); |
||
| 664 | |||
| 665 | if(empty($copyFromMessage)) { |
||
| 666 | throw new ZarafaException(_("Embedded attachment not found.")); |
||
| 667 | } |
||
| 668 | |||
| 669 | $newMessage = mapi_folder_createmessage($this->destinationFolder); |
||
| 670 | |||
| 671 | // Copy the entire message |
||
| 672 | mapi_copyto($copyFromMessage, array(), array(), $newMessage); |
||
| 673 | mapi_savechanges($newMessage); |
||
| 674 | |||
| 675 | $storeProps = mapi_getprops($this->store, array(PR_ENTRYID)); |
||
| 676 | $destinationFolderProps = mapi_getprops($this->destinationFolder, array(PR_PARENT_ENTRYID, PR_CONTENT_UNREAD)); |
||
| 677 | $return = Array( |
||
| 678 | // 'success' property is needed for Extjs Ext.form.Action.Submit#success handler |
||
| 679 | 'success' => true, |
||
| 680 | 'zarafa' => Array( |
||
| 681 | sanitizeGetValue('module', '', STRING_REGEX) => Array( |
||
| 682 | sanitizeGetValue('moduleid', '', STRING_REGEX) => Array( |
||
| 683 | 'update' => Array( |
||
| 684 | 'success'=> true |
||
| 685 | ) |
||
| 686 | ) |
||
| 687 | ), |
||
| 688 | 'hierarchynotifier' => Array( |
||
| 689 | 'hierarchynotifier1' => Array( |
||
| 690 | 'folders' => Array( |
||
| 691 | 'item' => Array( |
||
| 692 | 0 => Array( |
||
| 693 | 'entryid' => $this->destinationFolderId, |
||
| 694 | 'parent_entryid' => bin2hex($destinationFolderProps[PR_PARENT_ENTRYID]), |
||
| 695 | 'store_entryid' => bin2hex($storeProps[PR_ENTRYID]), |
||
| 696 | 'props' => Array( |
||
| 697 | 'content_unread' => $destinationFolderProps[PR_CONTENT_UNREAD] + 1 |
||
| 698 | ) |
||
| 699 | ) |
||
| 700 | ) |
||
| 701 | ) |
||
| 702 | ) |
||
| 703 | ) |
||
| 704 | ) |
||
| 705 | ); |
||
| 706 | |||
| 707 | echo json_encode($return); |
||
| 708 | } |
||
| 709 | |||
| 710 | /** |
||
| 711 | * Check if the attached eml is corrupted or not |
||
| 712 | * @param String $attachment Content fetched from PR_ATTACH_DATA_BIN property of an attachment. |
||
| 713 | * @return True if eml is broken, false otherwise. |
||
| 714 | */ |
||
| 715 | public function isBroken($attachment) |
||
| 716 | { |
||
| 717 | // Get header part to process further |
||
| 718 | $splittedContent = preg_split("/\r?\n\r?\n/", $attachment); |
||
| 719 | |||
| 720 | // Fetch raw header |
||
| 721 | if (preg_match_all('/([^:]+): ?.*\n/', $splittedContent[0], $matches)) { |
||
| 722 | $rawHeaders = $matches[1]; |
||
| 723 | } |
||
| 724 | |||
| 725 | // Compare if necessary headers are present or not |
||
| 726 | if (isset($rawHeaders) && in_array('From', $rawHeaders) && in_array('Date', $rawHeaders)) { |
||
| 727 | return false; |
||
| 728 | } |
||
| 729 | |||
| 730 | return true; |
||
| 731 | } |
||
| 732 | |||
| 733 | /** |
||
| 734 | * Generic function to check passed data and decide which type of attachment is requested. |
||
| 735 | */ |
||
| 736 | public function download() |
||
| 737 | { |
||
| 738 | $attachment = false; |
||
| 739 | |||
| 740 | // Check if all attachments are requested to be downloaded as ZIP |
||
| 741 | if ($this->allAsZip) { |
||
| 742 | $attachment_state = new AttachmentState(); |
||
| 743 | $attachment_state->open(); |
||
| 744 | |||
| 745 | // Generate random ZIP file name at default temporary path of PHP |
||
| 746 | $randomZipName = tempnam(sys_get_temp_dir(), 'zip'); |
||
| 747 | |||
| 748 | // Create an open zip archive. |
||
| 749 | $zip = new ZipArchive(); |
||
| 750 | $result = $zip->open($randomZipName, ZipArchive::CREATE); |
||
| 751 | |||
| 752 | if ($result === TRUE) { |
||
| 753 | // Check if attachments are of saved message. |
||
| 754 | // Only saved message has the entryid configured. |
||
| 755 | if($this->entryId) { |
||
| 756 | // Check if the requested attachment(s) are of an embedded message |
||
| 757 | if($this->isSubMessage){ |
||
| 758 | // Loop through the attachNums, message in message in message ... |
||
| 759 | for($index = 0, $len = count($this->attachNum); $index < $len - 1; $index++) { |
||
| 760 | // Open the attachment |
||
| 761 | $tempattach = mapi_message_openattach($this->message, $this->attachNum[$index]); |
||
| 762 | if($tempattach) { |
||
| 763 | // Open the object in the attachment |
||
| 764 | $this->message = mapi_attach_openobj($tempattach); |
||
| 765 | } |
||
| 766 | } |
||
| 767 | } |
||
| 768 | $this->addAttachmentsToZipArchive($attachment_state, $zip); |
||
| 769 | } else { |
||
| 770 | $this->addUnsavedAttachmentsToZipArchive($attachment_state, $zip); |
||
| 771 | } |
||
| 772 | } else { |
||
| 773 | // Throw exception if ZIP is not created successfully |
||
| 774 | throw new ZarafaException(_("ZIP is not created successfully")); |
||
| 775 | } |
||
| 776 | |||
| 777 | $zip->close(); |
||
| 778 | |||
| 779 | $this->sendZipResponse($randomZipName); |
||
| 780 | $attachment_state->close(); |
||
| 781 | // check if inline image is requested |
||
| 782 | } else if($this->attachCid) { |
||
| 783 | // check if the inline image is in a embedded message |
||
| 784 | if(count($this->attachNum) > 0) { |
||
| 785 | // get the embedded message attachment |
||
| 786 | $attachment = $this->getAttachmentByAttachNum(); |
||
| 787 | } |
||
| 788 | |||
| 789 | // now get the actual attachment object that should be sent back to client |
||
| 790 | $attachment = $this->getAttachmentByAttachCid($attachment); |
||
| 791 | |||
| 792 | // no need to return anything here function will echo all the output |
||
| 793 | $this->downloadSavedAttachment($attachment, true); |
||
| 794 | |||
| 795 | } else if(count($this->attachNum) > 0) { |
||
| 796 | // check if the attachment needs to be imported |
||
| 797 | if ($this->import) { |
||
| 798 | if ($this->isEmbedded) { |
||
| 799 | $this->importEmbeddedAttachment(); |
||
| 800 | } else { |
||
| 801 | $this->importAttachment(); |
||
| 802 | } |
||
| 803 | return; |
||
| 804 | } |
||
| 805 | |||
| 806 | // check if temporary unsaved attachment is requested |
||
| 807 | if(is_string($this->attachNum[0])) { |
||
| 808 | $this->downloadUnsavedAttachment(); |
||
| 809 | } else { |
||
| 810 | // normal saved attachment is requested, so get it |
||
| 811 | $attachment = $this->getAttachmentByAttachNum(); |
||
| 812 | |||
| 813 | if($attachment === false) { |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 814 | // something terrible happened and we can't continue |
||
| 815 | return; |
||
| 816 | } |
||
| 817 | |||
| 818 | // no need to return anything here function will echo all the output |
||
| 819 | $this->downloadSavedAttachment($attachment); |
||
| 820 | } |
||
| 821 | } else { |
||
| 822 | throw new ZarafaException(_("Attachments can not be downloaded")); |
||
| 823 | } |
||
| 824 | } |
||
| 825 | |||
| 826 | /** |
||
| 827 | * Function will encode all the necessary information about the exception |
||
| 828 | * into JSON format and send the response back to client. |
||
| 829 | * |
||
| 830 | * @param object $exception Exception object. |
||
| 831 | */ |
||
| 832 | function handleSaveMessageException($exception) |
||
| 833 | { |
||
| 834 | $return = array(); |
||
| 835 | |||
| 836 | // MAPI_E_NOT_FOUND exception contains generalize exception message. |
||
| 837 | // Set proper exception message as display message should be user understandable. |
||
| 838 | if($exception->getCode() == MAPI_E_NOT_FOUND) { |
||
| 839 | $exception->setDisplayMessage(_('Could not find attachment.')); |
||
| 840 | } |
||
| 841 | |||
| 842 | // Set the headers |
||
| 843 | header('Expires: 0'); // set expiration time |
||
| 844 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); |
||
| 845 | |||
| 846 | // Set Content Disposition header |
||
| 847 | header('Content-Disposition: inline'); |
||
| 848 | // Set content type header |
||
| 849 | header('Content-Type: text/plain'); |
||
| 850 | |||
| 851 | //prepare exception response according to exception class |
||
| 852 | if($exception instanceof MAPIException) { |
||
| 853 | $return = array( |
||
| 854 | 'success' => false, |
||
| 855 | 'zarafa' => array( |
||
| 856 | 'error' => array( |
||
| 857 | 'type' => ERROR_MAPI, |
||
| 858 | 'info' => array( |
||
| 859 | 'hresult' => $exception->getCode(), |
||
| 860 | 'hresult_name' => get_mapi_error_name($exception->getCode()), |
||
| 861 | 'file' => $exception->getFileLine(), |
||
| 862 | 'display_message' => $exception->getDisplayMessage() |
||
| 863 | ) |
||
| 864 | ) |
||
| 865 | ) |
||
| 866 | ); |
||
| 867 | } else if($exception instanceof ZarafaException) { |
||
| 868 | $return = array( |
||
| 869 | 'success' => false, |
||
| 870 | 'zarafa' => array( |
||
| 871 | 'error' => array( |
||
| 872 | 'type' => ERROR_ZARAFA, |
||
| 873 | 'info' => array( |
||
| 874 | 'file' => $exception->getFileLine(), |
||
| 875 | 'display_message' => $exception->getDisplayMessage(), |
||
| 876 | 'original_message' => $exception->getMessage() |
||
| 877 | ) |
||
| 878 | ) |
||
| 879 | ) |
||
| 880 | ); |
||
| 881 | } else if($exception instanceof BaseException) { |
||
| 882 | $return = array( |
||
| 883 | 'success' => false, |
||
| 884 | 'zarafa' => array( |
||
| 885 | 'error' => array( |
||
| 886 | 'type' => ERROR_GENERAL, |
||
| 887 | 'info' => array( |
||
| 888 | 'file' => $exception->getFileLine(), |
||
| 889 | 'display_message' => $exception->getDisplayMessage(), |
||
| 890 | 'original_message' => $exception->getMessage() |
||
| 891 | ) |
||
| 892 | ) |
||
| 893 | ) |
||
| 894 | ); |
||
| 895 | } else { |
||
| 896 | $return = array( |
||
| 897 | 'success' => false, |
||
| 898 | 'zarafa' => array( |
||
| 899 | 'error' => array( |
||
| 900 | 'type' => ERROR_GENERAL, |
||
| 901 | 'info' => array( |
||
| 902 | 'display_message' => _('Operation failed'), |
||
| 903 | 'original_message' => $exception->getMessage() |
||
| 904 | ) |
||
| 905 | ) |
||
| 906 | ) |
||
| 907 | ); |
||
| 908 | } |
||
| 909 | echo json_encode($return); |
||
| 910 | } |
||
| 911 | } |
||
| 912 | |||
| 913 | // create instance of class to download attachment |
||
| 914 | $attachInstance = new DownloadAttachment(); |
||
| 915 | |||
| 916 | try{ |
||
| 917 | // initialize variables |
||
| 918 | $attachInstance->init($_GET); |
||
| 919 | |||
| 920 | // download attachment |
||
| 921 | $attachInstance->download(); |
||
| 922 | } catch (Exception $e) { |
||
| 923 | $attachInstance->handleSaveMessageException($e); |
||
| 924 | } |
||
| 925 | ?> |
||
|
0 ignored issues
–
show
It is not recommended to use PHP's closing tag
?> in files other than templates.
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore. A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever. Loading history...
|
|||
| 926 |