1<?php 2/** 3 * EGroupware - Mail - interface class for compose mails in popup 4 * 5 * @link http://www.egroupware.org 6 * @package mail 7 * @author EGroupware GmbH [info@egroupware.org] 8 * @copyright (c) 2013-2016 by EGroupware GmbH <info-AT-egroupware.org> 9 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License 10 * @version $Id$ 11 */ 12 13use EGroupware\Api; 14use EGroupware\Api\Acl; 15use EGroupware\Api\Egw; 16use EGroupware\Api\Etemplate; 17use EGroupware\Api\Framework; 18use EGroupware\Api\Link; 19use EGroupware\Api\Mail; 20use EGroupware\Api\Vfs; 21 22/** 23 * Mail interface class for compose mails in popup 24 */ 25class mail_compose 26{ 27 var $public_functions = array 28 ( 29 'compose' => True, 30 'getAttachment' => True, 31 ); 32 33 /** 34 * class vars for destination, priorities, mimeTypes 35 */ 36 static $destinations = array( 37 'to' => 'to', // lang('to') 38 'cc' => 'cc', // lang('cc') 39 'bcc' => 'bcc', // lang('bcc') 40 'replyto' => 'replyto', // lang('replyto') 41 'folder' => 'folder' // lang('folder') 42 ); 43 static $priorities = array( 44 1=>"high", // lang('high') 45 3=>"normal", // lang('normal') 46 5=>"low" // lang('low') 47 ); 48 static $mimeTypes = array( 49 "plain"=>"plain", 50 "html"=>"html" 51 ); 52 53 /** 54 * Instance of Mail 55 * 56 * @var Mail 57 */ 58 var $mail_bo; 59 60 /** 61 * Active preferences, reference to $this->mail_bo->mailPreferences 62 * 63 * @var array 64 */ 65 var $mailPreferences; 66 var $attachments; // Array of attachments 67 var $displayCharset; 68 var $composeID; 69 var $sessionData; 70 71 function __construct() 72 { 73 $this->displayCharset = Api\Translation::charset(); 74 75 $profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']; 76 $this->mail_bo = Mail::getInstance(true,$profileID); 77 $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->mail_bo->profileID; 78 79 $this->mailPreferences =& $this->mail_bo->mailPreferences; 80 //force the default for the forwarding -> asmail 81 if (!is_array($this->mailPreferences) || empty($this->mailPreferences['message_forwarding'])) 82 { 83 $this->mailPreferences['message_forwarding'] = 'asmail'; 84 } 85 if (is_null(Mail::$mailConfig)) Mail::$mailConfig = Api\Config::read('mail'); 86 87 $this->mailPreferences =& $this->mail_bo->mailPreferences; 88 } 89 90 /** 91 * changeProfile 92 * 93 * @param int $_icServerID 94 */ 95 function changeProfile($_icServerID) 96 { 97 if ($this->mail_bo->profileID!=$_icServerID) 98 { 99 if (Mail::$debug) error_log(__METHOD__.__LINE__.'->'.$this->mail_bo->profileID.'<->'.$_icServerID); 100 $this->mail_bo = Mail::getInstance(false,$_icServerID); 101 if (Mail::$debug) error_log(__METHOD__.__LINE__.' Fetched IC Server:'.$this->mail_bo->profileID.':'.function_backtrace()); 102 // no icServer Object: something failed big time 103 if (!isset($this->mail_bo->icServer)) exit; // ToDo: Exception or the dialog for setting up a server config 104 $this->mail_bo->openConnection($this->mail_bo->profileID); 105 $this->mailPreferences =& $this->mail_bo->mailPreferences; 106 } 107 } 108 109 /** 110 * Provide toolbar actions used for compose toolbar 111 * @param array $content content of compose temp 112 * 113 * @return array an array of actions 114 */ 115 static function getToolbarActions($content) 116 { 117 $group = 0; 118 $actions = array( 119 'send' => array( 120 'caption' => 'Send', 121 'icon' => 'mail_send', 122 'group' => ++$group, 123 'onExecute' => 'javaScript:app.mail.compose_submitAction', 124 'hint' => 'Send', 125 'shortcut' => array('ctrl' => true, 'keyCode' => 83, 'caption' => 'Ctrl + S'), 126 'toolbarDefault' => true 127 ), 128 'button[saveAsDraft]' => array( 129 'caption' => 'Save', 130 'icon' => 'apply', 131 'group' => ++$group, 132 'onExecute' => 'javaScript:app.mail.saveAsDraft', 133 'hint' => 'Save as Draft', 134 'toolbarDefault' => true 135 ), 136 'button[saveAsDraftAndPrint]' => array( 137 'caption' => 'Print', 138 'icon' => 'print', 139 'group' => $group, 140 'onExecute' => 'javaScript:app.mail.saveAsDraft', 141 'hint' => 'Save as Draft and Print' 142 ), 143 'save2vfs' => array ( 144 'caption' => 'Save to filemanager', 145 'icon' => 'filesave', 146 'group' => $group, 147 'onExecute' => 'javaScript:app.mail.compose_saveDraft2fm', 148 'hint' => 'Save the drafted message as eml file into VFS' 149 ), 150 'selectFromVFSForCompose' => array( 151 'caption' => 'VFS', 152 'icon' => 'filemanager/navbar', 153 'group' => ++$group, 154 'onExecute' => 'javaScript:app.mail.compose_triggerWidget', 155 'hint' => 'Select file(s) from VFS', 156 'toolbarDefault' => true 157 ), 158 'uploadForCompose' => array( 159 'caption' => 'Upload files...', 160 'icon' => 'attach', 161 'group' => $group, 162 'onExecute' => 'javaScript:app.mail.compose_triggerWidget', 163 'hint' => 'Select files to upload', 164 'toolbarDefault' => true 165 ), 166 'to_infolog' => array( 167 'caption' => 'Infolog', 168 'icon' => 'infolog/navbar', 169 'group' => ++$group, 170 'checkbox' => true, 171 'hint' => 'check to save as infolog on send', 172 'toolbarDefault' => true, 173 'onExecute' => 'javaScript:app.mail.compose_setToggle' 174 ), 175 'to_tracker' => array( 176 'caption' => 'Tracker', 177 'icon' => 'tracker/navbar', 178 'group' => $group, 179 'checkbox' => true, 180 'hint' => 'check to save as tracker entry on send', 181 'onExecute' => 'javaScript:app.mail.compose_setToggle', 182 'mail_import' => Api\Hooks::single(array('location' => 'mail_import'),'tracker'), 183 ), 184 'to_calendar' => array( 185 'caption' => 'Calendar', 186 'icon' => 'calendar/navbar', 187 'group' => $group, 188 'checkbox' => true, 189 'hint' => 'check to save as calendar event on send', 190 'onExecute' => 'javaScript:app.mail.compose_setToggle' 191 ), 192 'disposition' => array( 193 'caption' => 'Notification', 194 'icon' => 'notification_message', 195 'group' => ++$group, 196 'checkbox' => true, 197 'hint' => 'check to receive a notification when the message is read (note: not all clients support this and/or the receiver may not authorize the notification)', 198 'onExecute' => 'javaScript:app.mail.compose_setToggle' 199 ), 200 'prty' => array( 201 'caption' => 'Priority', 202 'group' => $group, 203 'icon' => 'priority', 204 'children' => array(), 205 'hint' => 'Select the message priority tag', 206 ), 207 'pgp' => array( 208 'caption' => 'Encrypt', 209 'icon' => 'lock', 210 'group' => ++$group, 211 'onExecute' => 'javaScript:app.mail.togglePgpEncrypt', 212 'hint' => 'Send message PGP encrypted: requires keys from all recipients!', 213 'checkbox' => true, 214 'toolbarDefault' => true 215 ), 216 217 ); 218 $acc_smime = Mail\Smime::get_acc_smime($content['mailaccount']); 219 if ($acc_smime['acc_smime_password']) 220 { 221 $actions = array_merge($actions, array( 222 'smime_sign' => array ( 223 'caption' => 'SMIME Sign', 224 'icon' => 'smime_sign', 225 'group' => ++$group, 226 'onExecute' => 'javaScript:app.mail.compose_setToggle', 227 'checkbox' => true, 228 'hint' => 'Sign your message with smime certificate' 229 ), 230 'smime_encrypt' => array ( 231 'caption' => 'SMIME Encryption', 232 'icon' => 'smime_encrypt', 233 'group' => $group, 234 'onExecute' => 'javaScript:app.mail.compose_setToggle', 235 'checkbox' => true, 236 'hint' => 'Encrypt your message with smime certificate' 237 ))); 238 } 239 foreach (self::$priorities as $key => $priority) 240 { 241 $actions['prty']['children'][$key] = array( 242 'caption' => $priority, 243 'icon' => 'prio_high', 244 'default' => false, 245 'onExecute' => 'javaScript:app.mail.compose_priorityChange' 246 ); 247 switch ($priority) 248 { 249 case 'high': 250 $actions['prty']['children'][$key]['icon'] = 'prio_high'; 251 break; 252 case 'normal': 253 $actions['prty']['children'][$key]['icon'] = 'priority'; 254 break; 255 case 'low': 256 $actions['prty']['children'][$key]['icon'] = 'prio_low'; 257 } 258 } 259 // Set the priority action its current state 260 if ($content['priority']) 261 { 262 $actions['prty']['children'][$content['priority']]['default'] = true; 263 } 264 if (Api\Header\UserAgent::mobile()) 265 { 266 foreach (array_keys($actions) as $key) 267 { 268 if (!in_array($key, array('send','button[saveAsDraft]','uploadForCompose' ))) { 269 $actions[$key]['toolbarDefault'] = false; 270 } 271 } 272 unset($actions['pgp']); 273 } 274 if ($GLOBALS['egw_info']['server']['disable_pgp_encryption']) unset($actions['pgp']); 275 // remove vfs actions if the user has no run access to filemanager 276 if (!$GLOBALS['egw_info']['user']['apps']['filemanager']) 277 { 278 unset($actions['save2vfs']); 279 unset($actions['selectFromVFSForCompose']); 280 } 281 if (!isset($GLOBALS['egw_info']['user']['apps']['infolog'])) 282 { 283 unset($actions['to_infolog']); 284 } 285 if (!isset($GLOBALS['egw_info']['user']['apps']['tracker'])) 286 { 287 unset($actions['to_tracker']); 288 } 289 if (!isset($GLOBALS['egw_info']['user']['apps']['calendar'])) 290 { 291 unset($actions['to_calendar']); 292 } 293 return $actions; 294 } 295 296 /** 297 * Compose dialog 298 * 299 * @var array $_content =null etemplate content array 300 * @var string $msg =null a possible message to be passed and displayed to the userinterface 301 * @var string $_focusElement ='to' subject, to, body supported 302 * @var boolean $suppressSigOnTop =false 303 * @var boolean $isReply =false 304 */ 305 function compose(array $_content=null,$msg=null, $_focusElement='to',$suppressSigOnTop=false, $isReply=false) 306 { 307 if ($msg) Framework::message($msg); 308 309 if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'])) 310 { 311 $sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']; 312 } 313 else 314 { 315 $sigPref = array(); 316 } 317 // split mailaccount (acc_id) and identity (ident_id) 318 if ($_content && isset($_content['mailaccount'])) 319 { 320 list($_content['mailaccount'], $_content['mailidentity']) = explode(':', $_content['mailaccount']); 321 } 322 //error_log(__METHOD__.__LINE__.array2string($sigPref)); 323 //lang('compose'),lang('from') // needed to be found by translationtools 324 //error_log(__METHOD__.__LINE__.array2string($_REQUEST).function_backtrace()); 325 //error_log(__METHOD__.__LINE__.array2string($_content).function_backtrace()); 326 $_contentHasSigID = $_content?array_key_exists('mailidentity',(array)$_content):false; 327 $_contentHasMimeType = $_content? array_key_exists('mimeType',(array)$_content):false; 328 329 // fetch appendix data which is an assistance input value consisiting of json data 330 if ($_content['appendix_data']) 331 { 332 $appendix_data = json_decode($_content['appendix_data'], true); 333 $_content['appendix_data'] = ''; 334 } 335 336 if ($appendix_data['emails']) 337 { 338 try { 339 if ($appendix_data['emails']['processedmail_id']) $_content['processedmail_id'] .= ','.$appendix_data['emails']['processedmail_id']; 340 $attched_uids = $this->_get_uids_as_attachments($appendix_data['emails']['ids'], $_content['serverID']); 341 if (is_array($attched_uids)) 342 { 343 $_content['attachments'] = array_merge_recursive((array)$_content['attachments'], $attched_uids); 344 } 345 } catch (Exception $ex) { 346 Framework::message($ex->getMessage(), 'error'); 347 } 348 $suppressSigOnTop = true; 349 unset($appendix_data); 350 } 351 352 if (isset($_GET['reply_id'])) $replyID = $_GET['reply_id']; 353 if (!$replyID && isset($_GET['id'])) $replyID = $_GET['id']; 354 355 // Process different places we can use as a start for composing an email 356 $actionToProcess = 'compose'; 357 if($_GET['from'] && $replyID) 358 { 359 $_content = array_merge((array)$_content, $this->getComposeFrom( 360 // Parameters needed for fetching appropriate data 361 $replyID, $_GET['part_id'], $_GET['from'], 362 // Additionally may be changed 363 $_focusElement, $suppressSigOnTop, $isReply 364 )); 365 if (Mail\Smime::get_acc_smime($this->mail_bo->profileID)) 366 { 367 if (isset($_GET['smime_type'])) $smime_type = $_GET['smime_type']; 368 // pre set smime_sign and smime_encrypt actions if the original 369 // message is smime. 370 $_content['smime_sign'] = $smime_type == (Mail\Smime::TYPE_SIGN || 371 $smime_type == Mail\Smime::TYPE_SIGN_ENCRYPT) ? 'on' : 'off'; 372 $_content['smime_encrypt'] = ($smime_type == Mail\Smime::TYPE_ENCRYPT) ? 'on' : 'off'; 373 } 374 375 $actionToProcess = $_GET['from']; 376 unset($_GET['from']); 377 unset($_GET['reply_id']); 378 unset($_GET['part_id']); 379 unset($_GET['id']); 380 unset($_GET['mode']); 381 //error_log(__METHOD__.__LINE__.array2string($_content)); 382 } 383 384 $composeCache = array(); 385 if (isset($_content['composeID'])&&!empty($_content['composeID'])) 386 { 387 $isFirstLoad = false; 388 $composeCache = Api\Cache::getCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$_content['composeID'],$callback=null,$callback_params=array(),$expiration=60*60*2); 389 $this->composeID = $_content['composeID']; 390 //error_log(__METHOD__.__LINE__.array2string($composeCache)); 391 } 392 else 393 { 394 // as we use isFirstLoad to trigger the initalStyle on ckEditor, we 395 // respect that composeasnew may not want that, as we assume there 396 // is some style already set and our initalStyle always adds a span with 397 // and we want to avoid that 398 $isFirstLoad = !($actionToProcess=='composeasnew');//true; 399 $this->composeID = $_content['composeID'] = $this->generateComposeID(); 400 if (!is_array($_content)) 401 { 402 $_content = $this->setDefaults(); 403 } 404 else 405 { 406 $_content = $this->setDefaults($_content); 407 } 408 } 409 // VFS Selector was used 410 if (is_array($_content['selectFromVFSForCompose'])) 411 { 412 $suppressSigOnTop = true; 413 foreach ($_content['selectFromVFSForCompose'] as $i => $path) 414 { 415 $_content['uploadForCompose'][] = array( 416 'name' => Vfs::basename($path), 417 'type' => Vfs::mime_content_type($path), 418 'file' => Vfs::PREFIX.$path, 419 'size' => filesize(Vfs::PREFIX.$path), 420 ); 421 } 422 unset($_content['selectFromVFSForCompose']); 423 } 424 // check everything that was uploaded 425 if (is_array($_content['uploadForCompose'])) 426 { 427 $suppressSigOnTop = true; 428 foreach ($_content['uploadForCompose'] as $i => &$upload) 429 { 430 if (!isset($upload['file'])) $upload['file'] = $upload['tmp_name']; 431 try 432 { 433 $upload['file'] = $upload['tmp_name'] = Mail::checkFileBasics($upload,$this->composeID,false); 434 } 435 catch (Api\Exception\WrongUserinput $e) 436 { 437 Framework::message($e->getMessage(), 'error'); 438 unset($_content['uploadForCompose'][$i]); 439 continue; 440 } 441 if (is_dir($upload['file']) && (!$_content['filemode'] || $_content['filemode'] == Vfs\Sharing::ATTACH)) 442 { 443 $_content['filemode'] = Vfs\Sharing::READONLY; 444 Framework::message(lang('Directories have to be shared.'), 'info'); 445 } 446 } 447 } 448 // check if someone did hit delete on the attachments list 449 if (!empty($_content['attachments']['delete'])) 450 { 451 //error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments'])); 452 //error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments']['delete'])); 453 454 $suppressSigOnTop = true; 455 $toDelete = $_content['attachments']['delete']; 456 unset($_content['attachments']['delete']); 457 $attachments = $_content['attachments']; 458 unset($_content['attachments']); 459 foreach($attachments as $i => $att) 460 { 461 $remove=false; 462 foreach(array_keys($toDelete) as $k) 463 { 464 if ($att['tmp_name']==$k) $remove=true; 465 } 466 if (!$remove) $_content['attachments'][] = $att; 467 } 468 } 469 // someone clicked something like send, or saveAsDraft 470 // make sure, we are connected to the correct server for sending and storing the send message 471 $activeProfile = $composeProfile = $this->mail_bo->profileID; // active profile may not be the profile uised in/for compose 472 $activeFolderCache = Api\Cache::getCache(Api\Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*10); 473 if (!empty($activeFolderCache[$this->mail_bo->profileID])) 474 { 475 //error_log(__METHOD__.__LINE__.' CurrentFolder:'.$activeFolderCache[$this->mail_bo->profileID]); 476 $activeFolder = $activeFolderCache[$this->mail_bo->profileID]; 477 } 478 //error_log(__METHOD__.__LINE__.array2string($_content)); 479 if (!empty($_content['serverID']) && $_content['serverID'] != $this->mail_bo->profileID && 480 ($_content['composeToolbar'] === 'send' || $_content['button']['saveAsDraft']||$_content['button']['saveAsDraftAndPrint']) 481 ) 482 { 483 $this->changeProfile($_content['serverID']); 484 $composeProfile = $this->mail_bo->profileID; 485 } 486 // make sure $acc is set/initalized properly with the current composeProfile, as $acc is used down there 487 // at several locations and not neccesaryly initialized before 488 $acc = Mail\Account::read($composeProfile); 489 $buttonClicked = false; 490 if ($_content['composeToolbar'] === 'send') 491 { 492 $buttonClicked = $suppressSigOnTop = true; 493 $sendOK = true; 494 $_content['body'] = ($_content['body'] ? $_content['body'] : $_content['mail_'.($_content['mimeType'] == 'html'?'html':'plain').'text']); 495 /* 496 perform some simple checks, before trying to send on: 497 $_content['to'];$_content['cc'];$_content['bcc']; 498 trim($_content['subject']); 499 trim(strip_tags(str_replace(' ','',$_content['body']))); 500 */ 501 if (strlen(trim(strip_tags(str_replace(' ','',$_content['body']))))==0 && count($_content['attachments'])==0) 502 { 503 $sendOK = false; 504 $_content['msg'] = $message = lang("no message body supplied"); 505 } 506 if ($sendOK && strlen(trim($_content['subject']))==0) 507 { 508 $sendOK = false; 509 $_content['msg'] = $message = lang("no subject supplied"); 510 } 511 if ($sendOK && empty($_content['to']) && empty($_content['cc']) && empty($_content['bcc'])) 512 { 513 $sendOK = false; 514 $_content['msg'] = $message = lang("no adress, to send this mail to, supplied"); 515 } 516 if ($sendOK) 517 { 518 try 519 { 520 $success = $this->send($_content); 521 522 //hook mail_compose_after_save 523 Api\Hooks::process( array( 524 'location' => 'mail_compose_after_save', 525 'content' => $_content, 526 )); 527 528 if ($success==false) 529 { 530 $sendOK=false; 531 $message = $this->errorInfo; 532 } 533 if (!empty($_content['mailidentity']) && $_content['mailidentity'] != $sigPref[$this->mail_bo->profileID]) 534 { 535 $sigPref[$this->mail_bo->profileID]=$_content['mailidentity']; 536 $GLOBALS['egw']->preferences->add('mail','LastSignatureIDUsed',$sigPref,'user'); 537 // save prefs 538 $GLOBALS['egw']->preferences->save_repository(true); 539 } 540 } 541 catch (Api\Exception\WrongUserinput $e) 542 { 543 $sendOK = false; 544 $message = $e->getMessage(); 545 } 546 } 547 if ($activeProfile != $composeProfile) 548 { 549 $this->changeProfile($activeProfile); 550 $activeProfile = $this->mail_bo->profileID; 551 } 552 if ($sendOK) 553 { 554 $workingFolder = $activeFolder; 555 $mode = 'compose'; 556 $idsForRefresh = array(); 557 if (isset($_content['mode']) && !empty($_content['mode'])) 558 { 559 $mode = $_content['mode']; 560 if ($_content['mode']=='forward' && !empty($_content['processedmail_id'])) 561 { 562 $_content['processedmail_id'] = explode(',',$_content['processedmail_id']); 563 foreach ($_content['processedmail_id'] as $k =>$rowid) 564 { 565 $fhA = mail_ui::splitRowID($rowid); 566 //$this->sessionData['uid'][] = $fhA['msgUID']; 567 //$this->sessionData['forwardedUID'][] = $fhA['msgUID']; 568 $idsForRefresh[] = mail_ui::generateRowID($fhA['profileID'], $fhA['folder'], $fhA['msgUID'], $_prependApp=false); 569 if (!empty($fhA['folder'])) $workingFolder = $fhA['folder']; 570 } 571 } 572 if ($_content['mode']=='reply' && !empty($_content['processedmail_id'])) 573 { 574 $rhA = mail_ui::splitRowID($_content['processedmail_id']); 575 //$this->sessionData['uid'] = $rhA['msgUID']; 576 $idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false); 577 $workingFolder = $rhA['folder']; 578 } 579 } 580 //the line/condition below should not be needed 581 if (empty($idsForRefresh) && !empty($_content['processedmail_id'])) 582 { 583 $rhA = mail_ui::splitRowID($_content['processedmail_id']); 584 $idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false); 585 $workingFolder = $rhA['folder']; // need folder to refresh eg. drafts folder 586 } 587 $response = Api\Json\Response::get(); 588 if ($activeProfile != $composeProfile) 589 { 590 // we need a message only, when account ids (composeProfile vs. activeProfile) differ 591 $response->call('opener.egw_message',lang('Message send successfully.')); 592 } 593 elseif ($activeProfile == $composeProfile && ($workingFolder==$activeFolder['mailbox'] && $mode != 'compose') || 594 ($this->mail_bo->isSentFolder($workingFolder) || $this->mail_bo->isDraftFolder($workingFolder))) 595 { 596 if ($this->mail_bo->isSentFolder($workingFolder)||$this->mail_bo->isDraftFolder($workingFolder)) 597 { 598 // we may need a refresh when on sent folder or in drafts, as drafted messages will/should be deleted after succeeded send action 599 $response->call('opener.egw_refresh',lang('Message send successfully.'),'mail'); 600 } 601 // we only need to update the icon of the replied or forwarded mails --> 'update-in-place' 602 else 603 { 604 //error_log(__METHOD__.__LINE__.array2string($idsForRefresh)); 605 $response->call('opener.egw_refresh',lang('Message send successfully.'),'mail',$idsForRefresh,'update-in-place'); 606 } 607 } 608 else 609 { 610 $response->call('opener.egw_message',lang('Message send successfully.')); 611 } 612 //egw_framework::refresh_opener(lang('Message send successfully.'),'mail'); 613 Framework::window_close(); 614 } 615 if ($sendOK == false) 616 { 617 $response = Api\Json\Response::get(); 618 Framework::message(lang('Message send failed: %1',$message),'error');// maybe error is more appropriate 619 $response->call('app.mail.clearIntevals'); 620 } 621 } 622 623 if ($activeProfile != $composeProfile) $this->changeProfile($activeProfile); 624 $insertSigOnTop = false; 625 $content = (is_array($_content)?$_content:array()); 626 if ($_contentHasMimeType) 627 { 628 // mimeType is now a checkbox; convert it here to match expectations 629 // ToDo: match Code to meet checkbox value 630 if ($content['mimeType']==1) 631 { 632 $_content['mimeType'] = $content['mimeType']='html'; 633 } 634 else 635 { 636 $_content['mimeType'] = $content['mimeType']='plain'; 637 } 638 639 } 640 // user might have switched desired mimetype, so we should convert 641 if ($content['is_html'] && $content['mimeType']=='plain') 642 { 643 //error_log(__METHOD__.__LINE__.$content['mail_htmltext']); 644 $suppressSigOnTop = true; 645 if (stripos($content['mail_htmltext'],'<pre>')!==false) 646 { 647 $contentArr = Api\Mail\Html::splithtmlByPRE($content['mail_htmltext']); 648 if (is_array($contentArr)) 649 { 650 foreach ($contentArr as $k =>&$elem) 651 { 652 if (stripos($elem,'<pre>')!==false) $elem = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$elem); 653 } 654 $content['mail_htmltext'] = implode('',$contentArr); 655 } 656 } 657 $content['mail_htmltext'] = $this->_getCleanHTML($content['mail_htmltext']); 658 $content['mail_htmltext'] = Api\Mail\Html::convertHTMLToText($content['mail_htmltext'],$charset=false,false,true); 659 660 $content['body'] = $content['mail_htmltext']; 661 unset($content['mail_htmltext']); 662 $content['is_html'] = false; 663 $content['is_plain'] = true; 664 } 665 if ($content['is_plain'] && $content['mimeType']=='html') 666 { 667 // the possible font span should only be applied on first load or on switch plain->html 668 $isFirstLoad = "switchedplaintohtml"; 669 //error_log(__METHOD__.__LINE__.$content['mail_plaintext']); 670 $suppressSigOnTop = true; 671 $content['mail_plaintext'] = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$content['mail_plaintext']); 672 //$this->replaceEmailAdresses($content['mail_plaintext']); 673 $content['body'] = $content['mail_plaintext']; 674 unset($content['mail_plaintext']); 675 $content['is_html'] = true; 676 $content['is_plain'] = false; 677 } 678 679 $content['body'] = ($content['body'] ? $content['body'] : $content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text']); 680 unset($_content['body']); 681 unset($_content['mail_htmltext']); 682 unset($_content['mail_plaintext']); 683 $_currentMode = $_content['mimeType']; 684 685 // we have to keep comments to be able to changing signatures 686 // signature is wraped in "<!-- HTMLSIGBEGIN -->$signature<!-- HTMLSIGEND -->" 687 Mail::$htmLawed_config['comment'] = 2; 688 689 // form was submitted either by clicking a button or by changing one of the triggering selectboxes 690 // identity and signatureid; this might trigger that the signature in mail body may have to be altered 691 if ( !empty($content['body']) && 692 (!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount']) || 693 (!empty($composeCache['mailidentity']) && !empty($_content['mailidentity']) && $_content['mailidentity'] != $composeCache['mailidentity']) 694 ) 695 { 696 $buttonClicked = true; 697 $suppressSigOnTop = true; 698 if (!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount']) 699 { 700 $acc = Mail\Account::read($_content['mailaccount']); 701 //error_log(__METHOD__.__LINE__.array2string($acc)); 702 $Identities = Mail\Account::read_identity($acc['ident_id'],true); 703 //error_log(__METHOD__.__LINE__.array2string($Identities)); 704 if ($Identities['ident_id']) 705 { 706 $newSig = $Identities['ident_id']; 707 } 708 else 709 { 710 $newSig = $this->mail_bo->getDefaultIdentity(); 711 if ($newSig === false) $newSig = -2; 712 } 713 } 714 $_oldSig = $composeCache['mailidentity']; 715 $_signatureid = ($newSig?$newSig:$_content['mailidentity']); 716 717 if ($_oldSig != $_signatureid) 718 { 719 if($this->_debug) error_log(__METHOD__.__LINE__.' old,new ->'.$_oldSig.','.$_signatureid.'#'.$content['body']); 720 // prepare signatures, the selected sig may be used on top of the body 721 try 722 { 723 $oldSignature = Mail\Account::read_identity($_oldSig,true); 724 //error_log(__METHOD__.__LINE__.'Old:'.array2string($oldSignature).'#'); 725 $oldSigText = $oldSignature['ident_signature']; 726 } 727 catch (Exception $e) 728 { 729 $oldSignature=array(); 730 $oldSigText = null; 731 } 732 try 733 { 734 $signature = Mail\Account::read_identity($_signatureid,true); 735 //error_log(__METHOD__.__LINE__.'New:'.array2string($signature).'#'); 736 $sigText = $signature['ident_signature']; 737 } 738 catch (Exception $e) 739 { 740 $signature=array(); 741 $sigText = null; 742 } 743 //error_log(__METHOD__.'Old:'.$oldSigText.'#'); 744 //error_log(__METHOD__.'New:'.$sigText.'#'); 745 if ($_currentMode == 'plain') 746 { 747 $oldSigText = $this->convertHTMLToText($oldSigText,true,true); 748 $sigText = $this->convertHTMLToText($sigText,true,true); 749 if($this->_debug) error_log(__METHOD__." Old signature:".$oldSigText); 750 } 751 752 //$oldSigText = Mail::merge($oldSigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id'))); 753 //error_log(__METHOD__.'Old+:'.$oldSigText.'#'); 754 //$sigText = Mail::merge($sigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id'))); 755 //error_log(__METHOD__.'new+:'.$sigText.'#'); 756 $_htmlConfig = Mail::$htmLawed_config; 757 Mail::$htmLawed_config['transform_anchor'] = false; 758 $oldSigTextCleaned = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"), 759 $_currentMode == 'html' ? Api\Html::purify($oldSigText, null, array(), true) : $oldSigText); 760 //error_log(__METHOD__.'Old(clean):'.$oldSigTextCleaned.'#'); 761 if ($_currentMode == 'html') 762 { 763 $content['body'] = str_replace("\n",'\n',$content['body']); // dont know why, but \n screws up preg_replace 764 $styles = Mail::getStyles(array(array('body'=>$content['body']))); 765 if (stripos($content['body'],'style')!==false) Api\Mail\Html::replaceTagsCompletley($content['body'],'style',$endtag='',true); // clean out empty or pagewide style definitions / left over tags 766 } 767 $content['body'] = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"), 768 $_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']); 769 Mail::$htmLawed_config = $_htmlConfig; 770 if ($_currentMode == 'html') 771 { 772 $replaced = null; 773 $content['body'] = preg_replace($reg='|'.preg_quote('<!-- HTMLSIGBEGIN -->','|').'.*'.preg_quote('<!-- HTMLSIGEND -->','|').'|u', 774 $rep='<!-- HTMLSIGBEGIN -->'.$sigText.'<!-- HTMLSIGEND -->', $in=$content['body'], -1, $replaced); 775 $content['body'] = str_replace(array('\n',"\xe2\x80\x93","\xe2\x80\x94","\xe2\x82\xac"),array("\n",'–','—','€'),$content['body']); 776 //error_log(__METHOD__."() preg_replace('$reg', '$rep', '$in', -1)='".$content['body']."', replaced=$replaced"); 777 unset($rep, $in); 778 if ($replaced) 779 { 780 $content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid; 781 $found = false; // this way we skip further replacement efforts 782 } 783 else 784 { 785 // try the old way 786 $found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false); 787 } 788 } 789 else 790 { 791 $found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false); 792 } 793 794 if ($found !== false && $_oldSig != -2 && !(empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) =='')) 795 { 796 //error_log(__METHOD__.'Old Content:'.$content['body'].'#'); 797 $_oldSigText = preg_quote($oldSigTextCleaned,'~'); 798 //error_log(__METHOD__.'Old(masked):'.$_oldSigText.'#'); 799 $content['body'] = preg_replace('~'.$_oldSigText.'~mi',$sigText,$content['body'],1); 800 //error_log(__METHOD__.'new Content:'.$content['body'].'#'); 801 } 802 803 if ($_oldSig == -2 && (empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) =='')) 804 { 805 // if there is no sig selected, there is no way to replace a signature 806 } 807 808 if ($found === false) 809 { 810 if($this->_debug) error_log(__METHOD__." Old Signature failed to match:".$oldSigTextCleaned); 811 if($this->_debug) error_log(__METHOD__." Compare content:".$content['body']); 812 } 813 else 814 { 815 $content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid; 816 } 817 if ($styles) 818 { 819 //error_log($styles); 820 $content['body'] = $styles.$content['body']; 821 } 822 } 823 } 824 /*run the purify on compose body unconditional*/ 825 $content['body'] = str_replace(array("\r", "\t", "<br />\n"), array("", "", "<br />"), 826 $_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']); 827 828 // do not double insert a signature on a server roundtrip 829 if ($buttonClicked) $suppressSigOnTop = true; 830 831 // On submit reads external_vcard widget's value and addes them as attachments. 832 // this happens when we send vcards from addressbook to an opened compose 833 // dialog. 834 if ($appendix_data['files']) 835 { 836 $_REQUEST['preset']['file'] = $appendix_data['files']['file']; 837 $_REQUEST['preset']['type'] = $appendix_data['files']['type']; 838 $_content['filemode'] = !empty($appendix_data['files']['filemode']) && 839 isset(Vfs\Sharing::$modes[$appendix_data['files']['filemode']]) ? 840 $appendix_data['files']['filemode'] : Vfs\Sharing::ATTACH; 841 $suppressSigOnTop = true; 842 unset($_content['attachments']); 843 $this->addPresetFiles($content, $insertSigOnTop, true); 844 } 845 846 if ($isFirstLoad) 847 { 848 $alwaysAttachVCardAtCompose = false; // we use this to eliminate double attachments, if users VCard is already present/attached 849 if ( isset($GLOBALS['egw_info']['apps']['stylite']) && (isset($this->mailPreferences['attachVCardAtCompose']) && 850 $this->mailPreferences['attachVCardAtCompose'])) 851 { 852 $alwaysAttachVCardAtCompose = true; 853 if (!is_array($_REQUEST['preset']['file']) && !empty($_REQUEST['preset']['file'])) 854 { 855 $f = $_REQUEST['preset']['file']; 856 $_REQUEST['preset']['file'] = array($f); 857 } 858 $_REQUEST['preset']['file'][] = "vfs://default/apps/addressbook/".$GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')."/.entry"; 859 } 860 // an app passed the request for fetching and mailing an entry 861 if (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id'])) 862 { 863 $app = $_REQUEST['app']; 864 $mt = $_REQUEST['method']; 865 $id = $_REQUEST['id']; 866 // passed method MUST be registered 867 $method = Link::get_registry($app,$mt); 868 //error_log(__METHOD__.__LINE__.array2string($method)); 869 if ($method) 870 { 871 $res = ExecMethod($method,array($id,'html')); 872 //error_log(__METHOD__.__LINE__.array2string($res)); 873 if (!empty($res)) 874 { 875 $insertSigOnTop = 'below'; 876 if (isset($res['attachments']) && is_array($res['attachments'])) 877 { 878 foreach($res['attachments'] as $f) 879 { 880 $_REQUEST['preset']['file'][] = $f; 881 } 882 } 883 $content['subject'] = lang($app).' #'.$res['id'].': '; 884 foreach(array('subject','body','mimetype') as $name) { 885 $sName = $name; 886 if ($name=='mimetype'&&$res[$name]) 887 { 888 $sName = 'mimeType'; 889 $content[$sName] = $res[$name]; 890 } 891 else 892 { 893 if ($res[$name]) $content[$sName] .= (strlen($content[$sName])>0 ? ' ':'') .$res[$name]; 894 } 895 } 896 } 897 } 898 } 899 // handle preset info/values 900 if (is_array($_REQUEST['preset'])) 901 { 902 $alreadyProcessed=array(); 903 //_debug_array($_REQUEST); 904 if ($_REQUEST['preset']['mailto']) { 905 // handle mailto strings such as 906 // mailto:larry,dan?cc=mike&bcc=sue&subject=test&body=type+your&body=message+here 907 // the above string may be htmlentyty encoded, then multiple body tags are supported 908 // first, strip the mailto: string out of the mailto URL 909 $tmp_send_to = (stripos($_REQUEST['preset']['mailto'],'mailto')===false?$_REQUEST['preset']['mailto']:trim(substr(html_entity_decode($_REQUEST['preset']['mailto']),7))); 910 // check if there is more than the to address 911 $mailtoArray = explode('?',$tmp_send_to,2); 912 if ($mailtoArray[1]) { 913 // check if there are more than one requests 914 $addRequests = explode('&',$mailtoArray[1]); 915 foreach ($addRequests as $key => $reqval) { 916 // the additional requests should have a =, to separate key from value. 917 $reqval = preg_replace('/__AMPERSAND__/i', "&", $reqval); 918 $keyValuePair = explode('=',$reqval,2); 919 $content[$keyValuePair[0]] .= (strlen($content[$keyValuePair[0]])>0 ? ' ':'') . $keyValuePair[1]; 920 } 921 } 922 $content['to']= preg_replace('/__AMPERSAND__/i', "&", $mailtoArray[0]); 923 $alreadyProcessed['to']='to'; 924 // if the mailto string is not htmlentity decoded the arguments are passed as simple requests 925 foreach(array('cc','bcc','subject','body') as $name) { 926 $alreadyProcessed[$name]=$name; 927 if ($_REQUEST[$name]) $content[$name] .= (strlen($content[$name])>0 ? ( $name == 'cc' || $name == 'bcc' ? ',' : ' ') : '') . $_REQUEST[$name]; 928 } 929 } 930 931 if ($_REQUEST['preset']['mailtocontactbyid']) { 932 if ($GLOBALS['egw_info']['user']['apps']['addressbook']) { 933 $contacts_obj = new Api\Contacts(); 934 $addressbookprefs =& $GLOBALS['egw_info']['user']['preferences']['addressbook']; 935 if (method_exists($contacts_obj,'search')) { 936 937 $addressArray = explode(',',$_REQUEST['preset']['mailtocontactbyid']); 938 foreach ((array)$addressArray as $id => $addressID) 939 { 940 $addressID = (int) $addressID; 941 if (!($addressID>0)) 942 { 943 unset($addressArray[$id]); 944 } 945 } 946 if (count($addressArray)) 947 { 948 $_searchCond = array('contact_id'=>$addressArray); 949 //error_log(__METHOD__.__LINE__.$_searchString); 950 $showAccounts= $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1'; 951 $filter = ($showAccounts?array():array('account_id' => null)); 952 $filter['cols_to_search']=array('n_fn','email','email_home'); 953 $contacts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),$filter); 954 // additionally search the accounts, if the contact storage is not the account storage 955 if ($showAccounts && 956 $GLOBALS['egw_info']['server']['account_repository'] == 'ldap' && 957 $GLOBALS['egw_info']['server']['contact_repository'] == 'sql') 958 { 959 $accounts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),array('owner' => 0)); 960 961 if ($contacts && $accounts) 962 { 963 $contacts = array_merge($contacts,$accounts); 964 usort($contacts, function($a, $b) 965 { 966 return strcasecmp($a['n_fn'], $b['n_fn']); 967 }); 968 } 969 elseif($accounts) 970 { 971 $contacts =& $accounts; 972 } 973 unset($accounts); 974 } 975 } 976 if(is_array($contacts)) { 977 $mailtoArray = array(); 978 $primary = $addressbookprefs['distributionListPreferredMail']; 979 if ($primary != 'email' && $primary != 'email_home') $primary = 'email'; 980 $secondary = ($primary == 'email'?'email_home':'email'); 981 //error_log(__METHOD__.__LINE__.array2string($contacts)); 982 foreach($contacts as $contact) { 983 $innerCounter=0; 984 foreach(array($contact[$primary],$contact[$secondary]) as $email) { 985 // use pref distributionListPreferredMail for the primary address 986 // avoid wrong addresses, if an rfc822 encoded address is in addressbook 987 $email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email); 988 $contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']); 989 $completeMailString = addslashes(trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']) .' <'. trim($email) .'>'); 990 if($innerCounter==0 && !empty($email) && in_array($completeMailString ,$mailtoArray) === false) { 991 $i++; 992 $innerCounter++; 993 $mailtoArray[$i] = $completeMailString; 994 } 995 } 996 } 997 } 998 //error_log(__METHOD__.__LINE__.array2string($mailtoArray)); 999 $alreadyProcessed['to']='to'; 1000 $content['to']=$mailtoArray; 1001 } 1002 } 1003 } 1004 1005 if (isset($_REQUEST['preset']['file'])) 1006 { 1007 $content['filemode'] = !empty($_REQUEST['preset']['filemode']) && 1008 (isset(Vfs\Sharing::$modes[$_REQUEST['preset']['filemode']]) || isset(Vfs\HiddenUploadSharing::$modes[$_REQUEST['preset']['filemode']])) ? 1009 $_REQUEST['preset']['filemode'] : Vfs\Sharing::ATTACH; 1010 1011 $this->addPresetFiles($content, $insertSigOnTop, $alwaysAttachVCardAtCompose); 1012 $remember = array(); 1013 if (isset($_REQUEST['preset']['mailto']) || (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id']))) 1014 { 1015 foreach(array_keys($content) as $k) 1016 { 1017 if (in_array($k,array('to','cc','bcc','subject','body','mimeType'))&&isset($this->sessionData[$k])) 1018 { 1019 $alreadyProcessed[$k]=$k; 1020 $remember[$k] = $this->sessionData[$k]; 1021 } 1022 } 1023 } 1024 if(!empty($remember)) $content = array_merge($content,$remember); 1025 } 1026 foreach(array('to','cc','bcc','subject','body','mimeType') as $name) 1027 { 1028 //always handle mimeType 1029 if ($name=='mimeType' && $_REQUEST['preset'][$name]) 1030 { 1031 $_content[$name]=$content[$name]=$_REQUEST['preset'][$name]; 1032 } 1033 //skip if already processed by "preset Routines" 1034 if ($alreadyProcessed[$name]) continue; 1035 //error_log(__METHOD__.__LINE__.':'.$name.'->'. $_REQUEST['preset'][$name]); 1036 if ($_REQUEST['preset'][$name]) $content[$name] = $_REQUEST['preset'][$name]; 1037 } 1038 } 1039 // is the to address set already? 1040 if (!empty($_REQUEST['send_to'])) 1041 { 1042 $content['to'] = base64_decode($_REQUEST['send_to']); 1043 // first check if there is a questionmark or ampersand 1044 if (strpos($content['to'],'?')!== false) list($content['to'],$rest) = explode('?',$content['to'],2); 1045 $content['to'] = html_entity_decode($content['to']); 1046 if (($at_pos = strpos($content['to'],'@')) !== false) 1047 { 1048 if (($amp_pos = strpos(substr($content['to'],$at_pos),'&')) !== false) 1049 { 1050 //list($email,$addoptions) = explode('&',$value,2); 1051 $email = substr($content['to'],0,$amp_pos+$at_pos); 1052 $rest = substr($content['to'], $amp_pos+$at_pos+1); 1053 //error_log(__METHOD__.__LINE__.$email.' '.$rest); 1054 $content['to'] = $email; 1055 } 1056 } 1057 if (strpos($content['to'],'%40')!== false) $content['to'] = Api\Html::purify(str_replace('%40','@',$content['to'])); 1058 $rarr = array(Api\Html::purify($rest)); 1059 if (isset($rest)&&!empty($rest) && strpos($rest,'&')!== false) $rarr = explode('&',$rest); 1060 //error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($rarr)); 1061 $karr = array(); 1062 foreach ($rarr as &$rval) 1063 { 1064 //must contain = 1065 if (strpos($rval,'=')!== false) 1066 { 1067 list($k,$v) = explode('=',$rval,2); 1068 $karr[$k] = (string)$v; 1069 unset($k,$v); 1070 } 1071 } 1072 //error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($karr)); 1073 foreach(array('cc','bcc','subject','body') as $name) 1074 { 1075 if ($karr[$name]) $content[$name] = $karr[$name]; 1076 } 1077 if (!empty($_REQUEST['subject'])) $content['subject'] = Api\Html::purify(trim(html_entity_decode($_REQUEST['subject']))); 1078 } 1079 } 1080 //error_log(__METHOD__.__LINE__.array2string($content)); 1081 //is the MimeType set/requested 1082 if ($isFirstLoad && !empty($_REQUEST['mimeType'])) 1083 { 1084 $_content['mimeType'] = $content['mimeType']; 1085 if (($_REQUEST['mimeType']=="text" ||$_REQUEST['mimeType']=="plain") && $content['mimeType'] == 'html') 1086 { 1087 $_content['mimeType'] = $content['mimeType'] = 'plain'; 1088 $content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body'])); 1089 } 1090 if ($_REQUEST['mimeType']=="html" && $content['mimeType'] != 'html') 1091 { 1092 $_content['mimeType'] = $content['mimeType'] = 'html'; 1093 $content['body'] = "<pre>".$content['body']."</pre>"; 1094 // take care this assumption is made on the creation of the reply header in bocompose::getReplyData 1095 if (strpos($content['body'],"<pre> \r\n \r\n---")===0) $content['body'] = substr_replace($content['body']," <br>\r\n<pre>---",0,strlen("<pre> \r\n \r\n---")-1); 1096 } 1097 } 1098 else 1099 { 1100 // try to enforce a mimeType on reply ( if type is not of the wanted type ) 1101 if ($isReply) 1102 { 1103 if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="text" && 1104 $content['mimeType'] == 'html') 1105 { 1106 $_content['mimeType'] = $content['mimeType'] = 'plain'; 1107 $content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body'])); 1108 } 1109 if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="html" && 1110 $content['mimeType'] != 'html') 1111 { 1112 $_content['mimeType'] = $content['mimeType'] = 'html'; 1113 $content['body'] = "<pre>".$content['body']."</pre>"; 1114 // take care this assumption is made on the creation of the reply header in bocompose::getReplyData 1115 if (strpos($content['body'],"<pre> \r\n \r\n---")===0) $content['body'] = substr_replace($content['body']," <br>\r\n<pre>---",0,strlen("<pre> \r\n \r\n---")-1); 1116 } 1117 } 1118 } 1119 1120 if ($content['mimeType'] == 'html' && Api\Html::htmlarea_availible()===false) 1121 { 1122 $_content['mimeType'] = $content['mimeType'] = 'plain'; 1123 $content['body'] = $this->convertHTMLToText($content['body']); 1124 } 1125 // is a certain signature requested? 1126 // only the following values are supported (and make sense) 1127 // no => means -2 1128 // system => means -1 1129 // default => fetches the default, which is standard behavior 1130 if (!empty($_REQUEST['signature']) && (strtolower($_REQUEST['signature']) == 'no' || strtolower($_REQUEST['signature']) == 'system')) 1131 { 1132 $content['mailidentity'] = $presetSig = (strtolower($_REQUEST['signature']) == 'no' ? -2 : -1); 1133 } 1134 1135 $disableRuler = false; 1136 //_debug_array(($presetSig ? $presetSig : $content['mailidentity'])); 1137 try 1138 { 1139 $signature = Mail\Account::read_identity($content['mailidentity'] ? $content['mailidentity'] : $presetSig,true); 1140 } 1141 catch (Exception $e) 1142 { 1143 //PROBABLY NOT FOUND 1144 $signature=array(); 1145 } 1146 if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) && 1147 $this->mailPreferences['disableRulerForSignatureSeparation']) || 1148 empty($signature['ident_signature']) || 1149 trim($this->convertHTMLToText($signature['ident_signature'],true,true)) =='' || 1150 $this->mailPreferences['insertSignatureAtTopOfMessage'] == '1') 1151 { 1152 $disableRuler = true; 1153 } 1154 $font_span = $font_part = ''; 1155 if($content['mimeType'] == 'html') { 1156 // User preferences for style 1157 $font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font']; 1158 $font_size = Etemplate\Widget\HtmlArea::font_size_from_prefs(); 1159 $font_part = '<span style="width:100%; display: inline; '.($font?'font-family:'.$font.'; ':'').($font_size?'font-size:'.$font_size.'; ':'').'">'; 1160 $font_span = $font_part.'​</span>'; 1161 if (empty($font) && empty($font_size)) $font_span = ''; 1162 } 1163 // the font span should only be applied on first load or on switch plain->html and the absence of the font_part of the span 1164 if (!$isFirstLoad && !empty($font_span) && stripos($content['body'],$font_part)===false) $font_span = ''; 1165 //remove possible html header stuff 1166 if (stripos($content['body'],'<html><head></head><body>')!==false) $content['body'] = str_ireplace(array('<html><head></head><body>','</body></html>'),array('',''),$content['body']); 1167 //error_log(__METHOD__.__LINE__.array2string($this->mailPreferences)); 1168 $blockElements = array('address','blockquote','center','del','dir','div','dl','fieldset','form','h1','h2','h3','h4','h5','h6','hr','ins','isindex','menu','noframes','noscript','ol','p','pre','table','ul'); 1169 if ($this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend' && 1170 !(isset($_POST['mySigID']) && !empty($_POST['mySigID']) ) && !$suppressSigOnTop 1171 ) 1172 { 1173 // ON tOP OR BELOW? pREF CAN TELL 1174 /* 1175 Signature behavior preference changed. New default, if not set -> 0 1176 '0' => 'after reply, visible during compose', 1177 '1' => 'before reply, visible during compose', 1178 'no_belowaftersend' => 'appended after reply before sending', 1179 */ 1180 $insertSigOnTop = ($insertSigOnTop?$insertSigOnTop:($this->mailPreferences['insertSignatureAtTopOfMessage']?$this->mailPreferences['insertSignatureAtTopOfMessage']:'below')); 1181 $sigText = Mail::merge($signature['ident_signature'],array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id'))); 1182 if ($content['mimeType'] == 'html') 1183 { 1184 $sigTextStartsWithBlockElement = ($disableRuler?false:true); 1185 foreach($blockElements as $e) 1186 { 1187 if ($sigTextStartsWithBlockElement) break; 1188 if (stripos(trim($sigText),'<'.$e)===0) $sigTextStartsWithBlockElement = true; 1189 } 1190 } 1191 if($content['mimeType'] == 'html') { 1192 $before = $disableRuler ? '' : '<hr style="border:1px dotted silver; width:100%;">'; 1193 $inbetween = ''; 1194 } else { 1195 $before = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n"); 1196 $inbetween = "\r\n"; 1197 } 1198 if ($content['mimeType'] == 'html') 1199 { 1200 $sigText = ($sigTextStartsWithBlockElement?'':"<div>")."<!-- HTMLSIGBEGIN -->".$sigText."<!-- HTMLSIGEND -->".($sigTextStartsWithBlockElement?'':"</div>"); 1201 } 1202 1203 if ($insertSigOnTop === 'below') 1204 { 1205 $content['body'] = $font_span.$content['body'].$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true)); 1206 } 1207 else 1208 { 1209 $content['body'] = $font_span.$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true)).$inbetween.$content['body']; 1210 } 1211 } 1212 // Skip this part if we're merging, it would add an extra line at the top 1213 else if (!$content['body']) 1214 { 1215 $content['body'] = ($font_span?($isFirstLoad === "switchedplaintohtml"?$font_part:$font_span):'').($isFirstLoad === "switchedplaintohtml"?"</span>":""); 1216 } 1217 //error_log(__METHOD__.__LINE__.$content['body']); 1218 1219 // prepare body 1220 // in a way, this tests if we are having real utf-8 (the displayCharset) by now; we should if charsets reported (or detected) are correct 1221 $content['body'] = Api\Translation::convert_jsonsafe($content['body'],'utf-8'); 1222 //error_log(__METHOD__.__LINE__.array2string($content)); 1223 1224 // get identities of all accounts as "$acc_id:$ident_id" => $identity 1225 $sel_options['mailaccount'] = $identities = array(); 1226 foreach(Mail\Account::search(true,false) as $acc_id => $account) 1227 { 1228 // do NOT add SMTP only accounts as identities 1229 if (!$account->is_imap(false)) continue; 1230 1231 foreach($account->identities($acc_id) as $ident_id => $identity) 1232 { 1233 $sel_options['mailaccount'][$acc_id.':'.$ident_id] = $identity; 1234 $identities[$ident_id] = $identity; 1235 } 1236 unset($account); 1237 } 1238 1239 //$content['bcc'] = array('kl@egroupware.org','kl@leithoff.net'); 1240 // address stuff like from, to, cc, replyto 1241 $destinationRows = 0; 1242 foreach(self::$destinations as $destination) { 1243 if (!is_array($content[$destination])) 1244 { 1245 if (!empty($content[$destination])) $content[$destination] = (array)$content[$destination]; 1246 } 1247 $addr_content = $content[strtolower($destination)]; 1248 // we clear the given address array and rebuild it 1249 unset($content[strtolower($destination)]); 1250 foreach((array)$addr_content as $key => $value) { 1251 if ($value=="NIL@NIL") continue; 1252 if ($destination=='replyto' && str_replace('"','',$value) == 1253 str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()])) 1254 { 1255 // preserve/restore the value to content. 1256 $content[strtolower($destination)][]=$value; 1257 continue; 1258 } 1259 //error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value))); 1260 $value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT)); 1261 foreach(Mail::parseAddressList($value) as $addressObject) { 1262 if ($addressObject->host == '.SYNTAX-ERROR.') continue; 1263 $address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal); 1264 //$address = Mail::htmlentities($address, $this->displayCharset); 1265 $content[strtolower($destination)][]=$address; 1266 $destinationRows++; 1267 } 1268 } 1269 } 1270 if ($_content) 1271 { 1272 //input array of _content had no signature information but was seeded later, and content has a valid setting 1273 if (!$_contentHasSigID && $content['mailidentity'] && array_key_exists('mailidentity',$_content)) unset($_content['mailidentity']); 1274 $content = array_merge($content,$_content); 1275 1276 if (!empty($content['folder'])) $sel_options['folder']=$this->ajax_searchFolder(0,true); 1277 if (empty($content['mailaccount'])) $content['mailaccount'] = $this->mail_bo->profileID; 1278 } 1279 else 1280 { 1281 //error_log(__METHOD__.__LINE__.array2string(array($sel_options['mailaccount'],$selectedSender))); 1282 $content['mailaccount'] = $this->mail_bo->profileID; 1283 //error_log(__METHOD__.__LINE__.$content['body']); 1284 } 1285 $content['is_html'] = ($content['mimeType'] == 'html'?true:''); 1286 $content['is_plain'] = ($content['mimeType'] == 'html'?'':true); 1287 $content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body']; 1288 $content['showtempname']=0; 1289 //if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.'before merging content with uploadforCompose:'.array2string($content['attachments'])); 1290 $content['attachments']=(is_array($content['attachments'])&&is_array($content['uploadForCompose'])?array_merge($content['attachments'],(!empty($content['uploadForCompose'])?$content['uploadForCompose']:array())):(is_array($content['uploadForCompose'])?$content['uploadForCompose']:(is_array($content['attachments'])?$content['attachments']:null))); 1291 //if (is_array($content['attachments'])) foreach($content['attachments'] as $k => &$file) $file['delete['.$file['tmp_name'].']']=0; 1292 $content['no_griddata'] = empty($content['attachments']); 1293 $preserv['attachments'] = $content['attachments']; 1294 $content['expiration_blur'] = $GLOBALS['egw_info']['user']['apps']['stylite'] ? lang('Select a date') : lang('EPL only'); 1295 1296 //if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.' Attachments:'.array2string($content['attachments'])); 1297 // if no filemanager -> no vfsFileSelector 1298 if (!$GLOBALS['egw_info']['user']['apps']['filemanager']) 1299 { 1300 $content['vfsNotAvailable'] = "mail_DisplayNone"; 1301 } 1302 // if no infolog -> no save as infolog 1303 if (!$GLOBALS['egw_info']['user']['apps']['infolog']) 1304 { 1305 $content['noInfologAvailable'] = "mail_DisplayNone"; 1306 } 1307 // if no tracker -> no save as tracker 1308 if (!$GLOBALS['egw_info']['user']['apps']['tracker']) 1309 { 1310 $content['noTrackerAvailable'] = "mail_DisplayNone"; 1311 } 1312 if (!$GLOBALS['egw_info']['user']['apps']['infolog'] && !$GLOBALS['egw_info']['user']['apps']['tracker']) 1313 { 1314 $content['noSaveAsAvailable'] = "mail_DisplayNone"; 1315 } 1316 // composeID to detect if we have changes to certain content 1317 $preserv['composeID'] = $content['composeID'] = $this->composeID; 1318 //error_log(__METHOD__.__LINE__.' ComposeID:'.$preserv['composeID']); 1319 $preserv['is_html'] = $content['is_html']; 1320 $preserv['is_plain'] = $content['is_plain']; 1321 if (isset($content['mimeType'])) $preserv['mimeType'] = $content['mimeType']; 1322 $sel_options['mimeType'] = self::$mimeTypes; 1323 $sel_options['priority'] = self::$priorities; 1324 $sel_options['filemode'] = Vfs\Sharing::$modes; 1325 if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3; 1326 //$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed 1327 $etpl = new Etemplate('mail.compose'); 1328 1329 $etpl->setElementAttribute('composeToolbar', 'actions', self::getToolbarActions($content)); 1330 if ($content['mimeType']=='html') 1331 { 1332 //mode="$cont[rtfEditorFeatures]" validation_rules="$cont[validation_rules]" base_href="$cont[upload_dir]" 1333 $_htmlConfig = Mail::$htmLawed_config; 1334 Mail::$htmLawed_config['comment'] = 2; 1335 Mail::$htmLawed_config['transform_anchor'] = false; 1336 $content['validation_rules']= json_encode(Mail::$htmLawed_config); 1337 $etpl->setElementAttribute('mail_htmltext','validation_rules',$content['validation_rules']); 1338 Mail::$htmLawed_config = $_htmlConfig; 1339 } 1340 1341 if (isset($content['composeID'])&&!empty($content['composeID'])) 1342 { 1343 $composeCache = $content; 1344 unset($composeCache['body']); 1345 unset($composeCache['mail_htmltext']); 1346 unset($composeCache['mail_plaintext']); 1347 Api\Cache::setCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$this->composeID,$composeCache,$expiration=60*60*2); 1348 } 1349 if (!isset($_content['serverID'])||empty($_content['serverID'])) 1350 { 1351 $content['serverID'] = $this->mail_bo->profileID; 1352 } 1353 $preserv['serverID'] = $content['serverID']; 1354 $preserv['lastDrafted'] = $content['lastDrafted']; 1355 $preserv['processedmail_id'] = $content['processedmail_id']; 1356 $preserv['references'] = $content['references']; 1357 $preserv['in-reply-to'] = $content['in-reply-to']; 1358 // thread-topic is a proprietary microsoft header and deprecated with the current version 1359 // horde does not support the encoding of thread-topic, and probably will not no so in the future 1360 //$preserv['thread-topic'] = $content['thread-topic']; 1361 $preserv['thread-index'] = $content['thread-index']; 1362 $preserv['list-id'] = $content['list-id']; 1363 $preserv['mode'] = $content['mode']; 1364 // convert it back to checkbox expectations 1365 if($content['mimeType'] == 'html') { 1366 $content['mimeType']=1; 1367 } else { 1368 $content['mimeType']=0; 1369 } 1370 // set the current selected mailaccount as param for folderselection 1371 $etpl->setElementAttribute('folder','autocomplete_params',array('mailaccount'=>$content['mailaccount'])); 1372 // join again mailaccount and identity 1373 $content['mailaccount'] .= ':'.$content['mailidentity']; 1374 //Try to set the initial selected account to the first identity match found 1375 // which fixes the issue of prefered identity never get selected. 1376 if (!in_array($content['mailaccount'], array_keys($sel_options['mailaccount']))) 1377 { 1378 foreach ($sel_options['mailaccount'] as $ident => $value) 1379 { 1380 $idnt_acc_parts = explode(':', $ident); 1381 1382 if ($content['mailidentity'] == $idnt_acc_parts[1]) 1383 { 1384 $content['mailaccount'] = $ident; 1385 break; 1386 } 1387 } 1388 } 1389 // Resolve distribution list before send content to client 1390 foreach(array('to', 'cc', 'bcc', 'replyto') as $f) 1391 { 1392 if (is_array($content[$f])) $content[$f]= self::resolveEmailAddressList ($content[$f]); 1393 } 1394 1395 // set filemode icons for all attachments 1396 if($content['attachments'] && is_array($content['attachments'])) 1397 { 1398 foreach($content['attachments'] as &$attach) 1399 { 1400 $attach['is_dir'] = is_dir($attach['file']); 1401 $attach['filemode_icon'] = !is_dir($attach['file']) && 1402 ($content['filemode'] == Vfs\Sharing::READONLY || $content['filemode'] == Vfs\Sharing::WRITABLE) 1403 ? Vfs\Sharing::LINK : $content['filemode']; 1404 $attach['filemode_title'] = lang(Vfs\Sharing::$modes[$attach['filemode_icon']]['label']); 1405 } 1406 } 1407 1408 $content['to'] = self::resolveEmailAddressList($content['to']); 1409 $content['html_toolbar'] = empty(Mail::$mailConfig['html_toolbar']) ? 1410 join(',', Etemplate\Widget\HtmlArea::$toolbar_default_list) : join(',', Mail::$mailConfig['html_toolbar']); 1411 //error_log(__METHOD__.__LINE__.array2string($content)); 1412 $etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2); 1413 } 1414 1415 /** 1416 * Add preset files like vcard as attachments into content array 1417 * 1418 * Preset attachments are read from $_REQUEST['preset']['file'] with 1419 * optional ['type'] and ['name']. 1420 * 1421 * Attachments must either be in EGroupware Vfs or configured temp. directory! 1422 * 1423 * @param array $_content content 1424 * @param string $_insertSigOnTop 1425 * @param boolean $_eliminateDoubleAttachments 1426 */ 1427 function addPresetFiles (&$_content, &$_insertSigOnTop, $_eliminateDoubleAttachments) 1428 { 1429 // check if JSON was used 1430 if (!is_array($_REQUEST['preset']['file']) && 1431 ($_REQUEST['preset']['file'][0] === '[' && substr($_REQUEST['preset']['file'], -1) === ']' || 1432 $_REQUEST['preset']['file'][0] === '{' && substr($_REQUEST['preset']['file'], -1) === '}') && 1433 ($files = json_decode($_REQUEST['preset']['file'], true))) 1434 { 1435 $types = !empty($_REQUEST['preset']['type']) ? 1436 json_decode($_REQUEST['preset']['type'], true) : array(); 1437 $names = !empty($_REQUEST['preset']['name']) ? 1438 json_decode($_REQUEST['preset']['name'], true) : array(); 1439 } 1440 else 1441 { 1442 $files = (array)$_REQUEST['preset']['file']; 1443 $types = !empty($_REQUEST['preset']['type']) ? 1444 (array)$_REQUEST['preset']['type'] : array(); 1445 $names = !empty($_REQUEST['preset']['name']) ? 1446 (array)$_REQUEST['preset']['name'] : array(); 1447 } 1448 1449 foreach($files as $k => $path) 1450 { 1451 if (!empty($types[$k]) && stripos($types[$k],'text/calendar')!==false) 1452 { 1453 $_insertSigOnTop = 'below'; 1454 } 1455 //error_log(__METHOD__.__LINE__.$path.'->'.array2string(parse_url($path,PHP_URL_SCHEME == 'vfs'))); 1456 if (($scheme = parse_url($path,PHP_URL_SCHEME)) === 'vfs') 1457 { 1458 $type = Vfs::mime_content_type($path); 1459 // special handling for attaching vCard of iCal --> use their link-title as name 1460 if (substr($path,-7) != '/.entry' || 1461 !(list($app,$id) = array_slice(explode('/',$path),-3)) || 1462 !($name = Link::title($app, $id))) 1463 { 1464 $name = Vfs::decodePath(Vfs::basename($path)); 1465 } 1466 else 1467 { 1468 $name .= '.'.Api\MimeMagic::mime2ext($type); 1469 } 1470 // use type specified by caller, if Vfs reports only default, or contains specified type (eg. "text/vcard; charset=utf-8") 1471 if (!empty($types[$k]) && ($type == 'application/octet-stream' || stripos($types[$k], $type) === 0)) 1472 { 1473 $type = $types[$k]; 1474 } 1475 $path = str_replace('+','%2B',$path); 1476 $formData = array( 1477 'name' => $name, 1478 'type' => $type, 1479 'file' => Vfs::decodePath($path), 1480 'size' => filesize(Vfs::decodePath($path)), 1481 ); 1482 if ($formData['type'] == Vfs::DIR_MIME_TYPE && $_content['filemode'] == Vfs\Sharing::ATTACH) 1483 { 1484 $_content['filemode'] = Vfs\Sharing::READONLY; 1485 Framework::message(lang('Directories have to be shared.'), 'info'); 1486 } 1487 } 1488 // do not allow to attache something from server filesystem outside configured temp_dir 1489 elseif (strpos(realpath(parse_url($path, PHP_URL_PATH)), realpath($GLOBALS['egw_info']['server']['temp_dir']).'/') !== 0) 1490 { 1491 error_log(__METHOD__."() Attaching '$path' outside configured temp. directory '{$GLOBALS['egw_info']['server']['temp_dir']}' denied!"); 1492 } 1493 elseif(is_readable($path)) 1494 { 1495 $formData = array( 1496 'name' => isset($names[$k]) ? $names[$k] : basename($path), 1497 'type' => isset($types[$k]) ? $types[$k] : (function_exists('mime_content_type') ? mime_content_type($path) : Api\MimeMagic::filename2mime($path)), 1498 'file' => $path, 1499 'size' => filesize($path), 1500 ); 1501 } 1502 else 1503 { 1504 continue; 1505 } 1506 $this->addAttachment($formData,$_content, $_eliminateDoubleAttachments); 1507 } 1508 } 1509 1510 /** 1511 * Get pre-fill a new compose based on an existing email 1512 * 1513 * @param type $mail_id If composing based on an existing mail, this is the ID of the mail 1514 * @param type $part_id For multi-part mails, indicates which part 1515 * @param type $from Indicates what the mail is based on, and how to extract data. 1516 * One of 'compose', 'composeasnew', 'reply', 'reply_all', 'forward' or 'merge' 1517 * @param boolean $_focusElement varchar subject, to, body supported 1518 * @param boolean $suppressSigOnTop 1519 * @param boolean $isReply 1520 * 1521 * @return mixed[] Content array pre-filled according to source mail 1522 */ 1523 private function getComposeFrom($mail_id, $part_id, $from, &$_focusElement, &$suppressSigOnTop, &$isReply) 1524 { 1525 $content = array(); 1526 //error_log(__METHOD__.__LINE__.array2string($mail_id).", $part_id, $from, $_focusElement, $suppressSigOnTop, $isReply"); 1527 // on forward we may have to support multiple ids 1528 if ($from=='forward') 1529 { 1530 $replyIds = explode(',',$mail_id); 1531 $mail_id = $replyIds[0]; 1532 } 1533 $hA = mail_ui::splitRowID($mail_id); 1534 $msgUID = $hA['msgUID']; 1535 $folder = $hA['folder']; 1536 $icServerID = $hA['profileID']; 1537 if ($icServerID != $this->mail_bo->profileID) 1538 { 1539 $this->changeProfile($icServerID); 1540 } 1541 $icServer = $this->mail_bo->icServer; 1542 if (!empty($folder) && !empty($msgUID) ) 1543 { 1544 // this fill the session data with the values from the original email 1545 switch($from) 1546 { 1547 case 'composefromdraft': 1548 case 'composeasnew': 1549 $content = $this->getDraftData($icServer, $folder, $msgUID, $part_id); 1550 if ($from =='composefromdraft') $content['mode'] = 'composefromdraft'; 1551 $content['processedmail_id'] = $mail_id; 1552 1553 $_focusElement = 'body'; 1554 $suppressSigOnTop = true; 1555 break; 1556 case 'reply': 1557 case 'reply_all': 1558 $content = $this->getReplyData($from == 'reply' ? 'single' : 'all', $icServer, $folder, $msgUID, $part_id); 1559 $content['processedmail_id'] = $mail_id; 1560 $content['mode'] = 'reply'; 1561 $_focusElement = 'body'; 1562 $suppressSigOnTop = false; 1563 $isReply = true; 1564 break; 1565 case 'forward': 1566 $mode = ($_GET['mode']=='forwardinline'?'inline':'asmail'); 1567 // this fill the session data with the values from the original email 1568 foreach ($replyIds as &$mail_id) 1569 { 1570 //error_log(__METHOD__.__LINE__.' ID:'.$mail_id.' Mode:'.$mode); 1571 $hA = mail_ui::splitRowID($mail_id); 1572 $msgUID = $hA['msgUID']; 1573 $folder = $hA['folder']; 1574 $content = $this->getForwardData($icServer, $folder, $msgUID, $part_id, $mode); 1575 } 1576 $content['processedmail_id'] = implode(',',$replyIds); 1577 $content['mode'] = 'forward'; 1578 $isReply = ($mode?$mode=='inline':$this->mailPreferences['message_forwarding'] == 'inline'); 1579 $suppressSigOnTop = false;// ($mode && $mode=='inline'?true:false);// may be a better solution 1580 $_focusElement = 'to'; 1581 break; 1582 default: 1583 error_log('Unhandled compose source: ' . $from); 1584 } 1585 } 1586 else if ($from == 'merge' && $_REQUEST['document']) 1587 { 1588 /* 1589 * Special merge from everywhere else because all other apps merge gives 1590 * a document to be downloaded, this opens a compose dialog. 1591 * Use ajax_merge to merge & send multiple 1592 */ 1593 // Merge selected ID (in mailtocontactbyid or $mail_id) into given document 1594 $merge_class = preg_match('/^([a-z_-]+_merge)$/', $_REQUEST['merge']) ? $_REQUEST['merge'] : 'EGroupware\\Api\\Contacts\\Merge'; 1595 $document_merge = new $merge_class(); 1596 $this->mail_bo->openConnection(); 1597 $merge_ids = $_REQUEST['preset']['mailtocontactbyid'] ? $_REQUEST['preset']['mailtocontactbyid'] : $mail_id; 1598 if (!is_array($merge_ids)) $merge_ids = explode(',',$merge_ids); 1599 try 1600 { 1601 $merged_mail_id = ''; 1602 $folder = $this->mail_bo->getDraftFolder(); 1603 if(($error = $document_merge->check_document($_REQUEST['document'],''))) 1604 { 1605 $content['msg'] = $error; 1606 return $content; 1607 } 1608 1609 // Merge does not work correctly (missing to) if current app is not addressbook 1610 //$GLOBALS['egw_info']['flags']['currentapp'] = 'addressbook'; 1611 1612 // Actually do the merge 1613 if(count($merge_ids) <= 1) 1614 { 1615 $results = $this->mail_bo->importMessageToMergeAndSend( 1616 $document_merge, Vfs::PREFIX . $_REQUEST['document'], $merge_ids, $folder, $merged_mail_id 1617 ); 1618 1619 // Open compose 1620 $merged_mail_id = trim($GLOBALS['egw_info']['user']['account_id']).mail_ui::$delimiter. 1621 $this->mail_bo->profileID.mail_ui::$delimiter. 1622 base64_encode($folder).mail_ui::$delimiter.$merged_mail_id; 1623 $content = $this->getComposeFrom($merged_mail_id, $part_id, 'composefromdraft', $_focusElement, $suppressSigOnTop, $isReply); 1624 } 1625 else 1626 { 1627 $success = implode(', ',$results['success']); 1628 $fail = implode(', ', $results['failed']); 1629 if($success) Framework::message($success, 'success'); 1630 Framework::window_close($fail); 1631 } 1632 } 1633 catch (Api\Exception\WrongUserinput $e) 1634 { 1635 // if this returns with an exeption, something failed big time 1636 $content['msg'] = $e->getMessage(); 1637 } 1638 } 1639 return $content; 1640 } 1641 1642 /** 1643 * previous bocompose stuff 1644 */ 1645 1646 /** 1647 * replace emailaddresses eclosed in <> (eg.: <me@you.de>) with the emailaddress only (e.g: me@you.de) 1648 * always returns 1 1649 */ 1650 static function replaceEmailAdresses(&$text) 1651 { 1652 // replace emailaddresses eclosed in <> (eg.: <me@you.de>) with the emailaddress only (e.g: me@you.de) 1653 Api\Mail\Html::replaceEmailAdresses($text); 1654 return 1; 1655 } 1656 1657 function convertHTMLToText(&$_html,$sourceishtml = true, $stripcrl=false, $noRepEmailAddr = false) 1658 { 1659 $stripalltags = true; 1660 // third param is stripalltags, we may not need that, if the source is already in ascii 1661 if (!$sourceishtml) $stripalltags=false; 1662 return Api\Mail\Html::convertHTMLToText($_html,$this->displayCharset,$stripcrl,$stripalltags, $noRepEmailAddr); 1663 } 1664 1665 function generateRFC822Address($_addressObject) 1666 { 1667 if($_addressObject->personal && $_addressObject->mailbox && $_addressObject->host) { 1668 return sprintf('"%s" <%s@%s>', $this->mail_bo->decode_header($_addressObject->personal), $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE')); 1669 } elseif($_addressObject->mailbox && $_addressObject->host) { 1670 return sprintf("%s@%s", $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE')); 1671 } else { 1672 return $this->mail_bo->decode_header($_addressObject->mailbox,true); 1673 } 1674 } 1675 1676 /** 1677 * create a unique id, to keep track of different compose windows 1678 */ 1679 function generateComposeID() 1680 { 1681 return Mail::getRandomString(); 1682 } 1683 1684 // $_mode can be: 1685 // single: for a reply to one address 1686 // all: for a reply to all 1687 function getDraftData($_icServer, $_folder, $_uid, $_partID=NULL) 1688 { 1689 unset($_icServer); // not used 1690 $this->sessionData['to'] = array(); 1691 1692 $mail_bo = $this->mail_bo; 1693 $mail_bo->openConnection(); 1694 $mail_bo->reopen($_folder); 1695 1696 // get message headers for specified message 1697 #$headers = $mail_bo->getMessageHeader($_folder, $_uid); 1698 $headers = $mail_bo->getMessageEnvelope($_uid, $_partID); 1699 $addHeadInfo = $mail_bo->getMessageHeader($_uid, $_partID); 1700 // thread-topic is a proprietary microsoft header and deprecated with the current version 1701 // horde does not support the encoding of thread-topic, and probably will not no so in the future 1702 //if ($addHeadInfo['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $addHeadInfo['THREAD-TOPIC']; 1703 1704 //error_log(__METHOD__.__LINE__.array2string($headers)); 1705 if (!empty($addHeadInfo['X-MAILFOLDER'])) { 1706 foreach ( explode('|',$addHeadInfo['X-MAILFOLDER']) as $val ) { 1707 $fval=$val; 1708 $icServerID = $mail_bo->icServer->ImapServerId; 1709 if (stripos($val,'::')!==false) list($icServerID,$fval) = explode('::',$val,2); 1710 if ($icServerID != $mail_bo->icServer->ImapServerId) continue; 1711 if ($mail_bo->folderExists($fval)) $this->sessionData['folder'][] = $val; 1712 } 1713 } 1714 if (!empty($addHeadInfo['X-MAILIDENTITY'])) { 1715 // with the new system it would be the identity 1716 try 1717 { 1718 Mail\Account::read_identity($addHeadInfo['X-MAILIDENTITY']); 1719 $this->sessionData['mailidentity'] = $addHeadInfo['X-MAILIDENTITY']; 1720 } 1721 catch (Exception $e) 1722 { 1723 } 1724 } 1725 /* 1726 if (!empty($addHeadInfo['X-STATIONERY'])) { 1727 $this->sessionData['stationeryID'] = $addHeadInfo['X-STATIONERY']; 1728 } 1729 */ 1730 if (!empty($addHeadInfo['X-MAILACCOUNT'])) { 1731 // with the new system it would the identity is the account id 1732 try 1733 { 1734 Mail\Account::read($addHeadInfo['X-MAILACCOUNT']); 1735 $this->sessionData['mailaccount'] = $addHeadInfo['X-MAILACCOUNT']; 1736 } 1737 catch (Exception $e) 1738 { 1739 unset($e); 1740 // fail silently 1741 $this->sessionData['mailaccount'] = $mail_bo->profileID; 1742 } 1743 } 1744 // if the message is located within the draft folder, add it as last drafted version (for possible cleanup on abort)) 1745 if ($mail_bo->isDraftFolder($_folder)) $this->sessionData['lastDrafted'] = mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid);//array('uid'=>$_uid,'folder'=>$_folder); 1746 $this->sessionData['uid'] = $_uid; 1747 $this->sessionData['messageFolder'] = $_folder; 1748 $this->sessionData['isDraft'] = true; 1749 $foundAddresses = array(); 1750 foreach((array)$headers['CC'] as $val) { 1751 $rfcAddr=Mail::parseAddressList($val); 1752 $_rfcAddr = $rfcAddr[0]; 1753 if (!$_rfcAddr->valid) continue; 1754 if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) { 1755 continue; 1756 } 1757 $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; 1758 if(!$foundAddresses[$keyemail]) { 1759 $address = $this->mail_bo->decode_header($val,true); 1760 $this->sessionData['cc'][] = $val; 1761 $foundAddresses[$keyemail] = true; 1762 } 1763 } 1764 1765 foreach((array)$headers['TO'] as $val) { 1766 if(!is_array($val)) 1767 { 1768 $this->sessionData['to'][] = $val; 1769 continue; 1770 } 1771 $rfcAddr=Mail::parseAddressList($val); 1772 $_rfcAddr = $rfcAddr[0]; 1773 if (!$_rfcAddr->valid) continue; 1774 if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) { 1775 continue; 1776 } 1777 $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; 1778 if(!$foundAddresses[$keyemail]) { 1779 $address = $this->mail_bo->decode_header($val,true); 1780 $this->sessionData['to'][] = $val; 1781 $foundAddresses[$keyemail] = true; 1782 } 1783 } 1784 1785 $fromAddr = Mail::parseAddressList($addHeadInfo['FROM'])[0]; 1786 foreach((array)$headers['REPLY-TO'] as $val) { 1787 $rfcAddr=Mail::parseAddressList($val); 1788 $_rfcAddr = $rfcAddr[0]; 1789 if (!$_rfcAddr->valid || ($_rfcAddr->mailbox == $fromAddr->mailbox && $_rfcAddr->host == $fromAddr->host)) continue; 1790 if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { 1791 continue; 1792 } 1793 $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; 1794 if(!$foundAddresses[$keyemail]) { 1795 $address = $this->mail_bo->decode_header($val,true); 1796 $this->sessionData['replyto'][] = $val; 1797 $foundAddresses[$keyemail] = true; 1798 } 1799 } 1800 1801 foreach((array)$headers['BCC'] as $val) { 1802 $rfcAddr=Mail::parseAddressList($val); 1803 $_rfcAddr = $rfcAddr[0]; 1804 if (!$_rfcAddr->valid) continue; 1805 if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) { 1806 continue; 1807 } 1808 $keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host; 1809 if(!$foundAddresses[$keyemail]) { 1810 $address = $this->mail_bo->decode_header($val,true); 1811 $this->sessionData['bcc'][] = $val; 1812 $foundAddresses[$keyemail] = true; 1813 } 1814 } 1815 //_debug_array($this->sessionData); 1816 $this->sessionData['subject'] = $mail_bo->decode_header($headers['SUBJECT']); 1817 // remove a printview tag if composing 1818 $searchfor = '/^\['.lang('printview').':\]/'; 1819 $this->sessionData['subject'] = preg_replace($searchfor,'',$this->sessionData['subject']); 1820 $bodyParts = $mail_bo->getMessageBody($_uid,'always_display', $_partID); 1821 //_debug_array($bodyParts); 1822 #$fromAddress = ($headers['FROM'][0]['PERSONAL_NAME'] != 'NIL') ? $headers['FROM'][0]['RFC822_EMAIL'] : $headers['FROM'][0]['EMAIL']; 1823 if($bodyParts['0']['mimeType'] == 'text/html') { 1824 $this->sessionData['mimeType'] = 'html'; 1825 1826 for($i=0; $i<count($bodyParts); $i++) { 1827 if($i>0) { 1828 $this->sessionData['body'] .= '<hr>'; 1829 } 1830 if($bodyParts[$i]['mimeType'] == 'text/plain') { 1831 #$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body']); 1832 $bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>"; 1833 } 1834 if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']); 1835 $bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']); 1836 #error_log( "GetDraftData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1')); 1837 $this->sessionData['body'] .= ($i>0?"<br>":""). $bodyParts[$i]['body'] ; 1838 } 1839 $this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID); 1840 1841 } else { 1842 $this->sessionData['mimeType'] = 'plain'; 1843 1844 for($i=0; $i<count($bodyParts); $i++) { 1845 if($i>0) { 1846 $this->sessionData['body'] .= "<hr>"; 1847 } 1848 if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']); 1849 $bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']); 1850 #error_log( "GetDraftData (Plain) CharSet".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1')); 1851 $this->sessionData['body'] .= ($i>0?"\r\n":""). $bodyParts[$i]['body'] ; 1852 } 1853 $this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID,'plain'); 1854 } 1855 1856 if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID))) { 1857 foreach($attachments as $attachment) { 1858 //error_log(__METHOD__.__LINE__.array2string($attachment)); 1859 $cid = $attachment['cid']; 1860 $match=null; 1861 preg_match("/cid:{$cid}/", $bodyParts['0']['body'], $match); 1862 //error_log(__METHOD__.__LINE__.'searching for cid:'."/cid:{$cid}/".'#'.$r.'#'.array2string($match)); 1863 if (!$match || !$attachment['cid']) 1864 { 1865 $this->addMessageAttachment($_uid, $attachment['partID'], 1866 $_folder, 1867 $attachment['name'], 1868 $attachment['mimeType'], 1869 $attachment['size'], 1870 $attachment['is_winmail']); 1871 } 1872 } 1873 } 1874 $mail_bo->closeConnection(); 1875 return $this->sessionData; 1876 } 1877 1878 function getErrorInfo() 1879 { 1880 if(isset($this->errorInfo)) { 1881 $errorInfo = $this->errorInfo; 1882 unset($this->errorInfo); 1883 return $errorInfo; 1884 } 1885 return false; 1886 } 1887 1888 function getForwardData($_icServer, $_folder, $_uid, $_partID, $_mode=false) 1889 { 1890 if ($_mode) 1891 { 1892 $modebuff = $this->mailPreferences['message_forwarding']; 1893 $this->mailPreferences['message_forwarding'] = $_mode; 1894 } 1895 if ($this->mailPreferences['message_forwarding'] == 'inline') { 1896 $this->getReplyData('forward', $_icServer, $_folder, $_uid, $_partID); 1897 } 1898 $mail_bo = $this->mail_bo; 1899 $mail_bo->openConnection(); 1900 $mail_bo->reopen($_folder); 1901 1902 // get message headers for specified message 1903 $headers = $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder); 1904 //error_log(__METHOD__.__LINE__.array2string($headers)); 1905 //_debug_array($headers); exit; 1906 // check for Re: in subject header 1907 $this->sessionData['subject'] = "[FWD] " . $mail_bo->decode_header($headers['SUBJECT']); 1908 // the three attributes below are substituted by processedmail_id and mode 1909 //$this->sessionData['sourceFolder']=$_folder; 1910 //$this->sessionData['forwardFlag']='forwarded'; 1911 //$this->sessionData['forwardedUID']=$_uid; 1912 if ($this->mailPreferences['message_forwarding'] == 'asmail') { 1913 $this->sessionData['mimeType'] = $this->mailPreferences['composeOptions']; 1914 if($headers['SIZE']) 1915 $size = $headers['SIZE']; 1916 else 1917 $size = lang('unknown'); 1918 1919 $this->addMessageAttachment($_uid, $_partID, $_folder, 1920 $mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml', 1921 'MESSAGE/RFC822', $size); 1922 } 1923 else 1924 { 1925 unset($this->sessionData['in-reply-to']); 1926 unset($this->sessionData['to']); 1927 unset($this->sessionData['cc']); 1928 try 1929 { 1930 if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID,null,true,false,false))) { 1931 //error_log(__METHOD__.__LINE__.':'.array2string($attachments)); 1932 foreach($attachments as $attachment) { 1933 if (!($attachment['cid'] && preg_match("/image\//",$attachment['mimeType'])) || $attachment['disposition'] == 'attachment') 1934 { 1935 $this->addMessageAttachment($_uid, $attachment['partID'], 1936 $_folder, 1937 $attachment['name'], 1938 $attachment['mimeType'], 1939 $attachment['size']); 1940 } 1941 } 1942 } 1943 } 1944 catch (Mail\Smime\PassphraseMissing $e) 1945 { 1946 error_log(__METHOD__.'() Failed to forward because of smime '.$e->getMessage()); 1947 Framework::message(lang('Forwarding of this message failed'. 1948 ' because the content of this message seems to be encrypted'. 1949 ' and can not be decrypted properly. If you still wish to'. 1950 ' forward content of this encrypted message, you may try'. 1951 ' to use forward as attachment instead.'),'error'); 1952 } 1953 } 1954 $mail_bo->closeConnection(); 1955 if ($_mode) 1956 { 1957 $this->mailPreferences['message_forwarding'] = $modebuff; 1958 } 1959 //error_log(__METHOD__.__LINE__.array2string($this->sessionData)); 1960 return $this->sessionData; 1961 } 1962 1963 /** 1964 * adds uploaded files or files in eGW's temp directory as attachments 1965 * 1966 * passes the given $_formData representing an attachment to $_content 1967 * 1968 * @param array $_formData fields of the compose form (to,cc,bcc,reply-to,subject,body,priority,signature), plus data of the file (name,file,size,type) 1969 * @param array $_content the content passed to the function and to be modified 1970 * @return void 1971 */ 1972 function addAttachment($_formData,&$_content,$eliminateDoubleAttachments=false) 1973 { 1974 //error_log(__METHOD__.__LINE__.' Formdata:'.array2string($_formData).' Content:'.array2string($_content)); 1975 1976 $attachfailed = false; 1977 // to guard against exploits the file must be either uploaded or be in the temp_dir 1978 // check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.) 1979 try 1980 { 1981 $tmpFileName = Mail::checkFileBasics($_formData,$this->composeID,false); 1982 } 1983 catch (Api\Exception\WrongUserinput $e) 1984 { 1985 $attachfailed = true; 1986 $alert_msg = $e->getMessage(); 1987 Framework::message($e->getMessage(), 'error'); 1988 } 1989 //error_log(__METHOD__.__LINE__.array2string($tmpFileName)); 1990 //error_log(__METHOD__.__LINE__.array2string($_formData)); 1991 1992 if ($eliminateDoubleAttachments == true) 1993 { 1994 foreach ((array)$_content['attachments'] as $attach) 1995 { 1996 if ($attach['name'] && $attach['name'] == $_formData['name'] && 1997 strtolower($_formData['type'])== strtolower($attach['type']) && 1998 stripos($_formData['file'],'vfs://') !== false) return; 1999 } 2000 } 2001 if ($attachfailed === false) 2002 { 2003 $buffer = array( 2004 'name' => $_formData['name'], 2005 'type' => $_formData['type'], 2006 'file' => $tmpFileName, 2007 'tmp_name' => $tmpFileName, 2008 'size' => $_formData['size'] 2009 ); 2010 if (!is_array($_content['attachments'])) $_content['attachments']=array(); 2011 $_content['attachments'][] = $buffer; 2012 unset($buffer); 2013 } 2014 else 2015 { 2016 error_log(__METHOD__.__LINE__.array2string($alert_msg)); 2017 } 2018 } 2019 2020 function addMessageAttachment($_uid, $_partID, $_folder, $_name, $_type, $_size, $_is_winmail= null) 2021 { 2022 $this->sessionData['attachments'][]=array ( 2023 'uid' => $_uid, 2024 'partID' => $_partID, 2025 'name' => $_name, 2026 'type' => $_type, 2027 'size' => $_size, 2028 'folder' => $_folder, 2029 'winmailFlag' => $_is_winmail, 2030 'tmp_name' => mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid).'_'.(!empty($_partID)?$_partID:count($this->sessionData['attachments'])+1), 2031 ); 2032 } 2033 2034 function getAttachment() 2035 { 2036 // read attachment data from etemplate request, use tmpname only to identify it 2037 if (($request = Etemplate\Request::read($_GET['etemplate_exec_id']))) 2038 { 2039 foreach($request->preserv['attachments'] as $attachment) 2040 { 2041 if ($_GET['tmpname'] === $attachment['tmp_name']) break; 2042 } 2043 } 2044 if (!$request || $_GET['tmpname'] !== $attachment['tmp_name']) 2045 { 2046 header('HTTP/1.1 404 Not found'); 2047 die('Attachment '.htmlspecialchars($_GET['tmpname']).' NOT found!'); 2048 } 2049 2050 //error_log(__METHOD__.__LINE__.array2string($_GET)); 2051 if (parse_url($attachment['tmp_name'],PHP_URL_SCHEME) == 'vfs') 2052 { 2053 Vfs::load_wrapper('vfs'); 2054 } 2055 // attachment data in temp_dir, only use basename of given name, to not allow path traversal 2056 else 2057 { 2058 $attachment['tmp_name'] = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['tmp_name']); 2059 } 2060 if(!file_exists($attachment['tmp_name'])) 2061 { 2062 header('HTTP/1.1 404 Not found'); 2063 die('Attachment '.htmlspecialchars($attachment['tmp_name']).' NOT found!'); 2064 } 2065 $attachment['attachment'] = file_get_contents($attachment['tmp_name']); 2066 2067 //error_log(__METHOD__.__LINE__.' FileSize:'.filesize($attachment['tmp_name'])); 2068 if ($_GET['mode'] != "save") 2069 { 2070 if (strtoupper($attachment['type']) == 'TEXT/DIRECTORY') 2071 { 2072 $sfxMimeType = $attachment['type']; 2073 $buff = explode('.',$attachment['tmp_name']); 2074 $suffix = ''; 2075 if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime 2076 if (!empty($suffix)) $sfxMimeType = Api\MimeMagic::ext2mime($suffix); 2077 $attachment['type'] = $sfxMimeType; 2078 if (strtoupper($sfxMimeType) == 'TEXT/VCARD' || strtoupper($sfxMimeType) == 'TEXT/X-VCARD') $attachment['type'] = strtoupper($sfxMimeType); 2079 } 2080 //error_log(__METHOD__.print_r($attachment,true)); 2081 if (strtoupper($attachment['type']) == 'TEXT/CALENDAR' || strtoupper($attachment['type']) == 'TEXT/X-VCALENDAR') 2082 { 2083 //error_log(__METHOD__."about to call calendar_ical"); 2084 $calendar_ical = new calendar_ical(); 2085 $eventid = $calendar_ical->search($attachment['attachment'],-1); 2086 //error_log(__METHOD__.array2string($eventid)); 2087 if (!$eventid) $eventid = -1; 2088 $event = $calendar_ical->importVCal($attachment['attachment'],(is_array($eventid)?$eventid[0]:$eventid),null,true); 2089 //error_log(__METHOD__.$event); 2090 if ((int)$event > 0) 2091 { 2092 $vars = array( 2093 'menuaction' => 'calendar.calendar_uiforms.edit', 2094 'cal_id' => $event, 2095 ); 2096 $GLOBALS['egw']->redirect_link('../index.php',$vars); 2097 } 2098 //Import failed, download content anyway 2099 } 2100 if (strtoupper($attachment['type']) == 'TEXT/X-VCARD' || strtoupper($attachment['type']) == 'TEXT/VCARD') 2101 { 2102 $addressbook_vcal = new addressbook_vcal(); 2103 // double \r\r\n seems to end a vcard prematurely, so we set them to \r\n 2104 //error_log(__METHOD__.__LINE__.$attachment['attachment']); 2105 $attachment['attachment'] = str_replace("\r\r\n", "\r\n", $attachment['attachment']); 2106 $vcard = $addressbook_vcal->vcardtoegw($attachment['attachment']); 2107 if ($vcard['uid']) 2108 { 2109 $vcard['uid'] = trim($vcard['uid']); 2110 //error_log(__METHOD__.__LINE__.print_r($vcard,true)); 2111 $contact = $addressbook_vcal->find_contact($vcard,false); 2112 } 2113 if (!$contact) $contact = null; 2114 // if there are not enough fields in the vcard (or the parser was unable to correctly parse the vcard (as of VERSION:3.0 created by MSO)) 2115 if ($contact || count($vcard)>2) 2116 { 2117 $contact = $addressbook_vcal->addVCard($attachment['attachment'],(is_array($contact)?array_shift($contact):$contact),true); 2118 } 2119 if ((int)$contact > 0) 2120 { 2121 $vars = array( 2122 'menuaction' => 'addressbook.addressbook_ui.edit', 2123 'contact_id' => $contact, 2124 ); 2125 $GLOBALS['egw']->redirect_link('../index.php',$vars); 2126 } 2127 //Import failed, download content anyway 2128 } 2129 } 2130 //error_log(__METHOD__.__LINE__.'->'.array2string($attachment)); 2131 Api\Header\Content::safe($attachment['attachment'], $attachment['name'], $attachment['type'], $size=0, true, $_GET['mode'] == "save"); 2132 echo $attachment['attachment']; 2133 2134 exit(); 2135 } 2136 2137 /** 2138 * Test if string contains one of the keys of an array 2139 * 2140 * @param array arrayToTestAgainst to test its keys against haystack 2141 * @param string haystack 2142 * @return boolean 2143 */ 2144 function testIfOneKeyInArrayDoesExistInString($arrayToTestAgainst,$haystack) { 2145 foreach (array_keys($arrayToTestAgainst) as $k) 2146 { 2147 //error_log(__METHOD__.__LINE__.':'.$k.'<->'.$haystack); 2148 if (stripos($haystack,$k)!==false) 2149 { 2150 //error_log(__METHOD__.__LINE__.':FOUND:'.$k.'<->'.$haystack.function_backtrace()); 2151 return true; 2152 } 2153 } 2154 return false; 2155 } 2156 2157 /** 2158 * Gather the replyData and save it with the session, to be used then 2159 * 2160 * @param $_mode can be: 2161 * single: for a reply to one address 2162 * all: for a reply to all 2163 * forward: inlineforwarding of a message with its attachments 2164 * @param $_icServer number (0 as it is the active Profile) 2165 * @param $_folder string 2166 * @param $_uid number 2167 * @param $_partID number 2168 */ 2169 function getReplyData($_mode, $_icServer, $_folder, $_uid, $_partID) 2170 { 2171 unset($_icServer); // not used 2172 $foundAddresses = array(); 2173 2174 $mail_bo = $this->mail_bo; 2175 $mail_bo->openConnection(); 2176 $mail_bo->reopen($_folder); 2177 2178 $userEMailAddresses = $mail_bo->getUserEMailAddresses(); 2179 2180 // get message headers for specified message 2181 //print "AAAA: $_folder, $_uid, $_partID<br>"; 2182 $headers = $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder,$useHeaderInsteadOfEnvelope=true); 2183 //$headers = $mail_bo->getMessageHeader($_uid, $_partID, true, true, $_folder); 2184 $this->sessionData['uid'] = $_uid; 2185 $this->sessionData['messageFolder'] = $_folder; 2186 $this->sessionData['in-reply-to'] = ($headers['IN-REPLY-TO']?$headers['IN-REPLY-TO']:$headers['MESSAGE_ID']); 2187 $this->sessionData['references'] = ($headers['REFERENCES']?$headers['REFERENCES']:$headers['MESSAGE_ID']); 2188 2189 // break reference into multiple lines if they're greater than 998 chars 2190 // and remove comma seperation. Fix error serer does not support binary 2191 // data due to long references. 2192 if (strlen($this->sessionData['references'])> 998) 2193 { 2194 $temp_refs = explode(',',$this->sessionData['references']); 2195 $this->sessionData['references'] = implode(" ",$temp_refs); 2196 } 2197 2198 // thread-topic is a proprietary microsoft header and deprecated with the current version 2199 // horde does not support the encoding of thread-topic, and probably will not no so in the future 2200 //if ($headers['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $headers['THREAD-TOPIC']; 2201 if ($headers['THREAD-INDEX']) $this->sessionData['thread-index'] = $headers['THREAD-INDEX']; 2202 if ($headers['LIST-ID']) $this->sessionData['list-id'] = $headers['LIST-ID']; 2203 //error_log(__METHOD__.__LINE__.' Mode:'.$_mode.':'.array2string($headers)); 2204 // check for Reply-To: header and use if available 2205 if(!empty($headers['REPLY-TO']) && ($headers['REPLY-TO'] != $headers['FROM'])) { 2206 foreach($headers['REPLY-TO'] as $val) { 2207 if(!$foundAddresses[$val]) { 2208 $oldTo[] = $val; 2209 $foundAddresses[$val] = true; 2210 } 2211 } 2212 $oldToAddress = (is_array($headers['REPLY-TO'])?$headers['REPLY-TO'][0]:$headers['REPLY-TO']); 2213 } else { 2214 foreach($headers['FROM'] as $val) { 2215 if(!$foundAddresses[$val]) { 2216 $oldTo[] = $val; 2217 $foundAddresses[$val] = true; 2218 } 2219 } 2220 $oldToAddress = (is_array($headers['FROM'])?$headers['FROM'][0]:$headers['FROM']); 2221 } 2222 //error_log(__METHOD__.__LINE__.' OldToAddress:'.$oldToAddress.'#'); 2223 if($_mode != 'all' || ($_mode == 'all' && !empty($oldToAddress) && !$this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$oldToAddress)) ) { 2224 $this->sessionData['to'] = $oldTo; 2225 } 2226 2227 if($_mode == 'all') { 2228 // reply to any address which is cc, but not to my self 2229 #if($headers->cc) { 2230 foreach($headers['CC'] as $val) { 2231 if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) { 2232 continue; 2233 } 2234 if(!$foundAddresses[$val]) { 2235 $this->sessionData['cc'][] = $val; 2236 $foundAddresses[$val] = true; 2237 } 2238 } 2239 #} 2240 2241 // reply to any address which is to, but not to my self 2242 #if($headers->to) { 2243 foreach($headers['TO'] as $val) { 2244 if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) { 2245 continue; 2246 } 2247 if(!$foundAddresses[$val]) { 2248 $this->sessionData['to'][] = $val; 2249 $foundAddresses[$val] = true; 2250 } 2251 } 2252 #} 2253 2254 #if($headers->from) { 2255 foreach($headers['FROM'] as $val) { 2256 if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) { 2257 continue; 2258 } 2259 //error_log(__METHOD__.__LINE__.' '.$val); 2260 if(!$foundAddresses[$val]) { 2261 $this->sessionData['to'][] = $val; 2262 $foundAddresses[$val] = true; 2263 } 2264 } 2265 #} 2266 } 2267 2268 // check for Re: in subject header 2269 if(strtolower(substr(trim($mail_bo->decode_header($headers['SUBJECT'])), 0, 3)) == "re:") { 2270 $this->sessionData['subject'] = $mail_bo->decode_header($headers['SUBJECT']); 2271 } else { 2272 $this->sessionData['subject'] = "Re: " . $mail_bo->decode_header($headers['SUBJECT']); 2273 } 2274 2275 //_debug_array($headers); 2276 //error_log(__METHOD__.__LINE__.'->'.array2string($this->mailPreferences['htmlOptions'])); 2277 try { 2278 $bodyParts = $mail_bo->getMessageBody($_uid, ($this->mailPreferences['htmlOptions']?$this->mailPreferences['htmlOptions']:''), $_partID); 2279 } 2280 catch (Mail\Smime\PassphraseMissing $e) 2281 { 2282 $bodyParts = ''; 2283 error_log(__METHOD__.'() Failed to reply because of smime '.$e->getMessage()); 2284 Framework::message(lang('Replying to this message failed'. 2285 ' because the content of this message seems to be encrypted'. 2286 ' and can not be decrypted properly. If you still wish to include'. 2287 ' content of this encrypted message, you may try to use forward as'. 2288 ' attachment instead.'),'error'); 2289 } 2290 //_debug_array($bodyParts); 2291 $styles = Mail::getStyles($bodyParts); 2292 2293 $fromAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$headers['FROM'])); 2294 2295 $toAddressA = array(); 2296 $toAddress = ''; 2297 foreach ($headers['TO'] as $mailheader) { 2298 $toAddressA[] = $mailheader; 2299 } 2300 if (count($toAddressA)>0) 2301 { 2302 $toAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$toAddressA)); 2303 $toAddress = @htmlspecialchars(lang("to")).": ".$toAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n"); 2304 } 2305 $ccAddressA = array(); 2306 $ccAddress = ''; 2307 foreach ($headers['CC'] as $mailheader) { 2308 $ccAddressA[] = $mailheader; 2309 } 2310 if (count($ccAddressA)>0) 2311 { 2312 $ccAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$ccAddressA)); 2313 $ccAddress = @htmlspecialchars(lang("cc")).": ".$ccAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n"); 2314 } 2315 if($bodyParts['0']['mimeType'] == 'text/html') { 2316 $this->sessionData['body'] = /*"<br>".*//*" ".*/"<div>".'----------------'.lang("original message").'-----------------'."".'<br>'. 2317 @htmlspecialchars(lang("from")).": ".$fromAddress."<br>". 2318 $toAddress.$ccAddress. 2319 @htmlspecialchars(lang("date").": ".Mail::_strtotime($headers['DATE'],'r',true),ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."<br>". 2320 '----------------------------------------------------------'."</div>"; 2321 $this->sessionData['mimeType'] = 'html'; 2322 if (!empty($styles)) $this->sessionData['body'] .= $styles; 2323 $this->sessionData['body'] .= '<blockquote type="cite">'; 2324 2325 for($i=0; $i<count($bodyParts); $i++) { 2326 if($i>0) { 2327 $this->sessionData['body'] .= '<hr>'; 2328 } 2329 if($bodyParts[$i]['mimeType'] == 'text/plain') { 2330 #$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body'])."<br>"; 2331 $bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>"; 2332 } 2333 if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']); 2334 2335 $_htmlConfig = Mail::$htmLawed_config; 2336 Mail::$htmLawed_config['comment'] = 2; 2337 Mail::$htmLawed_config['transform_anchor'] = false; 2338 $this->sessionData['body'] .= "<br>".self::_getCleanHTML(Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet'])); 2339 Mail::$htmLawed_config = $_htmlConfig; 2340 #error_log( "GetReplyData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1')); 2341 } 2342 2343 $this->sessionData['body'] .= '</blockquote><br>'; 2344 $this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID, 'html'); 2345 } else { 2346 //$this->sessionData['body'] = @htmlspecialchars(lang("on")." ".$headers['DATE']." ".$mail_bo->decode_header($fromAddress), ENT_QUOTES) . " ".lang("wrote").":\r\n"; 2347 // take care the way the ReplyHeader is created here, is used later on in uicompose::compose, in case you force replys to be HTML (prefs) 2348 $this->sessionData['body'] = " \r\n \r\n".'----------------'.lang("original message").'-----------------'."\r\n". 2349 @htmlspecialchars(lang("from")).": ".$fromAddress."\r\n". 2350 $toAddress.$ccAddress. 2351 @htmlspecialchars(lang("date").": ".Mail::_strtotime($headers['DATE'],'r',true), ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."\r\n". 2352 '-------------------------------------------------'."\r\n \r\n "; 2353 $this->sessionData['mimeType'] = 'plain'; 2354 2355 for($i=0; $i<count($bodyParts); $i++) { 2356 if($i>0) { 2357 $this->sessionData['body'] .= "<hr>"; 2358 } 2359 2360 // add line breaks to $bodyParts 2361 $newBody2 = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'],$bodyParts[$i]['charSet']); 2362 #error_log( "GetReplyData (Plain) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1')); 2363 $newBody = mail_ui::resolve_inline_images($newBody2, $_folder, $_uid, $_partID, 'plain'); 2364 $this->sessionData['body'] .= "\r\n"; 2365 $hasSignature = false; 2366 // create body new, with good line breaks and indention 2367 foreach(explode("\n",$newBody) as $value) { 2368 // the explode is removing the character 2369 //$value .= 'ee'; 2370 2371 // Try to remove signatures from qouted parts to avoid multiple 2372 // signatures problem in reply (rfc3676#section-4.3). 2373 if ($_mode != 'forward' && ($hasSignature || ($hasSignature = preg_match("/^--\s[\r\n]$/",$value)))) 2374 { 2375 continue; 2376 } 2377 2378 $numberOfChars = strspn(trim($value), ">"); 2379 $appendString = str_repeat('>', $numberOfChars + 1); 2380 2381 $bodyAppend = $this->mail_bo->wordwrap($value, 76-strlen("\r\n$appendString "), "\r\n$appendString ",'>'); 2382 2383 if($bodyAppend[0] == '>') { 2384 $bodyAppend = '>'. $bodyAppend; 2385 } else { 2386 $bodyAppend = '> '. $bodyAppend; 2387 } 2388 2389 $this->sessionData['body'] .= $bodyAppend; 2390 } 2391 } 2392 } 2393 2394 $mail_bo->closeConnection(); 2395 return $this->sessionData; 2396 2397 } 2398 2399 /** 2400 * HTML cleanup 2401 * 2402 * @param type $_body message 2403 * @param type $_useTidy = false, if true tidy extention will be loaded and tidy will try to clean body message 2404 * since the tidy causes segmentation fault ATM, we set the default to false. 2405 * @return type 2406 */ 2407 static function _getCleanHTML($_body, $_useTidy = false) 2408 { 2409 static $nonDisplayAbleCharacters = array('[\016]','[\017]', 2410 '[\020]','[\021]','[\022]','[\023]','[\024]','[\025]','[\026]','[\027]', 2411 '[\030]','[\031]','[\032]','[\033]','[\034]','[\035]','[\036]','[\037]'); 2412 2413 if ($_useTidy && extension_loaded('tidy') ) 2414 { 2415 $tidy = new tidy(); 2416 $cleaned = $tidy->repairString($_body, Mail::$tidy_config,'utf8'); 2417 // Found errors. Strip it all so there's some output 2418 if($tidy->getStatus() == 2) 2419 { 2420 error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$tidy->errorBuffer); 2421 } 2422 else 2423 { 2424 $_body = $cleaned; 2425 } 2426 } 2427 2428 Mail::getCleanHTML($_body); 2429 return preg_replace($nonDisplayAbleCharacters, '', $_body); 2430 } 2431 2432 static function _getHostName() 2433 { 2434 if (isset($_SERVER['SERVER_NAME'])) { 2435 $result = $_SERVER['SERVER_NAME']; 2436 } else { 2437 $result = 'localhost.localdomain'; 2438 } 2439 return $result; 2440 } 2441 2442 /** 2443 * Create a message from given data and identity 2444 * 2445 * @param Api\Mailer $_mailObject 2446 * @param array $_formData 2447 * @param array $_identity 2448 * @param boolean $_autosaving =false true: autosaving, false: save-as-draft or send 2449 * 2450 * @return array returns found inline images as attachment structure 2451 */ 2452 function createMessage(Api\Mailer $_mailObject, array $_formData, array $_identity, $_autosaving=false) 2453 { 2454 if (substr($_formData['body'], 0, 27) == '-----BEGIN PGP MESSAGE-----') 2455 { 2456 $_formData['mimeType'] = 'openpgp'; 2457 } 2458 $mail_bo = $this->mail_bo; 2459 $activeMailProfile = Mail\Account::read($this->mail_bo->profileID); 2460 2461 // you need to set the sender, if you work with different identities, since most smtp servers, dont allow 2462 // sending in the name of someone else 2463 if ($_identity['ident_id'] != $activeMailProfile['ident_id'] && !empty($_identity['ident_email']) && strtolower($activeMailProfile['ident_email']) != strtolower($_identity['ident_email'])) 2464 { 2465 error_log(__METHOD__.__LINE__.' Faking From/SenderInfo for '.$activeMailProfile['ident_email'].' with ID:'.$activeMailProfile['ident_id'].'. Identitiy to use for sending:'.array2string($_identity)); 2466 } 2467 $email_From = $_identity['ident_email'] ? $_identity['ident_email'] : $activeMailProfile['ident_email']; 2468 // Try to fix identity email with no domain part set 2469 $_mailObject->setFrom(Mail::fixInvalidAliasAddress(Api\Accounts::id2name($_identity['account_id'], 'account_email'), $email_From), 2470 Mail::generateIdentityString($_identity,false)); 2471 2472 $_mailObject->addHeader('X-Priority', $_formData['priority']); 2473 $_mailObject->addHeader('X-Mailer', 'EGroupware-Mail'); 2474 if(!empty($_formData['in-reply-to'])) { 2475 if (stripos($_formData['in-reply-to'],'<')===false) $_formData['in-reply-to']='<'.trim($_formData['in-reply-to']).'>'; 2476 $_mailObject->addHeader('In-Reply-To', $_formData['in-reply-to']); 2477 } 2478 if(!empty($_formData['references'])) { 2479 if (stripos($_formData['references'],'<')===false) 2480 { 2481 $_formData['references']='<'.trim($_formData['references']).'>'; 2482 } 2483 $_mailObject->addHeader('References', $_formData['references']); 2484 } 2485 2486 if(!empty($_formData['thread-index'])) { 2487 $_mailObject->addHeader('Thread-Index', $_formData['thread-index']); 2488 } 2489 if(!empty($_formData['list-id'])) { 2490 $_mailObject->addHeader('List-Id', $_formData['list-id']); 2491 } 2492 if($_formData['disposition']=='on') { 2493 $_mailObject->addHeader('Disposition-Notification-To', $_identity['ident_email']); 2494 } 2495 2496 // Expand any mailing lists 2497 foreach(array('to', 'cc', 'bcc', 'replyto') as $field) 2498 { 2499 if ($field != 'replyto') $_formData[$field] = self::resolveEmailAddressList($_formData[$field]); 2500 2501 if ($_formData[$field]) $_mailObject->addAddress($_formData[$field], '', $field); 2502 } 2503 2504 $_mailObject->addHeader('Subject', $_formData['subject']); 2505 2506 // this should never happen since we come from the edit dialog 2507 if (Mail::detect_qp($_formData['body'])) { 2508 $_formData['body'] = preg_replace('/=\r\n/', '', $_formData['body']); 2509 $_formData['body'] = quoted_printable_decode($_formData['body']); 2510 } 2511 $disableRuler = false; 2512 $signature = $_identity['ident_signature']; 2513 $sigAlreadyThere = $this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend'?1:0; 2514 if ($sigAlreadyThere) 2515 { 2516 // note: if you use stationery ' s the insert signatures at the top does not apply here anymore, as the signature 2517 // is already part of the body, so the signature part of the template will not be applied. 2518 $signature = null; // note: no signature, no ruler!!!! 2519 } 2520 if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) && 2521 $this->mailPreferences['disableRulerForSignatureSeparation']) || 2522 empty($signature) || trim($this->convertHTMLToText($signature)) =='') 2523 { 2524 $disableRuler = true; 2525 } 2526 if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving) 2527 { 2528 $attachment_links = $this->_getAttachmentLinks($_formData['attachments'], $_formData['filemode'], 2529 $_formData['mimeType'] == 'html', 2530 array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])), 2531 $_formData['expiration'], $_formData['password']); 2532 } 2533 switch ($_formData['mimeType']) 2534 { 2535 case 'html': 2536 $body = $_formData['body']; 2537 if ($attachment_links) 2538 { 2539 if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false) 2540 { 2541 $body = str_replace('<!-- HTMLSIGBEGIN -->', $attachment_links.'<!-- HTMLSIGBEGIN -->', $body); 2542 } 2543 else 2544 { 2545 $body .= $attachment_links; 2546 } 2547 } 2548 if(!empty($signature)) 2549 { 2550 $_mailObject->setBody($this->convertHTMLToText($body, true, true). 2551 ($disableRuler ? "\r\n" : "\r\n-- \r\n"). 2552 $this->convertHTMLToText($signature, true, true)); 2553 2554 $body .= ($disableRuler ?'<br>':'<hr style="border:1px dotted silver; width:90%;">').$signature; 2555 } 2556 else 2557 { 2558 $_mailObject->setBody($this->convertHTMLToText($body, true, true)); 2559 } 2560 // convert URL Images to inline images - if possible 2561 if (!$_autosaving) $inline_images = Mail::processURL2InlineImages($_mailObject, $body, $mail_bo); 2562 if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false) 2563 { 2564 $body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body); 2565 } 2566 $_mailObject->setHtmlBody($body, null, false); // false = no automatic alternative, we called setBody() 2567 break; 2568 case 'openpgp': 2569 $_mailObject->setOpenPgpBody($_formData['body']); 2570 break; 2571 default: 2572 $body = $this->convertHTMLToText($_formData['body'],false, false, true, true); 2573 2574 if ($attachment_links) $body .= $attachment_links; 2575 2576 #$_mailObject->Body = $_formData['body']; 2577 if(!empty($signature)) { 2578 $body .= ($disableRuler ?"\r\n":"\r\n-- \r\n"). 2579 $this->convertHTMLToText($signature,true,true); 2580 } 2581 $_mailObject->setBody($body); 2582 } 2583 // add the attachments 2584 if (is_array($_formData) && isset($_formData['attachments'])) 2585 { 2586 $connection_opened = false; 2587 $tnfattachments = null; 2588 foreach((array)$_formData['attachments'] as $attachment) { 2589 if(is_array($attachment)) 2590 { 2591 if (!empty($attachment['uid']) && !empty($attachment['folder'])) { 2592 /* Example: 2593 Array([0] => Array( 2594 [uid] => 21178 2595 [partID] => 2 2596 [name] => [Untitled].pdf 2597 [type] => application/pdf 2598 [size] => 622379 2599 [folder] => INBOX)) 2600 */ 2601 if (!$connection_opened) 2602 { 2603 $mail_bo->openConnection($mail_bo->profileID); 2604 $connection_opened = true; 2605 } 2606 $mail_bo->reopen($attachment['folder']); 2607 switch(strtoupper($attachment['type'])) { 2608 case 'MESSAGE/RFC': 2609 case 'MESSAGE/RFC822': 2610 $rawBody=''; 2611 if (isset($attachment['partID'])) { 2612 $eml = $mail_bo->getAttachment($attachment['uid'],$attachment['partID'],0,false,true,$attachment['folder']); 2613 $rawBody=$eml['attachment']; 2614 } else { 2615 $rawBody = $mail_bo->getMessageRawBody($attachment['uid'], $attachment['partID'],$attachment['folder']); 2616 } 2617 $_mailObject->addStringAttachment($rawBody, $attachment['name'], 'message/rfc822'); 2618 break; 2619 default: 2620 $attachmentData = $mail_bo->getAttachment($attachment['uid'], $attachment['partID'],0,false); 2621 if ($attachmentData['type'] == 'APPLICATION/MS-TNEF') 2622 { 2623 if (!is_array($tnfattachments)) $tnfattachments = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID']); 2624 foreach ($tnfattachments as $k) 2625 { 2626 if ($k['name'] == $attachment['name']) 2627 { 2628 $tnfpart = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID'],$k['is_winmail']); 2629 $attachmentData['attachment'] = $tnfpart['attachment']; 2630 break; 2631 } 2632 } 2633 } 2634 $_mailObject->addStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['type']); 2635 break; 2636 } 2637 } 2638 // attach files not for autosaving 2639 elseif ($_formData['filemode'] == Vfs\Sharing::ATTACH && !$_autosaving) 2640 { 2641 if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs') 2642 { 2643 Vfs::load_wrapper('vfs'); 2644 $tmp_path = $attachment['file']; 2645 } 2646 else // non-vfs file has to be in temp_dir 2647 { 2648 $tmp_path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['file']); 2649 } 2650 $_mailObject->addAttachment ( 2651 $tmp_path, 2652 $attachment['name'], 2653 $attachment['type'] 2654 ); 2655 } 2656 } 2657 } 2658 if ($connection_opened) $mail_bo->closeConnection(); 2659 } 2660 return is_array($inline_images)?$inline_images:array(); 2661 } 2662 2663 /** 2664 * Get html or text containing links to attachments 2665 * 2666 * We only care about file attachments, not forwarded messages or parts 2667 * 2668 * @param array $attachments 2669 * @param string $filemode Vfs\Sharing::(ATTACH|LINK|READONL|WRITABLE) 2670 * @param boolean $html 2671 * @param array $recipients =array() 2672 * @param string $expiration =null 2673 * @param string $password =null 2674 * @return string might be empty if no file attachments found 2675 */ 2676 protected function _getAttachmentLinks(array $attachments, $filemode, $html, $recipients=array(), $expiration=null, $password=null) 2677 { 2678 if ($filemode == Vfs\Sharing::ATTACH) return ''; 2679 2680 $links = array(); 2681 foreach($attachments as $attachment) 2682 { 2683 $path = $attachment['file']; 2684 if (empty($path)) continue; // we only care about file attachments, not forwarded messages or parts 2685 if (parse_url($attachment['file'],PHP_URL_SCHEME) != 'vfs') 2686 { 2687 $path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($path); 2688 } 2689 // create share 2690 if ($filemode == Vfs\Sharing::WRITABLE || $expiration || $password) 2691 { 2692 $share = stylite_sharing::create($path, $filemode, $attachment['name'], $recipients, $expiration, $password); 2693 } 2694 else 2695 { 2696 $share = Vfs\Sharing::create('', $path, $filemode, $attachment['name'], $recipients); 2697 } 2698 $link = Vfs\Sharing::share2link($share); 2699 2700 $name = Vfs::basename($attachment['name'] ? $attachment['name'] : $attachment['file']); 2701 2702 if ($html) 2703 { 2704 $links[] = Api\Html::a_href($name, $link).' '. 2705 (is_dir($path) ? lang('Directory') : Vfs::hsize($attachment['size'])); 2706 } 2707 else 2708 { 2709 $links[] = $name.' '.Vfs::hsize($attachment['size']).': '. 2710 (is_dir($path) ? lang('Directory') : $link); 2711 } 2712 } 2713 if (!$links) 2714 { 2715 return null; // no file attachments found 2716 } 2717 elseif ($html) 2718 { 2719 return '<p>'.lang('Download attachments').":</p>\n<ul><li>".implode("</li>\n<li>", $links)."</li></ul>\n"; 2720 } 2721 return lang('Download attachments').":\n- ".implode("\n- ", $links)."\n"; 2722 } 2723 2724 /** 2725 * Save compose mail as draft 2726 * 2727 * @param array $content content sent from client-side 2728 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]' 2729 */ 2730 public function ajax_saveAsDraft ($content, $action='button[saveAsDraft]') 2731 { 2732 //error_log(__METHOD__.__LINE__.array2string($content)."(, action=$action)"); 2733 $response = Api\Json\Response::get(); 2734 $success = true; 2735 2736 // check if default account is changed then we need to change profile 2737 if (!empty($content['serverID']) && $content['serverID'] != $this->mail_bo->profileID) 2738 { 2739 $this->changeProfile($content['serverID']); 2740 } 2741 2742 $formData = array_merge($content, array( 2743 'isDrafted' => 1, 2744 'body' => $content['mail_'.($content['mimeType']?'htmltext':'plaintext')], 2745 'mimeType' => $content['mimeType']?'html':'plain' // checkbox has only true|false value 2746 )); 2747 2748 //Saving draft procedure 2749 try 2750 { 2751 $folder = $this->mail_bo->getDraftFolder(); 2752 $this->mail_bo->reopen($folder); 2753 $status = $this->mail_bo->getFolderStatus($folder); 2754 if (($messageUid = $this->saveAsDraft($formData, $folder, $action))) 2755 { 2756 // saving as draft, does not mean closing the message 2757 $messageUid = ($messageUid===true ? $status['uidnext'] : $messageUid); 2758 if (is_array($this->mail_bo->getMessageHeader($messageUid, '',false, false, $folder))) 2759 { 2760 $draft_id = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $messageUid); 2761 if ($content['lastDrafted'] != $draft_id && isset($content['lastDrafted'])) 2762 { 2763 $dhA = mail_ui::splitRowID($content['lastDrafted']); 2764 $duid = $dhA['msgUID']; 2765 $dmailbox = $dhA['folder']; 2766 // beware: do not delete the original mail as found in processedmail_id 2767 $pMuid=''; 2768 if ($content['processedmail_id']) 2769 { 2770 $pMhA = mail_ui::splitRowID($content['processedmail_id']); 2771 $pMuid = $pMhA['msgUID']; 2772 } 2773 //error_log(__METHOD__.__LINE__."#$pMuid#$pMuid!=$duid#".array2string($content['attachments'])); 2774 // do not delete the original message if attachments are present 2775 if (empty($pMuid) || $pMuid!=$duid || empty($content['attachments'])) 2776 { 2777 try 2778 { 2779 $this->mail_bo->deleteMessages($duid,$dmailbox,'remove_immediately'); 2780 } 2781 catch (Api\Exception $e) 2782 { 2783 $msg = str_replace('"',"'",$e->getMessage()); 2784 $success = false; 2785 error_log(__METHOD__.__LINE__.$msg); 2786 } 2787 } else { 2788 error_log(__METHOD__.__LINE__.': original message ('.$pMuid.') has attachments and lastDrafted ID ('.$duid.') equals the former'); 2789 } 2790 } else { 2791 error_log(__METHOD__.__LINE__." No current draftID (".$draft_id."), or no lastDrafted Info (".$content['lastDrafted'].") or the former being equal:".array2string($content)."(, action=$action)"); 2792 } 2793 } else { 2794 error_log(__METHOD__.__LINE__.' No headerdata found for messageUID='.$messageUid.' in Folder:'.$folder.':'.array2string($content)."(, action=$action)"); 2795 } 2796 } 2797 else 2798 { 2799 throw new Api\Exception\WrongUserinput(lang("Error: Could not save Message as Draft")); 2800 } 2801 } 2802 catch (Api\Exception\WrongUserinput $e) 2803 { 2804 $msg = str_replace('"',"'",$e->getMessage()); 2805 error_log(__METHOD__.__LINE__.$msg); 2806 $success = false; 2807 } 2808 2809 if ($success) $msg = lang('Message saved successfully.'); 2810 2811 // Include new information to json respose, because we need them in client-side callback 2812 $response->data(array( 2813 'draftedId' => $draft_id, 2814 'message' => $msg, 2815 'success' => $success, 2816 'draftfolder' => $this->mail_bo->profileID.mail_ui::$delimiter.$this->mail_bo->getDraftFolder() 2817 )); 2818 } 2819 2820 /** 2821 * resolveEmailAddressList 2822 * @param array $_emailAddressList list of emailaddresses, may contain distributionlists 2823 * @return array return the list of emailaddresses with distributionlists resolved 2824 */ 2825 static function resolveEmailAddressList($_emailAddressList) 2826 { 2827 $contacts_obs = null; 2828 $addrFromList=array(); 2829 foreach((array)$_emailAddressList as $ak => $address) 2830 { 2831 if(is_int($address)) 2832 { 2833 if (!isset($contacts_obs)) $contacts_obj = new Api\Contacts(); 2834 // List was selected, expand to addresses 2835 unset($_emailAddressList[$ak]); 2836 $list = $contacts_obj->search('',array('n_fn','n_prefix','n_given','n_family','org_name','email','email_home'),'','','',False,'AND',false,array('list' =>(int)$address)); 2837 // Just add email addresses, they'll be checked below 2838 foreach($list as $email) 2839 { 2840 $addrFromList[] = $email['email'] ? $email['email'] : $email['email_home']; 2841 } 2842 } 2843 } 2844 if (!empty($addrFromList)) 2845 { 2846 foreach ($addrFromList as $addr) 2847 { 2848 if (!empty($addr)) $_emailAddressList[]=$addr; 2849 } 2850 } 2851 return is_array($_emailAddressList) ? array_values($_emailAddressList) : (array)$_emailAddressList; 2852 } 2853 2854 /** 2855 * Save message as draft to specific folder 2856 * 2857 * @param array $_formData content 2858 * @param string &$savingDestination ='' destination folder 2859 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]' 2860 * @return boolean return messageUID| false due to an error 2861 */ 2862 function saveAsDraft($_formData, &$savingDestination='', $action='button[saveAsDraft]') 2863 { 2864 //error_log(__METHOD__."(..., $savingDestination, action=$action)"); 2865 $mail_bo = $this->mail_bo; 2866 $mail = new Api\Mailer($this->mail_bo->profileID); 2867 2868 // preserve the bcc and if possible the save to folder information 2869 $this->sessionData['folder'] = $_formData['folder']; 2870 $this->sessionData['bcc'] = $_formData['bcc']; 2871 $this->sessionData['mailidentity'] = $_formData['mailidentity']; 2872 //$this->sessionData['stationeryID'] = $_formData['stationeryID']; 2873 $this->sessionData['mailaccount'] = $_formData['mailaccount']; 2874 $this->sessionData['attachments'] = $_formData['attachments']; 2875 try 2876 { 2877 $acc = Mail\Account::read($this->sessionData['mailaccount']); 2878 //error_log(__METHOD__.__LINE__.array2string($acc)); 2879 $identity = Mail\Account::read_identity($acc['ident_id'],true); 2880 } 2881 catch (Exception $e) 2882 { 2883 $identity=array(); 2884 } 2885 2886 $flags = '\\Seen \\Draft'; 2887 2888 $this->createMessage($mail, $_formData, $identity, $action === 'autosaving'); 2889 2890 // folder list as Customheader 2891 if (!empty($this->sessionData['folder'])) 2892 { 2893 $folders = implode('|',array_unique($this->sessionData['folder'])); 2894 $mail->addHeader('X-Mailfolder', $folders); 2895 } 2896 $mail->addHeader('X-Mailidentity', $this->sessionData['mailidentity']); 2897 //$mail->addHeader('X-Stationery', $this->sessionData['stationeryID']); 2898 $mail->addHeader('X-Mailaccount', (int)$this->sessionData['mailaccount']); 2899 // decide where to save the message (default to draft folder, if we find nothing else) 2900 // if the current folder is in draft or template folder save it there 2901 // if it is called from printview then save it with the draft folder 2902 if (empty($savingDestination)) $savingDestination = $mail_bo->getDraftFolder(); 2903 if (empty($this->sessionData['messageFolder']) && !empty($this->sessionData['mailbox'])) 2904 { 2905 $this->sessionData['messageFolder'] = $this->sessionData['mailbox']; 2906 } 2907 if (!empty($this->sessionData['messageFolder']) && ($mail_bo->isDraftFolder($this->sessionData['messageFolder']) 2908 || $mail_bo->isTemplateFolder($this->sessionData['messageFolder']))) 2909 { 2910 $savingDestination = $this->sessionData['messageFolder']; 2911 //error_log(__METHOD__.__LINE__.' SavingDestination:'.$savingDestination); 2912 } 2913 if ( !empty($_formData['printit']) && $_formData['printit'] == 0 ) $savingDestination = $mail_bo->getDraftFolder(); 2914 2915 // normaly Bcc is only added to recipients, but not as header visible to all recipients 2916 $mail->forceBccHeader(); 2917 2918 $mail_bo->openConnection(); 2919 if ($mail_bo->folderExists($savingDestination,true)) { 2920 try 2921 { 2922 $messageUid = $mail_bo->appendMessage($savingDestination, $mail->getRaw(), null, $flags); 2923 } 2924 catch (Api\Exception\WrongUserinput $e) 2925 { 2926 error_log(__METHOD__.__LINE__.lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",__METHOD__,$savingDestination,$e->getMessage())); 2927 return false; 2928 } 2929 2930 } else { 2931 error_log(__METHOD__.__LINE__."->".lang("folder")." ". $savingDestination." ".lang("does not exist on IMAP Server.")); 2932 return false; 2933 } 2934 $mail_bo->closeConnection(); 2935 return $messageUid; 2936 } 2937 2938 function send($_formData) 2939 { 2940 $mail_bo = $this->mail_bo; 2941 $mail = new Api\Mailer($mail_bo->profileID); 2942 $messageIsDraft = false; 2943 2944 $this->sessionData['mailaccount'] = $_formData['mailaccount']; 2945 $this->sessionData['to'] = self::resolveEmailAddressList($_formData['to']); 2946 $this->sessionData['cc'] = self::resolveEmailAddressList($_formData['cc']); 2947 $this->sessionData['bcc'] = self::resolveEmailAddressList($_formData['bcc']); 2948 $this->sessionData['folder'] = $_formData['folder']; 2949 $this->sessionData['replyto'] = $_formData['replyto']; 2950 $this->sessionData['subject'] = trim($_formData['subject']); 2951 $this->sessionData['body'] = $_formData['body']; 2952 $this->sessionData['priority'] = $_formData['priority']; 2953 $this->sessionData['mailidentity'] = $_formData['mailidentity']; 2954 //$this->sessionData['stationeryID'] = $_formData['stationeryID']; 2955 $this->sessionData['disposition'] = $_formData['disposition']; 2956 $this->sessionData['mimeType'] = $_formData['mimeType']; 2957 $this->sessionData['to_infolog'] = $_formData['to_infolog']; 2958 $this->sessionData['to_tracker'] = $_formData['to_tracker']; 2959 $this->sessionData['attachments'] = $_formData['attachments']; 2960 $this->sessionData['smime_sign'] = $_formData['smime_sign']; 2961 $this->sessionData['smime_encrypt'] = $_formData['smime_encrypt']; 2962 2963 if (isset($_formData['lastDrafted']) && !empty($_formData['lastDrafted'])) 2964 { 2965 $this->sessionData['lastDrafted'] = $_formData['lastDrafted']; 2966 } 2967 //error_log(__METHOD__.__LINE__.' Mode:'.$_formData['mode'].' PID:'.$_formData['processedmail_id']); 2968 if (isset($_formData['mode']) && !empty($_formData['mode'])) 2969 { 2970 if ($_formData['mode']=='forward' && !empty($_formData['processedmail_id'])) 2971 { 2972 $this->sessionData['forwardFlag']='forwarded'; 2973 $_formData['processedmail_id'] = explode(',',$_formData['processedmail_id']); 2974 $this->sessionData['uid']=array(); 2975 foreach ($_formData['processedmail_id'] as $k =>$rowid) 2976 { 2977 $fhA = mail_ui::splitRowID($rowid); 2978 $this->sessionData['uid'][] = $fhA['msgUID']; 2979 $this->sessionData['forwardedUID'][] = $fhA['msgUID']; 2980 if (!empty($fhA['folder'])) $this->sessionData['sourceFolder'] = $fhA['folder']; 2981 } 2982 } 2983 if ($_formData['mode']=='reply' && !empty($_formData['processedmail_id'])) 2984 { 2985 $rhA = mail_ui::splitRowID($_formData['processedmail_id']); 2986 $this->sessionData['uid'] = $rhA['msgUID']; 2987 $this->sessionData['messageFolder'] = $rhA['folder']; 2988 } 2989 if ($_formData['mode']=='composefromdraft' && !empty($_formData['processedmail_id'])) 2990 { 2991 $dhA = mail_ui::splitRowID($_formData['processedmail_id']); 2992 $this->sessionData['uid'] = $dhA['msgUID']; 2993 $this->sessionData['messageFolder'] = $dhA['folder']; 2994 } 2995 } 2996 // if the body is empty, maybe someone pasted something with scripts, into the message body 2997 // this should not happen anymore, unless you call send directly, since the check was introduced with the action command 2998 if(empty($this->sessionData['body'])) 2999 { 3000 // this is to be found with the egw_unset_vars array for the _POST['body'] array 3001 $name='_POST'; 3002 $key='body'; 3003 #error_log($GLOBALS['egw_unset_vars'][$name.'['.$key.']']); 3004 if (isset($GLOBALS['egw_unset_vars'][$name.'['.$key.']'])) 3005 { 3006 $this->sessionData['body'] = self::_getCleanHTML( $GLOBALS['egw_unset_vars'][$name.'['.$key.']']); 3007 $_formData['body']=$this->sessionData['body']; 3008 } 3009 #error_log($this->sessionData['body']); 3010 } 3011 if(empty($this->sessionData['to']) && empty($this->sessionData['cc']) && 3012 empty($this->sessionData['bcc']) && empty($this->sessionData['folder'])) { 3013 $messageIsDraft = true; 3014 } 3015 try 3016 { 3017 $identity = Mail\Account::read_identity((int)$this->sessionData['mailidentity'],true); 3018 } 3019 catch (Exception $e) 3020 { 3021 $identity = array(); 3022 } 3023 //error_log($this->sessionData['mailaccount']); 3024 //error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['mailidentity']).'->'.array2string($identity)); 3025 // create the messages and store inline images 3026 $inline_images = $this->createMessage($mail, $_formData, $identity); 3027 // remember the identity 3028 if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') $fromAddress = $mail->From;//$mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':''); 3029 #print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>"; 3030 #print "<pre>". $mail->getMessageBody() ."</pre><hr><br>"; 3031 #exit; 3032 // check if there are folders to be used 3033 $folderToCheck = (array)$this->sessionData['folder']; 3034 $folder = array(); //for counting only 3035 $folderOnServerID = array(); 3036 $folderOnMailAccount = array(); 3037 foreach ($folderToCheck as $k => $f) 3038 { 3039 $fval=$f; 3040 $icServerID = $_formData['serverID'];//folders always assumed with serverID 3041 if (stripos($f,'::')!==false) list($icServerID,$fval) = explode('::',$f,2); 3042 if ($_formData['serverID']!=$_formData['mailaccount']) 3043 { 3044 if ($icServerID == $_formData['serverID'] ) 3045 { 3046 $folder[$fval] = $fval; 3047 $folderOnServerID[] = $fval; 3048 } 3049 if ($icServerID == $_formData['mailaccount']) 3050 { 3051 $folder[$fval] = $fval; 3052 $folderOnMailAccount[] = $fval; 3053 } 3054 } 3055 else 3056 { 3057 if ($icServerID == $_formData['serverID'] ) 3058 { 3059 $folder[$fval] = $fval; 3060 $folderOnServerID[] = $fval; 3061 } 3062 } 3063 } 3064 //error_log(__METHOD__.__LINE__.'#'.array2string($_formData['serverID']).'<serverID<->mailaccount>'.array2string($_formData['mailaccount'])); 3065 // serverID ($_formData['serverID']) specifies where we originally came from. 3066 // mailaccount ($_formData['mailaccount']) specifies the mailaccount we send from and where the sent-copy should end up 3067 // serverID : is or may be needed to mark a mail as replied/forwarded or delete the original draft. 3068 // all other folders are tested against serverID that is carried with the foldername ID::Foldername; See above 3069 // (we work the folder from formData into folderOnMailAccount and folderOnServerID) 3070 // right now only folders from serverID or mailaccount should be selectable in compose form/dialog 3071 // we use the sentFolder settings of the choosen mailaccount 3072 // sentFolder is account specific 3073 $changeProfileOnSentFolderNeeded = false; 3074 if ($_formData['serverID']!=$_formData['mailaccount']) 3075 { 3076 $this->changeProfile($_formData['mailaccount']); 3077 //error_log(__METHOD__.__LINE__.'#'.$this->mail_bo->profileID.'<->'.$mail_bo->profileID.'#'); 3078 $changeProfileOnSentFolderNeeded = true; 3079 // sentFolder is account specific 3080 $sentFolder = $this->mail_bo->getSentFolder(); 3081 //error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#'); 3082 if ($sentFolder&& $sentFolder!= 'none' && !$this->mail_bo->folderExists($sentFolder, true)) $sentFolder=false; 3083 } 3084 else 3085 { 3086 $sentFolder = $mail_bo->getSentFolder(); 3087 //error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#'); 3088 if ($sentFolder&& $sentFolder!= 'none' && !$mail_bo->folderExists($sentFolder, true)) $sentFolder=false; 3089 } 3090 //error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#'); 3091 3092 // we switch $this->mail_bo back to the account we used to work on 3093 if ($_formData['serverID']!=$_formData['mailaccount']) 3094 { 3095 $this->changeProfile($_formData['serverID']); 3096 } 3097 3098 3099 if(isset($sentFolder) && $sentFolder && $sentFolder != 'none' && 3100 $this->mailPreferences['sendOptions'] != 'send_only' && 3101 $messageIsDraft == false) 3102 { 3103 if ($sentFolder) 3104 { 3105 if ($_formData['serverID']!=$_formData['mailaccount']) 3106 { 3107 $folderOnMailAccount[] = $sentFolder; 3108 } 3109 else 3110 { 3111 $folderOnServerID[] = $sentFolder; 3112 } 3113 $folder[$sentFolder] = $sentFolder; 3114 } 3115 else 3116 { 3117 $this->errorInfo = lang("No (valid) Send Folder set in preferences"); 3118 } 3119 } 3120 else 3121 { 3122 if (((!isset($sentFolder)||$sentFolder==false) && $this->mailPreferences['sendOptions'] != 'send_only') || 3123 ($this->mailPreferences['sendOptions'] != 'send_only' && 3124 $sentFolder != 'none')) $this->errorInfo = lang("No Send Folder set in preferences"); 3125 } 3126 // draftFolder is on Server we start from 3127 if($messageIsDraft == true) { 3128 $draftFolder = $mail_bo->getDraftFolder(); 3129 if(!empty($draftFolder) && $mail_bo->folderExists($draftFolder,true)) { 3130 $this->sessionData['folder'] = array($draftFolder); 3131 $folderOnServerID[] = $draftFolder; 3132 $folder[$draftFolder] = $draftFolder; 3133 } 3134 } 3135 if ($folderOnServerID) $folderOnServerID = array_unique($folderOnServerID); 3136 if ($folderOnMailAccount) $folderOnMailAccount = array_unique($folderOnMailAccount); 3137 if (($this->mailPreferences['sendOptions'] != 'send_only' && $sentFolder != 'none') && 3138 !( count($folder) > 0) && 3139 !($_formData['to_infolog']=='on' || $_formData['to_tracker']=='on')) 3140 { 3141 $this->errorInfo = lang("Error: ").lang("No Folder destination supplied, and no folder to save message or other measure to store the mail (save to infolog/tracker) provided, but required.").($this->errorInfo?' '.$this->errorInfo:''); 3142 #error_log($this->errorInfo); 3143 return false; 3144 } 3145 // SMIME SIGN/ENCRYPTION 3146 if ($_formData['smime_sign'] == 'on' || $_formData['smime_encrypt'] == 'on' ) 3147 { 3148 $recipients = array_merge($_formData['to'], (array) $_formData['cc'], (array) $_formData['bcc']); 3149 try { 3150 if ($_formData['smime_sign'] == 'on') 3151 { 3152 if ($_formData['smime_passphrase'] != '') { 3153 Api\Cache::setSession( 3154 'mail', 3155 'smime_passphrase', 3156 $_formData['smime_passphrase'], 3157 $GLOBALS['egw_info']['user']['preferences']['mail']['smime_pass_exp'] * 60 3158 ); 3159 } 3160 $smime_success = $this->_encrypt( 3161 $mail, 3162 $_formData['smime_encrypt'] == 'on'? Mail\Smime::TYPE_SIGN_ENCRYPT: Mail\Smime::TYPE_SIGN, 3163 Mail::stripRFC822Addresses($recipients), 3164 $identity['ident_email'], 3165 $_formData['smime_passphrase'] 3166 ); 3167 if (!$smime_success) 3168 { 3169 $response = Api\Json\Response::get(); 3170 $this->errorInfo = $_formData['smime_passphrase'] == ''? 3171 lang('You need to enter your S/MIME passphrase to send this message.'): 3172 lang('The entered passphrase is not correct! Please try again.'); 3173 $response->call('app.mail.smimePassDialog', $this->errorInfo); 3174 return false; 3175 } 3176 } 3177 elseif ($_formData['smime_sign'] == 'off' && $_formData['smime_encrypt'] == 'on') 3178 { 3179 $smime_success = $this->_encrypt( 3180 $mail, 3181 Mail\Smime::TYPE_ENCRYPT, 3182 Mail::stripRFC822Addresses($recipients), 3183 $identity['ident_email'] 3184 ); 3185 } 3186 } 3187 catch (Exception $ex) 3188 { 3189 $response = Api\Json\Response::get(); 3190 $this->errorInfo = $ex->getMessage(); 3191 return false; 3192 } 3193 } 3194 3195 // set a higher timeout for big messages 3196 @set_time_limit(120); 3197 //$mail->SMTPDebug = 10; 3198 //error_log("Folder:".count(array($this->sessionData['folder']))."To:".count((array)$this->sessionData['to'])."CC:". count((array)$this->sessionData['cc']) ."bcc:".count((array)$this->sessionData['bcc'])); 3199 if(count((array)$this->sessionData['to']) > 0 || count((array)$this->sessionData['cc']) > 0 || count((array)$this->sessionData['bcc']) > 0) { 3200 try { 3201 // do no close the session before sending, if we have to store the send text for infolog or other integration in the session 3202 if (!($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )) 3203 { 3204 $GLOBALS['egw']->session->commit_session(); 3205 } 3206 $mail->send(); 3207 } 3208 catch(Exception $e) { 3209 _egw_log_exception($e); 3210 //if( $e->details ) error_log(__METHOD__.__LINE__.array2string($e->details)); 3211 $this->errorInfo = $e->getMessage().($e->details?'<br/>'.$e->details:''); 3212 return false; 3213 } 3214 } else { 3215 if (count(array($this->sessionData['folder']))>0 && !empty($this->sessionData['folder'])) { 3216 //error_log(__METHOD__.__LINE__."Folders:".print_r($this->sessionData['folder'],true)); 3217 } else { 3218 $this->errorInfo = lang("Error: ").lang("No Address TO/CC/BCC supplied, and no folder to save message to provided."); 3219 //error_log(__METHOD__.__LINE__.$this->errorInfo); 3220 return false; 3221 } 3222 } 3223 //error_log(__METHOD__.__LINE__."Mail Sent.!"); 3224 //error_log(__METHOD__.__LINE__."Number of Folders to move copy the message to:".count($folder)); 3225 //error_log(__METHOD__.__LINE__.array2string($folder)); 3226 if ((count($folder) > 0) || (isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder'])) 3227 || (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) { 3228 $mail_bo = $this->mail_bo; 3229 $mail_bo->openConnection(); 3230 //$mail_bo->reopen($this->sessionData['messageFolder']); 3231 #error_log("(re)opened Connection"); 3232 } 3233 // if copying mail to folder, or saving mail to infolog, we need to gather the needed information 3234 if (count($folder) > 0 || $_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') { 3235 //error_log(__METHOD__.__LINE__.array2string($this->sessionData['bcc'])); 3236 3237 // normaly Bcc is only added to recipients, but not as header visible to all recipients 3238 $mail->forceBccHeader(); 3239 } 3240 // copying mail to folder 3241 if (count($folder) > 0) 3242 { 3243 foreach($folderOnServerID as $folderName) { 3244 if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all 3245 //error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName)); 3246 // if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID 3247 // if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue; 3248 if ($mail_bo->folderExists($folderName,true)) { 3249 if($mail_bo->isSentFolder($folderName)) { 3250 $flags = '\\Seen'; 3251 } elseif($mail_bo->isDraftFolder($folderName)) { 3252 $flags = '\\Draft'; 3253 } else { 3254 $flags = '\\Seen'; 3255 } 3256 #$mailHeader=explode('From:',$mail->getMessageHeader()); 3257 #$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1], 3258 //error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName)); 3259 //$mail_bo->reopen($folderName); 3260 try 3261 { 3262 //error_log(__METHOD__.__LINE__.array2string($folderName)); 3263 $mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags); 3264 } 3265 catch (Api\Exception\WrongUserinput $e) 3266 { 3267 error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$this->sessionData['subject'],$folderName,$e->getMessage())); 3268 } 3269 } 3270 else 3271 { 3272 error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName)); 3273 } 3274 } 3275 // if we choose to send from a differing profile 3276 if ($folderOnMailAccount) $this->changeProfile($_formData['mailaccount']); 3277 foreach($folderOnMailAccount as $folderName) { 3278 if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all 3279 //error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName)); 3280 // if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID 3281 // if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue; 3282 if ($this->mail_bo->folderExists($folderName,true)) { 3283 if($this->mail_bo->isSentFolder($folderName)) { 3284 $flags = '\\Seen'; 3285 } elseif($this->mail_bo->isDraftFolder($folderName)) { 3286 $flags = '\\Draft'; 3287 } else { 3288 $flags = '\\Seen'; 3289 } 3290 #$mailHeader=explode('From:',$mail->getMessageHeader()); 3291 #$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1], 3292 //error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName)); 3293 //$mail_bo->reopen($folderName); 3294 try 3295 { 3296 //error_log(__METHOD__.__LINE__.array2string($folderName)); 3297 $this->mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags); 3298 } 3299 catch (Api\Exception\WrongUserinput $e) 3300 { 3301 error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$this->sessionData['subject'],$folderName,$e->getMessage())); 3302 } 3303 } 3304 else 3305 { 3306 error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName)); 3307 } 3308 } 3309 if ($folderOnMailAccount) $this->changeProfile($_formData['serverID']); 3310 3311 //$mail_bo->closeConnection(); 3312 } 3313 // handle previous drafted versions of that mail 3314 $lastDrafted = false; 3315 if (isset($this->sessionData['lastDrafted'])) 3316 { 3317 $lastDrafted=array(); 3318 $dhA = mail_ui::splitRowID($this->sessionData['lastDrafted']); 3319 $lastDrafted['uid'] = $dhA['msgUID']; 3320 $lastDrafted['folder'] = $dhA['folder']; 3321 if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']); 3322 // manually drafted, do not delete 3323 // will be handled later on IF mode was $_formData['mode']=='composefromdraft' 3324 if (isset($lastDrafted['uid']) && (empty($lastDrafted['uid']) || $lastDrafted['uid'] == $this->sessionData['uid'])) $lastDrafted=false; 3325 //error_log(__METHOD__.__LINE__.array2string($lastDrafted)); 3326 } 3327 if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder'])) 3328 { 3329 try 3330 { 3331 if ($this->sessionData['lastDrafted'] != $this->sessionData['uid'] || !($_formData['mode']=='composefromdraft' && 3332 ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )&&$this->sessionData['attachments'])) 3333 { 3334 //error_log(__METHOD__.__LINE__."#".$lastDrafted['uid'].'#'.$lastDrafted['folder'].array2string($_formData)); 3335 //error_log(__METHOD__.__LINE__."#".array2string($_formData)); 3336 //error_log(__METHOD__.__LINE__."#".array2string($this->sessionData)); 3337 $mail_bo->deleteMessages($lastDrafted['uid'],$lastDrafted['folder'],'remove_immediately'); 3338 } 3339 } 3340 catch (Api\Exception $e) 3341 { 3342 //error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage())); 3343 unset($e); 3344 } 3345 } 3346 unset($this->sessionData['lastDrafted']); 3347 3348 //error_log("handling draft messages, flagging and such"); 3349 if((isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder'])) 3350 || (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) { 3351 // mark message as answered 3352 $mail_bo->openConnection(); 3353 $mail_bo->reopen(($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder'])); 3354 // if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send 3355 // unless your templatefolder is a subfolder of your draftfolder, and the message is in there 3356 if ($mail_bo->isDraftFolder($this->sessionData['messageFolder']) && !$mail_bo->isTemplateFolder($this->sessionData['messageFolder'])) 3357 { 3358 try // message may be deleted already, as it maybe done by autosave 3359 { 3360 if ($_formData['mode']=='composefromdraft' && 3361 !(($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on') && $this->sessionData['attachments'])) 3362 { 3363 //error_log(__METHOD__.__LINE__."#".$this->sessionData['uid'].'#'.$this->sessionData['messageFolder']); 3364 $mail_bo->deleteMessages(array($this->sessionData['uid']),$this->sessionData['messageFolder'], 'remove_immediately'); 3365 } 3366 } 3367 catch (Api\Exception $e) 3368 { 3369 //error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage())); 3370 unset($e); 3371 } 3372 } else { 3373 $mail_bo->flagMessages("answered", $this->sessionData['uid'],($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder'])); 3374 //error_log(__METHOD__.__LINE__.array2string(array_keys($this->sessionData)).':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']); 3375 if (array_key_exists('forwardFlag',$this->sessionData) && $this->sessionData['forwardFlag']=='forwarded') 3376 { 3377 try 3378 { 3379 //error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']); 3380 $mail_bo->flagMessages("forwarded", $this->sessionData['forwardedUID'],$this->sessionData['sourceFolder']); 3381 } 3382 catch (Api\Exception $e) 3383 { 3384 //error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage())); 3385 unset($e); 3386 } 3387 } 3388 } 3389 //$mail_bo->closeConnection(); 3390 } 3391 if ($mail_bo) $mail_bo->closeConnection(); 3392 //error_log("performing Infolog Stuff"); 3393 //error_log(print_r($this->sessionData['to'],true)); 3394 //error_log(print_r($this->sessionData['cc'],true)); 3395 //error_log(print_r($this->sessionData['bcc'],true)); 3396 if (is_array($this->sessionData['to'])) 3397 { 3398 $mailaddresses['to'] = $this->sessionData['to']; 3399 } 3400 else 3401 { 3402 $mailaddresses = array(); 3403 } 3404 if (is_array($this->sessionData['cc'])) $mailaddresses['cc'] = $this->sessionData['cc']; 3405 if (is_array($this->sessionData['bcc'])) $mailaddresses['bcc'] = $this->sessionData['bcc']; 3406 if (!empty($mailaddresses)) $mailaddresses['from'] = Mail\Html::decodeMailHeader($fromAddress); 3407 3408 if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' ) 3409 { 3410 $this->sessionData['attachments'] = array_merge((array)$this->sessionData['attachments'], (array)$inline_images); 3411 3412 foreach(array('to_infolog','to_tracker','to_calendar') as $app_key) 3413 { 3414 $entryid = $_formData['to_integrate_ids'][0][$app_key]; 3415 if ($_formData[$app_key] == 'on') 3416 { 3417 $app_name = substr($app_key,3); 3418 // Get registered hook data of the app called for integration 3419 $hook = Api\Hooks::single(array('location'=> 'mail_import'),$app_name); 3420 3421 // store mail / eml in temp. file to not have to download it from mail-server again 3422 $eml = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'mail_integrate'); 3423 $eml_fp = fopen($eml, 'w'); 3424 stream_copy_to_stream($mail->getRaw(), $eml_fp); 3425 fclose($eml_fp); 3426 $target = array( 3427 'menuaction' => $hook['menuaction'], 3428 'egw_data' => Link::set_data(null,'mail_integration::integrate',array( 3429 $mailaddresses, 3430 $this->sessionData['subject'], 3431 $this->convertHTMLToText($this->sessionData['body']), 3432 $this->sessionData['attachments'], 3433 false, // date 3434 $eml, 3435 $_formData['serverID']),true), 3436 'app' => $app_name 3437 ); 3438 if ($entryid) $target['entry_id'] = $entryid; 3439 // Open the app called for integration in a popup 3440 // and store the mail raw data as egw_data, in order to 3441 // be stored from registered app method later 3442 Framework::popup(Egw::link('/index.php', $target),'_blank',$hook['popup']); 3443 } 3444 } 3445 } 3446 // only clean up temp-files, if we dont need them for mail_integration::integrate 3447 elseif(is_array($this->sessionData['attachments'])) 3448 { 3449 foreach($this->sessionData['attachments'] as $value) { 3450 if (!empty($value['file']) && parse_url($value['file'],PHP_URL_SCHEME) != 'vfs') { // happens when forwarding mails 3451 unlink($GLOBALS['egw_info']['server']['temp_dir'].'/'.$value['file']); 3452 } 3453 } 3454 } 3455 3456 $this->sessionData = ''; 3457 3458 return true; 3459 } 3460 3461 /** 3462 * setDefaults, sets some defaults 3463 * 3464 * @param array $content 3465 * @return array - the input, enriched with some not set attributes 3466 */ 3467 function setDefaults($content=array()) 3468 { 3469 // if there's not already an identity selected for current account 3470 if (empty($content['mailidentity'])) 3471 { 3472 // check if there a preference / previous selection of identity for current account 3473 if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'])) 3474 { 3475 $sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']; 3476 if (!empty($sigPref[$this->mail_bo->profileID]) && $sigPref[$this->mail_bo->profileID]>0) 3477 { 3478 $content['mailidentity'] = $sigPref[$this->mail_bo->profileID]; 3479 } 3480 } 3481 // if we have no preference search for first identity with non-empty signature 3482 if (empty($content['mailidentity'])) 3483 { 3484 $default_identity = null; 3485 foreach(Mail\Account::identities($this->mail_bo->profileID, true, 'params') as $identity) 3486 { 3487 if (!isset($default_identity)) $default_identity = $identity['ident_id']; 3488 if (!empty($identity['ident_signature'])) 3489 { 3490 $content['mailidentity'] = $identity['ident_id']; 3491 break; 3492 } 3493 } 3494 } 3495 if (empty($content['mailidentity'])) $content['mailidentity'] = $default_identity; 3496 } 3497 if (!isset($content['mimeType']) || empty($content['mimeType'])) 3498 { 3499 $content['mimeType'] = 'html'; 3500 if (!empty($this->mailPreferences['composeOptions']) && $this->mailPreferences['composeOptions']=="text") $content['mimeType'] = 'plain'; 3501 } 3502 return $content; 3503 3504 } 3505 3506 function stripSlashes($_string) 3507 { 3508 if (get_magic_quotes_gpc()) { 3509 return stripslashes($_string); 3510 } else { 3511 return $_string; 3512 } 3513 } 3514 /** 3515 * Callback function to search mail folders 3516 * 3517 * @param int $_searchStringLength 3518 * @param boolean $_returnList 3519 * @param int $_mailaccountToSearch 3520 * @param boolean $_noPrefixId = false, if set to true folders name does not get prefixed by account id 3521 * @return type 3522 */ 3523 function ajax_searchFolder($_searchStringLength=2, $_returnList=false, $_mailaccountToSearch=null, $_noPrefixId=false) { 3524 //error_log(__METHOD__.__LINE__.':'.array2string($_REQUEST)); 3525 static $useCacheIfPossible = null; 3526 if (is_null($useCacheIfPossible)) $useCacheIfPossible = true; 3527 $_searchString = trim($_REQUEST['query']); 3528 $results = array(); 3529 $rememberServerID = $this->mail_bo->icServer->ImapServerId; 3530 if (is_null($_mailaccountToSearch) && !empty($_REQUEST['mailaccount'])) $_mailaccountToSearch = $_REQUEST['mailaccount']; 3531 if (empty($_mailaccountToSearch)) $_mailaccountToSearch = $this->mail_bo->icServer->ImapServerId; 3532 if ($this->mail_bo->icServer && $_mailaccountToSearch && $this->mail_bo->icServer->ImapServerId != $_mailaccountToSearch) 3533 { 3534 $this->changeProfile($_mailaccountToSearch); 3535 } 3536 if (strlen($_searchString)>=$_searchStringLength && isset($this->mail_bo->icServer)) 3537 { 3538 //error_log(__METHOD__.__LINE__.':'.$this->mail_bo->icServer->ImapServerId); 3539 $this->mail_bo->openConnection($this->mail_bo->icServer->ImapServerId); 3540 //error_log(__METHOD__.__LINE__.array2string($_searchString).'<->'.$searchString); 3541 $folderObjects = $this->mail_bo->getFolderObjects(true,false,true,$useCacheIfPossible); 3542 if (count($folderObjects)<=1) { 3543 $useCacheIfPossible = false; 3544 } 3545 else 3546 { 3547 $useCacheIfPossible = true; 3548 } 3549 $searchString = Api\Translation::convert($_searchString, Mail::$displayCharset,'UTF7-IMAP'); 3550 foreach ($folderObjects as $k =>$fA) 3551 { 3552 //error_log(__METHOD__.__LINE__.$_searchString.'/'.$searchString.' in '.$k.'->'.$fA->displayName); 3553 $f=false; 3554 $key = $_noPrefixId?$k:$_mailaccountToSearch.'::'.$k; 3555 if ($_searchStringLength<=0) 3556 { 3557 $f=true; 3558 $results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName)); 3559 } 3560 if ($f==false && stripos($fA->displayName,$_searchString)!==false) 3561 { 3562 $f=true; 3563 $results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName)); 3564 } 3565 if ($f==false && stripos($k,$searchString)!==false) 3566 { 3567 $results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName)); 3568 } 3569 } 3570 } 3571 if ($this->mail_bo->icServer && $rememberServerID != $this->mail_bo->icServer->ImapServerId) 3572 { 3573 $this->changeProfile($rememberServerID); 3574 } 3575 //error_log(__METHOD__.__LINE__.' IcServer:'.$this->mail_bo->icServer->ImapServerId.':'.array2string($results)); 3576 if ($_returnList) 3577 { 3578 foreach ((array)$results as $k => $_result) 3579 { 3580 $rL[$_result['id']] = $_result['label']; 3581 } 3582 return $rL; 3583 } 3584 // switch regular JSON response handling off 3585 Api\Json\Request::isJSONRequest(false); 3586 3587 header('Content-Type: application/json; charset=utf-8'); 3588 //error_log(__METHOD__.__LINE__); 3589 echo json_encode($results); 3590 exit(); 3591 } 3592 3593 public static function ajax_searchAddress($_searchStringLength=2) { 3594 //error_log(__METHOD__. "request from seachAddress " . $_REQUEST['query']); 3595 $_searchString = trim($_REQUEST['query']); 3596 $include_lists = (boolean)$_REQUEST['include_lists']; 3597 3598 $contacts_obj = new Api\Contacts(); 3599 $results = array(); 3600 $mailPrefs = $GLOBALS['egw_info']['user']['preferences']['mail']; 3601 $contactLabelPref = !is_array($mailPrefs['contactLabel']) && !empty($mailPrefs['contactLabel']) ? 3602 explode(',', $mailPrefs['contactLabel']) : $mailPrefs['contactLabel']; 3603 3604 // Add some matching mailing lists, and some groups, limited by config 3605 if($include_lists) 3606 { 3607 $results += static::get_lists($_searchString, $contacts_obj); 3608 } 3609 3610 if ($GLOBALS['egw_info']['user']['apps']['addressbook'] && strlen($_searchString)>=$_searchStringLength) 3611 { 3612 //error_log(__METHOD__.__LINE__.array2string($_searchString)); 3613 $showAccounts = $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1'; 3614 $search = explode(' ', $_searchString); 3615 foreach ($search as $k => $v) 3616 { 3617 if (mb_strlen($v) < 3) unset($search[$k]); 3618 } 3619 $search_str = implode(' +', $search); // tell contacts/so_sql to AND search patterns 3620 //error_log(__METHOD__.__LINE__.$_searchString); 3621 $filter = $showAccounts ? array() : array('account_id' => null); 3622 $filter['cols_to_search'] = array('n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id'); 3623 $cols = array('n_fn','n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id', 'etag'); 3624 $contacts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false, 'OR', array(0,100), $filter); 3625 $cfs_type_email = Api\Storage\Customfields::get_email_cfs('addressbook'); 3626 // additionally search the accounts, if the contact storage is not the account storage 3627 if ($showAccounts && $contacts_obj->so_accounts) 3628 { 3629 $filter['owner'] = 0; 3630 $accounts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false,'OR', array(0,100), $filter); 3631 3632 if ($contacts && $accounts) 3633 { 3634 $contacts = array_merge($contacts,$accounts); 3635 usort($contacts,function($a, $b) 3636 { 3637 return strcasecmp($a['n_fn'], $b['n_fn']); 3638 }); 3639 } 3640 elseif($accounts) 3641 { 3642 $contacts =& $accounts; 3643 } 3644 unset($accounts); 3645 } 3646 } 3647 3648 if (is_array($contacts)) 3649 { 3650 foreach($contacts as $contact) 3651 { 3652 $cf_emails = []; 3653 if ($cfs_type_email && ($cf_emails = $contacts_obj->read_customfields($contact['id'], $cfs_type_email))) 3654 { 3655 // cf_emails: [$contact['id'] => ['cf1'=>'email','cf2'=>'email2',...]] 3656 $cf_emails = array_values(reset($cf_emails)); 3657 } 3658 foreach(array_merge(array($contact['email'],$contact['email_home']), $cf_emails) as $email) 3659 { 3660 // avoid wrong addresses, if an rfc822 encoded address is in addressbook 3661 //$email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email); 3662 $rfcAddr = Mail::parseAddressList($email); 3663 $_rfcAddr=$rfcAddr->first(); 3664 if (!$_rfcAddr->valid) 3665 { 3666 continue; // skip address if we encounter an error here 3667 } 3668 $email = $_rfcAddr->mailbox.'@'.$_rfcAddr->host; 3669 3670 if (method_exists($contacts_obj,'search')) 3671 { 3672 $contact['n_fn']=''; 3673 if (!empty($contact['n_prefix']) && (empty($contactLabelPref) || in_array('n_prefix', $contactLabelPref))) $contact['n_fn'] = $contact['n_prefix']; 3674 if (!empty($contact['n_given']) && (empty($contactLabelPref) || in_array('n_given', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_given']; 3675 if (!empty($contact['n_family']) && (empty($contactLabelPref) || in_array('n_family', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_family']; 3676 if (!empty($contact['org_name']) && (empty($contactLabelPref) || in_array('org_name', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').'('.$contact['org_name'].')'; 3677 $contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']); 3678 } 3679 else 3680 { 3681 $contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']); 3682 } 3683 $args = explode('@', trim($email)); 3684 $args[] = trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']); 3685 $completeMailString = call_user_func_array('imap_rfc822_write_address', $args); 3686 if(!empty($email) && in_array($completeMailString ,$results) === false) { 3687 $results[] = array( 3688 'id'=>$completeMailString, 3689 'label' => $completeMailString, 3690 // Add just name for nice display, with title for hover 3691 'name' => $contact['n_fn'], 3692 'title' => $email, 3693 'icon' => Egw::link('/api/avatar.php', array( 3694 'contact_id' => $contact['id'], 3695 'etag' => $contact['etag'] 3696 )) 3697 ); 3698 } 3699 } 3700 } 3701 } 3702 3703 // Add groups 3704 $group_options = array('account_type' => 'groups'); 3705 $groups = $GLOBALS['egw']->accounts->link_query($_searchString, $group_options); 3706 foreach($groups as $g_id => $name) 3707 { 3708 $group = $GLOBALS['egw']->accounts->read($g_id); 3709 if(!$group['account_email']) continue; 3710 $args = explode('@', trim($group['account_email'])); 3711 $args[] = $name; 3712 $completeMailString = call_user_func_array('imap_rfc822_write_address', $args); 3713 $results[] = array( 3714 'id' => $completeMailString, 3715 'label' => $completeMailString, 3716 'name' => $name, 3717 'title' => $group['account_email'] 3718 ); 3719 } 3720 3721 // switch regular JSON response handling off 3722 Api\Json\Request::isJSONRequest(false); 3723 3724 //error_log(__METHOD__.__LINE__.array2string($jsArray)); 3725 header('Content-Type: application/json; charset=utf-8'); 3726 echo json_encode($results); 3727 exit(); 3728 } 3729 3730 /** 3731 * Get list of matching distribution lists when searching for email addresses 3732 * 3733 * The results are limited by config setting. Default 10 each of group lists and normal lists 3734 * 3735 * @param String $_searchString 3736 * @param Contacts $contacts_obj 3737 * @return array 3738 */ 3739 protected static function get_lists($_searchString, &$contacts_obj) 3740 { 3741 $group_lists = array(); 3742 $manual_lists = array(); 3743 $lists = array_filter( 3744 $contacts_obj->get_lists(Acl::READ), 3745 function($element) use($_searchString) { 3746 return (stripos($element, $_searchString) !== false); 3747 } 3748 ); 3749 3750 foreach($lists as $key => $list_name) 3751 { 3752 $type = $key > 0 ? 'manual' : 'group'; 3753 $list = array( 3754 'id' => $key, 3755 'name' => $list_name, 3756 'label' => $list_name, 3757 'class' => 'mailinglist ' . "{$type}_list", 3758 'title' => lang('Mailinglist'), 3759 'data' => $key 3760 ); 3761 ${"${type}_lists"}[] = $list; 3762 } 3763 $config = Api\Config::read('mail'); 3764 $limit = $config['address_list_limit'] ?: 10; 3765 $trim = function($list) use ($limit) { 3766 if(count($list) <= $limit) return $list; 3767 $list[$limit-1]['class'].= ' more_results'; 3768 $list[$limit-1]['title'] .= ' (' . lang('%1 more', count($list) - $limit) . ')'; 3769 return array_slice($list, 0, $limit); 3770 }; 3771 return array_merge($trim($group_lists), $trim($manual_lists)); 3772 } 3773 /** 3774 * Merge the selected contact ID into the document given in $_REQUEST['document'] 3775 * and send it. 3776 * 3777 * @param int $contact_id 3778 */ 3779 public function ajax_merge($contact_id) 3780 { 3781 $response = Api\Json\Response::get(); 3782 if(class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge')) 3783 { 3784 $document_merge = new $_REQUEST['merge'](); 3785 } 3786 else 3787 { 3788 $document_merge = new Api\Contacts\Merge(); 3789 } 3790 $this->mail_bo->openConnection(); 3791 3792 if(($error = $document_merge->check_document($_REQUEST['document'],''))) 3793 { 3794 $response->error($error); 3795 return; 3796 } 3797 3798 // Actually do the merge 3799 $folder = $merged_mail_id = null; 3800 try 3801 { 3802 $results = $this->mail_bo->importMessageToMergeAndSend( 3803 $document_merge, Vfs::PREFIX . $_REQUEST['document'], 3804 // Send an extra non-numeric ID to force actual send of document 3805 // instead of save as draft 3806 array((int)$contact_id, ''), 3807 $folder,$merged_mail_id 3808 ); 3809 3810 // Also save as infolog 3811 if($merged_mail_id && $_REQUEST['to_app'] && isset($GLOBALS['egw_info']['user']['apps'][$_REQUEST['to_app']])) 3812 { 3813 $rowid = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $merged_mail_id, true); 3814 $data = mail_integration::get_integrate_data($rowid); 3815 if($data && $_REQUEST['to_app'] == 'infolog') 3816 { 3817 $bo = new infolog_bo(); 3818 $entry = $bo->import_mail($data['addresses'],$data['subject'],$data['message'],$data['attachments'],$data['date']); 3819 if($_REQUEST['info_type'] && isset($bo->enums['type'][$_REQUEST['info_type']])) 3820 { 3821 $entry['info_type'] = $_REQUEST['info_type']; 3822 } 3823 $bo->write($entry); 3824 } 3825 } 3826 } 3827 catch (Exception $e) 3828 { 3829 $contact = $document_merge->contacts->read((int)$contact_id); 3830 //error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact)); 3831 $email = ($contact['email'] ? $contact['email'] : $contact['email_home']); 3832 $nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']); 3833 $response->error(lang('Sending mail to "%1" failed', "$nfn <$email>"). 3834 "\n".$e->getMessage() 3835 ); 3836 } 3837 3838 if($results['success']) 3839 { 3840 $response->data(implode(',',$results['success'])); 3841 } 3842 if($results['failed']) 3843 { 3844 $response->error(implode(',',$results['failed'])); 3845 } 3846 } 3847 3848 /** 3849 * Method to do encryption on given mail object 3850 * 3851 * @param Api\Mailer $mail 3852 * @param string $type encryption type 3853 * @param array|string $recipients list of recipients 3854 * @param string $sender email of sender 3855 * @param string $passphrase = '', SMIME Private key passphrase 3856 * 3857 * @return boolean returns true if successful and false if passphrase required 3858 * @throws Api\Exception\WrongUserinput if no certificate found 3859 */ 3860 protected function _encrypt($mail, $type, $recipients, $sender, $passphrase='') 3861 { 3862 $AB = new addressbook_bo(); 3863 // passphrase of sender private key 3864 $params['passphrase'] = $passphrase; 3865 3866 try 3867 { 3868 $sender_cert = $AB->get_smime_keys($sender); 3869 if (!$sender_cert) throw new Exception(lang("S/MIME Encryption failed because no certificate has been found for sender address: %1", $sender)); 3870 $params['senderPubKey'] = $sender_cert[strtolower($sender)]; 3871 3872 if (isset($sender) && ($type == Mail\Smime::TYPE_SIGN || $type == Mail\Smime::TYPE_SIGN_ENCRYPT)) 3873 { 3874 $acc_smime = Mail\Smime::get_acc_smime($this->mail_bo->profileID, $params['passphrase']); 3875 $params['senderPrivKey'] = $acc_smime['pkey']; 3876 $params['extracerts'] = $acc_smime['extracerts']; 3877 } 3878 3879 if (isset($recipients) && ($type == Mail\Smime::TYPE_ENCRYPT || $type == Mail\Smime::TYPE_SIGN_ENCRYPT)) 3880 { 3881 $params['recipientsCerts'] = $AB->get_smime_keys($recipients); 3882 foreach ($recipients as &$recipient) 3883 { 3884 if (!$params['recipientsCerts'][strtolower($recipient)]) $missingCerts []= $recipient; 3885 } 3886 if (is_array($missingCerts)) throw new Exception ('S/MIME Encryption failed because no certificate has been found for following addresses: '. implode ('|', $missingCerts)); 3887 } 3888 3889 return $mail->smimeEncrypt($type, $params); 3890 } 3891 catch(Api\Exception\WrongUserinput $e) 3892 { 3893 throw new $e; 3894 } 3895 } 3896 3897 /** 3898 * Builds attachments from provided UIDs and add them to sessionData 3899 * 3900 * @param string|array $_ids series of message ids 3901 * @param int $_serverID compose current profileID 3902 * 3903 * @return array returns an array of attachments 3904 * 3905 * @throws Exception throws exception on cross account attempt 3906 */ 3907 function _get_uids_as_attachments ($_ids, $_serverID) 3908 { 3909 $ids = is_array($_ids) ? $_ids : explode(',', $_ids); 3910 if (is_array($ids) && $_serverID) 3911 { 3912 $parts = mail_ui::splitRowID($ids[0]); 3913 if ($_serverID != $parts['profileID']) 3914 { 3915 throw new Exception(lang('Cross account forward attachment is not allowed!')); 3916 } 3917 } 3918 foreach ($ids as &$id) 3919 { 3920 $parts = mail_ui::splitRowID($id); 3921 $mail_bo = $this->mail_bo; 3922 $mail_bo->openConnection(); 3923 $mail_bo->reopen($parts['folder']); 3924 $headers = $mail_bo->getMessageEnvelope($parts['msgUID'], null,false,$parts['folder']); 3925 $this->addMessageAttachment($parts['msgUID'], null, $parts['folder'], 3926 $mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml', 3927 'MESSAGE/RFC822', $headers['SIZE'] ? $headers['SIZE'] : lang('unknown')); 3928 $mail_bo->closeConnection(); 3929 } 3930 return $this->sessionData['attachments']; 3931 } 3932} 3933