diff --git a/mail/inc/class.mail_bo.inc.php b/mail/inc/class.mail_bo.inc.php index 6a2fb7d545..522e01c474 100644 --- a/mail/inc/class.mail_bo.inc.php +++ b/mail/inc/class.mail_bo.inc.php @@ -312,6 +312,7 @@ class mail_bo */ private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0) { + if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset; if ($_restoreSession) { //error_log(__METHOD__." Session restore ".function_backtrace()); @@ -2721,18 +2722,6 @@ class mail_bo return $_string; } - /** - * strip tags out of the message completely with their content - * param $_body is the text to be processed - * param $tag is the tagname which is to be removed. Note, that only the name of the tag is to be passed to the function - * without the enclosing brackets - * param $endtag can be different from tag but should be used only, if begin and endtag are known to be different e.g.: - */ - static function replaceTagsCompletley(&$_body,$tag,$endtag='',$addbracesforendtag=true) - { - translation::replaceTagsCompletley($_body,$tag,$endtag,$addbracesforendtag); - } - /** * clean a message from elements regarded as potentially harmful * param string/reference $_html is the text to be processed @@ -2751,10 +2740,10 @@ class mail_bo $_html = str_replace(array('&','

',"
 
",'
 
','','
','','','',''), array('&', '
', '
', '
', '','', '', '', '', ''),$_html); //$_html = str_replace(array('&'),array('&'),$_html); - if (stripos($_html,'style')!==false) self::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags - if (stripos($_html,'head')!==false) self::replaceTagsCompletley($_html,'head'); // Strip out stuff in head - //if (stripos($_html,'![if')!==false && stripos($_html,'')!==false) self::replaceTagsCompletley($_html,'!\[if','',false); // Strip out stuff in ifs - //if (stripos($_html,'!--[if')!==false && stripos($_html,'')!==false) self::replaceTagsCompletley($_html,'!--\[if','',false); // Strip out stuff in ifs + if (stripos($_html,'style')!==false) translation::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags + if (stripos($_html,'head')!==false) translation::replaceTagsCompletley($_html,'head'); // Strip out stuff in head + //if (stripos($_html,'![if')!==false && stripos($_html,'')!==false) translation::replaceTagsCompletley($_html,'!\[if','',false); // Strip out stuff in ifs + //if (stripos($_html,'!--[if')!==false && stripos($_html,'')!==false) translation::replaceTagsCompletley($_html,'!--\[if','',false); // Strip out stuff in ifs //error_log(__METHOD__.__LINE__.$_html); // force the use of kses, as it is still have the edge over purifier with some stuff $usepurify = true; @@ -2763,10 +2752,10 @@ class mail_bo // we need a customized config, as we may allow external images, $GLOBALS['egw_info']['user']['preferences']['mail']['allowExternalIMGs'] if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html); // Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards - if (stripos($_html,'!doctype')!==false) self::replaceTagsCompletley($_html,'!doctype'); - if (stripos($_html,'?xml:namespace')!==false) self::replaceTagsCompletley($_html,'\?xml:namespace','/>',false); - if (stripos($_html,'?xml version')!==false) self::replaceTagsCompletley($_html,'\?xml version','\?>',false); - if (strpos($_html,'!CURSOR')!==false) self::replaceTagsCompletley($_html,'!CURSOR'); + if (stripos($_html,'!doctype')!==false) translation::replaceTagsCompletley($_html,'!doctype'); + if (stripos($_html,'?xml:namespace')!==false) translation::replaceTagsCompletley($_html,'\?xml:namespace','/>',false); + if (stripos($_html,'?xml version')!==false) translation::replaceTagsCompletley($_html,'\?xml version','\?>',false); + if (strpos($_html,'!CURSOR')!==false) translation::replaceTagsCompletley($_html,'!CURSOR'); // htmLawed filter only the 'body' //preg_match('`(]*>)(.+?)(.*?)`ims', $_html, $matches); //if ($matches[2]) @@ -3565,6 +3554,70 @@ class mail_bo return $retValue; } + /** + * getStyles - extracts the styles from the given bodyparts + * @param array $bodyParts with the bodyparts + * @return string a preformatted string with the mails converted to text + */ + static function &getStyles($_bodyParts) + { + $style = ''; + if (empty($_bodyParts)) return ""; + foreach((array)$_bodyParts as $singleBodyPart) { + if (!isset($singleBodyPart['body'])) { + $singleBodyPart['body'] = self::getStyles($singleBodyPart); + $style .= $singleBodyPart['body']; + continue; + } + + if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = translation::detect_encoding($singleBodyPart['body']); + $singleBodyPart['body'] = translation::convert( + $singleBodyPart['body'], + strtolower($singleBodyPart['charSet']) + ); + $ct = 0; + + if (stripos($singleBodyPart['body'],'(.+)#isU', $singleBodyPart['body'], $newStyle); + if ($ct>0) + { + //error_log(__METHOD__.__LINE__.array2string($newStyle[0])); + $style2buffer = implode('',$newStyle[0]); + } + if ($style2buffer && strtoupper(self::$displayCharset) == 'UTF-8') + { + //error_log(__METHOD__.__LINE__.array2string($style2buffer)); + $test = json_encode($style2buffer); + //error_log(__METHOD__.__LINE__.'#'.$test.'# ->'.strlen($style2buffer).' Error:'.json_last_error()); + //if (json_last_error() != JSON_ERROR_NONE && strlen($style2buffer)>0) + if ($test=="null" && strlen($style2buffer)>0) + { + // this should not be needed, unless something fails with charset detection/ wrong charset passed + error_log(__METHOD__.__LINE__.' Found Invalid sequence for utf-8 in CSS:'.$style2buffer.' Charset Reported:'.$singleBodyPart['charSet'].' Carset Detected:'.translation::detect_encoding($style2buffer)); + $style2buffer = utf8_encode($style2buffer); + } + } + $style .= $style2buffer; + } + // clean out comments and stuff + $search = array( + '@url\(http:\/\/[^\)].*?\)@si', // url calls e.g. in style definitions +// '@@', // Strip multi-line comments including CDATA +// '@ in stylesheet are outdated, and ck-editor does not understand it, we remove it + $css = str_replace(array(':',''),array(': ','',''),$css); + //error_log(__METHOD__.__LINE__.$css); + // TODO: we may have to strip urls and maybe comments and ifs + return $css; + } + /** * getMessageRawBody * get the message raw body @@ -3952,6 +4005,120 @@ class mail_bo return $attachmentData; } + /** + * Fetch a specific attachment from a message by it's cid + * + * this function is based on a on "Building A PHP-Based Mail Client" + * http://www.devshed.com + * + * @param string|int $_uid + * @param string $_cid + * @param string $_part + * @return array with values for keys 'type', 'filename' and 'attachment' + */ + function getAttachmentByCID($_uid, $_cid, $_part) + { + // some static variables to avoid fetching the same mail multiple times + static $uid,$part,$attachments,$structure; + //error_log("getAttachmentByCID:$_uid, $_cid, $_part"); + + if(empty($_cid)) return false; + + if ($_uid != $uid || $_part != $part) + { + $attachments = $this->getMessageAttachments($uid=$_uid, $part=$_part); + $structure = null; + } + $partID = false; + foreach($attachments as $attachment) + { + //error_log(print_r($attachment,true)); + if(isset($attachment['cid']) && (strpos($attachment['cid'], $_cid) !== false || strpos($_cid, $attachment['cid']) !== false)) + { + $partID = $attachment['partID']; + break; + } + } + if ($partID == false) + { + foreach($attachments as $attachment) + { + // if the former did not match try matching the cid to the name of the attachment + if(isset($attachment['cid']) && isset($attachment['name']) && (strpos($attachment['name'], $_cid) !== false || strpos($_cid, $attachment['name']) !== false)) + { + $partID = $attachment['partID']; + break; + } + } + } + if ($partID == false) + { + foreach($attachments as $attachment) + { + // if the former did not match try matching the cid to the name of the attachment, AND there is no mathing attachment with cid + if(isset($attachment['name']) && (strpos($attachment['name'], $_cid) !== false || strpos($_cid, $attachment['name']) !== false)) + { + $partID = $attachment['partID']; + break; + } + } + } + //error_log( "Cid:$_cid PARTID:$partID
"); #exit; + + if($partID == false) { + return false; + } + + // parse message structure + if (is_null($structure)) + { + $structure = $this->_getStructure($_uid, true); + } + $part_structure = $this->_getSubStructure($structure, $partID); + $filename = $this->getFileNameFromStructure($part_structure, $_uid, $_uid, $part_structure->partID); + $attachment = $this->icServer->getBodyPart($_uid, $partID, true); + if (PEAR::isError($attachment)) + { + error_log(__METHOD__.__LINE__.' failed:'.$attachment->message); + return array('type' => 'text/plain', + 'filename' => 'error.txt', + 'attachment' =>__METHOD__.' failed:'.$attachment->message + ); + } + + if (PEAR::isError($attachment)) + { + error_log(__METHOD__.__LINE__.' failed:'.$attachment->message); + return array('type' => 'text/plain', + 'filename' => 'error.txt', + 'attachment' =>__METHOD__.' failed:'.$attachment->message + ); + } + + switch ($part_structure->encoding) { + case 'BASE64': + // use imap_base64 to decode + $attachment = imap_base64($attachment); + break; + case 'QUOTED-PRINTABLE': + // use imap_qprint to decode + #$attachment = imap_qprint($attachment); + $attachment = quoted_printable_decode($attachment); + break; + default: + // it is either not encoded or we don't know about it + } + + $attachmentData = array( + 'type' => $part_structure->type .'/'. $part_structure->subType, + 'filename' => $filename, + 'attachment' => $attachment + ); + // try guessing the mimetype, if we get the application/octet-stream + if (strtolower($attachmentData['type']) == 'application/octet-stream') $attachmentData['type'] = mime_magic::filename2mime($attachmentData['filename']); + + return $attachmentData; + } /** * functions to allow access to mails through other apps to fetch content diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 4f47e1f764..fe367c46f1 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -10,6 +10,8 @@ * @version $Id$ */ +include_once(EGW_INCLUDE_ROOT.'/etemplate/inc/class.etemplate.inc.php'); + /** * Mail Interface class */ @@ -26,6 +28,7 @@ class mail_ui 'displayHeader' => True, 'saveMessage' => True, 'vfsSaveMessage' => True, + 'loadEmailBody' => True, 'importMessage' => True, 'TestConnection' => True, ); @@ -183,7 +186,7 @@ class mail_ui $sel_options['cat_id'] = array(1=>'none'); if (!isset($content['nm']['cat_id'])) $content['nm']['cat_id'] = 'All'; - $etpl = new etemplate('mail.index'); + $etpl = new etemplate_new('mail.index'); // Set tree actions $etpl->set_cell_attribute('nm[foldertree]','actions', array( @@ -1504,6 +1507,453 @@ unset($query['actions']); return $err.'window.close();'; } + + function get_load_email_data($uid, $partID, $mailbox) + { + // seems to be needed, as if we open a mail from notification popup that is + // located in a different folder, we experience: could not parse message + $this->mail_bo->reopen($mailbox); +$this->mailbox = $mailbox; +$this->uid = $uid; +$this->partID = $partID; + $bodyParts = $this->mail_bo->getMessageBody($uid, '', $partID, '', false, $mailbox); + //error_log(__METHOD__.__LINE__.array2string($bodyParts)); + $meetingRequest = false; + $fetchEmbeddedImages = false; + if ($this->mail_bo->htmlOptions !='always_display') $fetchEmbeddedImages = true; + $attachments = $this->mail_bo->getMessageAttachments($uid, $partID, '',$fetchEmbeddedImages,true); + foreach ((array)$attachments as $key => $attach) + { + if (strtolower($attach['mimeType']) == 'text/calendar' && + (strtolower($attach['method']) == 'request' || strtolower($attach['method']) == 'reply') && + isset($GLOBALS['egw_info']['user']['apps']['calendar']) && + ($attachment = $this->mail_bo->getAttachment($uid, $attach['partID']))) + { + egw_cache::setSession('calendar', 'ical', array( + 'charset' => $attach['charset'] ? $attach['charset'] : 'utf-8', + 'attachment' => $attachment['attachment'], + 'method' => $attach['method'], + 'sender' => $sender, + )); + return array("src"=>egw::link('/index.php',array( + 'menuaction' => 'calendar.calendar_uiforms.meeting', + 'ical' => 'session', + ))); + } + } + + // Compose the content of the frame + $frameHtml = + $this->get_email_header($this->mail_bo->getStyles($bodyParts)). + $this->showBody($this->getdisplayableBody($bodyParts), false); + //IE10 eats away linebreaks preceeded by a whitespace in PRE sections + $frameHtml = str_replace(" \r\n","\r\n",$frameHtml); + + return $frameHtml; + } + + static function get_email_header($additionalStyle='') + { + //error_log(__METHOD__.__LINE__.$additionalStyle); + return ' + + + + + '.$additionalStyle.' + + + +'; + } + + function showBody(&$body, $print=true) + { + $BeginBody = ' +
+
'; + + $EndBody = '
'; + $EndBody .= ""; + if ($print) { + print $BeginBody. $body .$EndBody; + } else { + return $BeginBody. $body .$EndBody; + } + } + + function &getdisplayableBody($_bodyParts,$modifyURI=true) + { + $bodyParts = $_bodyParts; + + $webserverURL = $GLOBALS['egw_info']['server']['webserver_url']; + + $nonDisplayAbleCharacters = array('[\016]','[\017]', + '[\020]','[\021]','[\022]','[\023]','[\024]','[\025]','[\026]','[\027]', + '[\030]','[\031]','[\032]','[\033]','[\034]','[\035]','[\036]','[\037]'); + + $body = ''; + + //error_log(__METHOD__.array2string($bodyParts)); //exit; + if (empty($bodyParts)) return ""; + foreach((array)$bodyParts as $singleBodyPart) { + if (!isset($singleBodyPart['body'])) { + $singleBodyPart['body'] = $this->getdisplayableBody($singleBodyPart,$modifyURI); + $body .= $singleBodyPart['body']; + continue; + } + if(!empty($body)) { + $body .= '
'; + } + //_debug_array($singleBodyPart['charSet']); + //_debug_array($singleBodyPart['mimeType']); + //error_log($singleBodyPart['body']); + //error_log(__METHOD__.__LINE__.' CharSet:'.$singleBodyPart['charSet'].' mimeType:'.$singleBodyPart['mimeType']); + // some characterreplacements, as they fail to translate + $sar = array( + '@(\x84|\x93|\x94)@', + '@(\x96|\x97|\x1a)@', + '@(\x82|\x91|\x92)@', + '@(\x85)@', + '@(\x86)@', + '@(\x99)@', + '@(\xae)@', + ); + $rar = array( + '"', + '-', + '\'', + '...', + '&', + '(TM)', + '(R)', + ); + + if(($singleBodyPart['mimeType'] == 'text/html' || $singleBodyPart['mimeType'] == 'text/plain') && + strtoupper($singleBodyPart['charSet']) != 'UTF-8') + { + $singleBodyPart['body'] = preg_replace($sar,$rar,$singleBodyPart['body']); + } + if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = translation::detect_encoding($singleBodyPart['body']); + $singleBodyPart['body'] = $GLOBALS['egw']->translation->convert( + $singleBodyPart['body'], + strtolower($singleBodyPart['charSet']) + ); + // 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 + if (strtoupper(mail_bo::$displayCharset) == 'UTF-8') + { + $test = @json_encode($singleBodyPart['body']); + //error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#'); + //if (json_last_error() != JSON_ERROR_NONE && strlen($singleBodyPart['body'])>0) + if (($test=="null" || $test === false || is_null($test)) && strlen($singleBodyPart['body'])>0) + { + // this should not be needed, unless something fails with charset detection/ wrong charset passed + error_log(__METHOD__.__LINE__.' Charset Reported:'.$singleBodyPart['charSet'].' Charset Detected:'.felamimail_bo::detect_encoding($singleBodyPart['body'])); + $singleBodyPart['body'] = utf8_encode($singleBodyPart['body']); + } + } + //error_log($singleBodyPart['body']); + #$CharSetUsed = mb_detect_encoding($singleBodyPart['body'] . 'a' , strtoupper($singleBodyPart['charSet']).','.strtoupper(mail_bo::$displayCharset).',UTF-8, ISO-8859-1'); + + if($singleBodyPart['mimeType'] == 'text/plain') + { + //$newBody = $singleBodyPart['body']; + + $newBody = @htmlentities($singleBodyPart['body'],ENT_QUOTES, strtoupper(mail_bo::$displayCharset)); + // if empty and charset is utf8 try sanitizing the string in question + if (empty($newBody) && strtolower($singleBodyPart['charSet'])=='utf-8') $newBody = @htmlentities(iconv('utf-8', 'utf-8', $singleBodyPart['body']),ENT_QUOTES, strtoupper(mail_bo::$displayCharset)); + // if the conversion to htmlentities fails somehow, try without specifying the charset, which defaults to iso- + if (empty($newBody)) $newBody = htmlentities($singleBodyPart['body'],ENT_QUOTES); + #$newBody = $this->bofelamimail->wordwrap($newBody, 90, "\n"); + + // search http[s] links and make them as links available again + // to understand what's going on here, have a look at + // http://www.php.net/manual/en/function.preg-replace.php + + // create links for websites + if ($modifyURI) $newBody = html::activate_links($newBody); + // redirect links for websites if you use no cookies + #if (!($GLOBALS['egw_info']['server']['usecookies'])) + # $newBody = preg_replace("/href=(\"|\')((http(s?):\/\/)|(www\.))([\w,\-,\/,\?,\=,\.,&,!\n,\%,@,\(,\),\*,#,:,~,\+]+)(\"|\')/ie", + # "'href=\"$webserverURL/redirect.php?go='.@htmlentities(urlencode('http$4://$5$6'),ENT_QUOTES,\"mail_bo::$displayCharset\").'\"'", $newBody); + + // create links for email addresses + //TODO:if ($modifyURI) $this->parseEmail($newBody); + // create links for inline images + if ($modifyURI) + { + $newBody = preg_replace_callback("/\[cid:(.*)\]/iU",array($this,'image_callback_plain'),$newBody); + } + + //TODO:$newBody = $this->highlightQuotes($newBody); + // to display a mailpart of mimetype plain/text, may be better taged as preformatted + #$newBody = nl2br($newBody); + // since we do not display the message as HTML anymore we may want to insert good linebreaking (for visibility). + //error_log($newBody); + // dont break lines that start with > (> as the text was processed with htmlentities before) + //TODO:$newBody = "
".felamimail_bo::wordwrap($newBody,90,"\n",'>')."
"; + //$newBody = "
".$newBody."
"; + } + else + { + $newBody = $singleBodyPart['body']; + //TODO:$newBody = $this->highlightQuotes($newBody); + #error_log(print_r($newBody,true)); + + // do the cleanup, set for the use of purifier + $usepurifier = true; + $newBodyBuff = $newBody; + mail_bo::getCleanHTML($newBody,$usepurifier); + // 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 + if (strtoupper(mail_bo::$displayCharset) == 'UTF-8') + { + $test = @json_encode($newBody); + //error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#'); + //if (json_last_error() != JSON_ERROR_NONE && strlen($singleBodyPart['body'])>0) + if (($test=="null" || $test === false || is_null($test)) && strlen($newBody)>0) + { + $newBody = $newBodyBuff; + $tv = mail_bo::$htmLawed_config['tidy']; + mail_bo::$htmLawed_config['tidy'] = 0; + mail_bo::getCleanHTML($newBody,$usepurifier); + mail_bo::$htmLawed_config['tidy'] = $tv; + } + } + + // removes stuff between http and ?http + $Protocol = '(http:\/\/|(ftp:\/\/|https:\/\/))'; // only http:// gets removed, other protocolls are shown + $newBody = preg_replace('~'.$Protocol.'[^>]*\?'.$Protocol.'~sim','$1',$newBody); // removes stuff between http:// and ?http:// + // TRANSFORM MAILTO LINKS TO EMAILADDRESS ONLY, WILL BE SUBSTITUTED BY parseEmail TO CLICKABLE LINK + $newBody = preg_replace('/(?parseHREF($newBody); + #} + // create links for inline images + if ($modifyURI) + { + $newBody = preg_replace_callback("/src=(\"|\')cid:(.*)(\"|\')/iU",array($this,'image_callback'),$newBody); + $newBody = preg_replace_callback("/url\(cid:(.*)\);/iU",array($this,'image_callback_url'),$newBody); + $newBody = preg_replace_callback("/background=(\"|\')cid:(.*)(\"|\')/iU",array($this,'image_callback_background'),$newBody); + } + + // create links for email addresses + if ($modifyURI) + { + $link = $GLOBALS['egw']->link('/index.php',array('menuaction' => 'felamimail.uicompose.compose')); + $newBody = preg_replace("/href=(\"|\')mailto:([\w,\-,\/,\?,\=,\.,&,!\n,\%,@,\*,#,:,~,\+]+)(\"|\')/ie", + "'href=\"$link&send_to='.base64_encode('$2').'\"'.' target=\"compose\" onclick=\"window.open(this,this.target,\'dependent=yes,width=700,height=egw_getWindowOuterHeight(),location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false;\"'", $newBody); + //print "
".htmlentities($newBody)."

"; + } + // replace emails within the text with clickable links. + //TODO:$this->parseEmail($newBody); + } + + $body .= $newBody; + #print "
$body

"; + } + // create links for windows shares + // \\\\\\\\ == '\\' in real life!! :) + $body = preg_replace("/(\\\\\\\\)([\w,\\\\,-]+)/i", + "$1$2", $body); + + $body = preg_replace($nonDisplayAbleCharacters,'',$body); + + return $body; + } + + /** + * preg_replace callback to replace image cid url's + * + * @param array $matches matches from preg_replace("/src=(\"|\')cid:(.*)(\"|\')/iU",...) + * @return string src attribute to replace + */ + function image_callback($matches) + { + static $cache = array(); // some caching, if mails containing the same image multiple times + $this->icServer->currentMailbox; + $linkData = array ( + 'menuaction' => 'felamimail.uidisplay.displayImage', + 'uid' => $this->uid, + 'mailbox' => base64_encode($this->mailbox), + 'cid' => base64_encode($matches[2]), + 'partID' => $this->partID, + ); + $imageURL = $GLOBALS['egw']->link('/index.php', $linkData); + + // to test without data uris, comment the if close incl. it's body + if (html::$user_agent != 'msie' || html::$ua_version >= 8) + { + if (!isset($cache[$imageURL])) + { + $attachment = $this->mail_bo->getAttachmentByCID($this->uid, $matches[2], $this->partID); + + // only use data uri for "smaller" images, as otherwise the first display of the mail takes to long + if (bytes($attachment['attachment']) < 8192) // msie=8 allows max 32k data uris + { + $cache[$imageURL] = 'data:'.$attachment['type'].';base64,'.base64_encode($attachment['attachment']); + } + else + { + $cache[$imageURL] = $imageURL; + } + } + $imageURL = $cache[$imageURL]; + } + return 'src="'.$imageURL.'"'; + } + + /** + * preg_replace callback to replace image cid url's + * + * @param array $matches matches from preg_replace("/src=(\"|\')cid:(.*)(\"|\')/iU",...) + * @return string src attribute to replace + */ + function image_callback_plain($matches) + { + static $cache = array(); // some caching, if mails containing the same image multiple times + //error_log(__METHOD__.__LINE__.array2string($matches)); + $linkData = array ( + 'menuaction' => 'felamimail.uidisplay.displayImage', + 'uid' => $this->uid, + 'mailbox' => base64_encode($this->mailbox), + 'cid' => base64_encode($matches[1]), + 'partID' => $this->partID, + ); + $imageURL = $GLOBALS['egw']->link('/index.php', $linkData); + + // to test without data uris, comment the if close incl. it's body + if (html::$user_agent != 'msie' || html::$ua_version >= 8) + { + if (!isset($cache[$imageURL])) + { + $attachment = $this->mail_bo->getAttachmentByCID($this->uid, $matches[1], $this->partID); + + // only use data uri for "smaller" images, as otherwise the first display of the mail takes to long + if (bytes($attachment['attachment']) < 8192) // msie=8 allows max 32k data uris + { + $cache[$imageURL] = 'data:'.$attachment['type'].';base64,'.base64_encode($attachment['attachment']); + } + else + { + $cache[$imageURL] = $imageURL; + } + } + $imageURL = $cache[$imageURL]; + } + return ''; + } + + /** + * preg_replace callback to replace image cid url's + * + * @param array $matches matches from preg_replace("/src=(\"|\')cid:(.*)(\"|\')/iU",...) + * @return string src attribute to replace + */ + function image_callback_url($matches) + { + static $cache = array(); // some caching, if mails containing the same image multiple times + //error_log(__METHOD__.__LINE__.array2string($matches)); + $linkData = array ( + 'menuaction' => 'felamimail.uidisplay.displayImage', + 'uid' => $this->uid, + 'mailbox' => base64_encode($this->mailbox), + 'cid' => base64_encode($matches[1]), + 'partID' => $this->partID, + ); + $imageURL = $GLOBALS['egw']->link('/index.php', $linkData); + + // to test without data uris, comment the if close incl. it's body + if (html::$user_agent != 'msie' || html::$ua_version >= 8) + { + if (!isset($cache[$imageURL])) + { + $attachment = $this->mail_bo->getAttachmentByCID($this->uid, $matches[1], $this->partID); + + // only use data uri for "smaller" images, as otherwise the first display of the mail takes to long + if (bytes($attachment['attachment']) < 8192) // msie=8 allows max 32k data uris + { + $cache[$imageURL] = 'data:'.$attachment['type'].';base64,'.base64_encode($attachment['attachment']); + } + else + { + $cache[$imageURL] = $imageURL; + } + } + $imageURL = $cache[$imageURL]; + } + return 'url('.$imageURL.');'; + } + + /** + * preg_replace callback to replace image cid url's + * + * @param array $matches matches from preg_replace("/src=(\"|\')cid:(.*)(\"|\')/iU",...) + * @return string src attribute to replace + */ + function image_callback_background($matches) + { + static $cache = array(); // some caching, if mails containing the same image multiple times + $linkData = array ( + 'menuaction' => 'felamimail.uidisplay.displayImage', + 'uid' => $this->uid, + 'mailbox' => base64_encode($this->mailbox), + 'cid' => base64_encode($matches[2]), + 'partID' => $this->partID, + ); + $imageURL = $GLOBALS['egw']->link('/index.php', $linkData); + + // to test without data uris, comment the if close incl. it's body + if (html::$user_agent != 'msie' || html::$ua_version >= 8) + { + if (!isset($cache[$imageURL])) + { + $cache[$imageURL] = $imageURL; + } + $imageURL = $cache[$imageURL]; + } + return 'background="'.$imageURL.'"'; + } + /** * importMessage */ @@ -1661,6 +2111,26 @@ unset($query['actions']); */ } + /** + * loadEmailBody + * + * @param string _messageID UID + * + * @return xajax response + */ + function loadEmailBody($_messageID) + { + if (!$_messageID) $_messageID = $_GET['_messageID']; + if(mail_bo::$debug); error_log(__METHOD__."->".$_flag.':'.print_r($_messageID,true)); + $uidA = self::splitRowID($_messageID); + $folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder + $messageID = $uidA['msgUID']; + $bodyResponse = $this->get_load_email_data($messageID,'',$folder); + //error_log(array2string($bodyResponse)); + echo $bodyResponse; + + } + /** * ajax_setFolderStatus - its called via json, so the function must start with ajax (or the class-name must contain ajax) * gets the counters and sets the text of a treenode if needed (unread Messages found) diff --git a/mail/js/app.js b/mail/js/app.js index 194465b68a..cf6346cb06 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -47,13 +47,13 @@ mail_open: function(_action, _senders) { }, /** - * mail_preview - implementation of the copy action + * mail_preview - implementation of the preview action * * @param nextmatch et2_nextmatch The widget whose row was selected * @param selected Array Selected row IDs. May be empty if user unselected all rows. */ mail_preview: function(nextmatch, selected) { - //console.log("mail_preview",_action, _senders); + //console.log("mail_preview",nextmatch, selected); // Empty values, just in case selected is empty (user cleared selection) var dataElem = {data:{subject:"",fromaddress:"",toaddress:"",date:"",subject:""}}; @@ -62,12 +62,27 @@ mail_preview: function(nextmatch, selected) { var _id = selected[0]; dataElem = egw.dataGetUIDdata(_id); } + else + { + return; + } console.log("mail_preview",dataElem); var subject =dataElem.data.subject; etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('previewFromAddress').set_value(dataElem.data.fromaddress); etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('previewToAddress').set_value(dataElem.data.toaddress); etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('previewDate').set_value(dataElem.data.date); etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('previewSubject').set_value(subject); + var IframeHandle = etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('messageIFRAME'); + IframeHandle.set_src(egw.link('/index.php',{menuaction:'mail.mail_ui.loadEmailBody',_messageID:_id})); + + +// var request = new egw_json_request('mail.mail_ui.ajax_loadEmailBody',[_id]); +// request.sendRequest(false); +}, +mail_setMailBody: function(content) { + console.log('mail_setMailBody',content); + var IframeHandle = etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('messageIFRAME'); + IframeHandle.set_value(''); }, /** @@ -230,7 +245,7 @@ mail_delete: function(_action,_elems) { var msg = this.mail_getFormData(_elems); //alert(_action.id+','+ msg); - app_refresh(egw.lang('delete messages'), 'mail'); + app.mail.app_refresh(egw.lang('delete messages'), 'mail'); this.mail_setRowClass(_elems,'deleted'); var request = new egw_json_request('mail.mail_ui.ajax_deleteMessages',[msg]); request.sendRequest(false); @@ -262,7 +277,7 @@ mail_undeleteMessages: function(_messageList) { * mail_emptyTrash */ mail_emptyTrash: function() { - app_refresh(egw.lang('empty trash'), 'mail'); + app.mail.app_refresh(egw.lang('empty trash'), 'mail'); var request = new egw_json_request('mail.mail_ui.ajax_emptyTrash'); request.sendRequest(); }, @@ -271,7 +286,7 @@ mail_emptyTrash: function() { * mail_compressFolder */ mail_compressFolder: function() { - app_refresh(egw.lang('compress folder'), 'mail'); + app.mail.app_refresh(egw.lang('compress folder'), 'mail'); var request = new egw_json_request('mail.mail_ui.ajax_compressFolder'); request.sendRequest(); }, @@ -297,7 +312,7 @@ mail_changeProfile: function(folder,_widget) { */ mail_changeFolder: function(folder,_widget) { //alert('change Folder called:'+folder); - app_refresh(egw.lang('change folder')+'...', 'mail'); + app.mail.app_refresh(egw.lang('change folder')+'...', 'mail'); var img = _widget.getSelectedNode().images[0]; // fetch first image if (!(img.search(eval('/'+'NoSelect'+'/'))<0) || !(img.search(eval('/'+'thunderbird'+'/'))<0)) { @@ -334,7 +349,7 @@ mail_changeFolder: function(folder,_widget) { if (outBraket!=-1) displayname = displayname.replace(/\((.*?)\)/,""); } myMsg = (displayname?displayname:folder)+' '+egw.lang('selected'); - app_refresh(myMsg, 'mail'); + app.mail.app_refresh(myMsg, 'mail'); } //mail_refreshMessageGrid(); this.mail_refreshFolderStatus(folder,'forced'); @@ -363,7 +378,7 @@ mail_flag: function(_action, _elems) */ mail_flagMessages: function(_flag, _elems) { - app_refresh(egw.lang('flag messages'), 'mail'); + app.mail.app_refresh(egw.lang('flag messages'), 'mail'); var request = new egw_json_request('mail.mail_ui.ajax_flagMessages',[_flag, _elems]); request.sendRequest(false); this.mail_refreshMessageGrid() diff --git a/mail/setup/etemplates.inc.php b/mail/setup/etemplates.inc.php index 754eb1ded0..0231c53d97 100644 --- a/mail/setup/etemplates.inc.php +++ b/mail/setup/etemplates.inc.php @@ -2,7 +2,7 @@ /** * EGroupware - eTemplates for Application mail * http://www.egroupware.org - * generated by soetemplate::dump4setup() 2013-04-09 15:37 + * generated by soetemplate::dump4setup() 2013-04-12 14:54 * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package mail @@ -12,7 +12,9 @@ $templ_version=1; -$templ_data[] = array('name' => 'mail.index','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:4:{i:0;a:5:{s:7:"onclick";s:56:"app.mail.mail_changeFolder(widget.event_args[0],widget);";s:4:"name";s:14:"nm[foldertree]";s:4:"type";s:4:"tree";s:11:"parent_node";s:11:"tree_target";s:11:"autoloading";s:28:"mail.mail_ui.ajax_foldertree";}i:1;a:2:{s:4:"type";s:4:"html";s:4:"name";s:3:"msg";}i:2;a:3:{s:4:"name";s:2:"nm";s:4:"size";s:15:"mail.index.rows";s:4:"type";s:9:"nextmatch";}i:3;a:3:{s:4:"type";s:8:"template";s:4:"name";s:7:"preview";s:4:"size";s:12:"mail.preview";}}','size' => '','style' => '','modified' => '1365514624',); +$templ_data[] = array('name' => 'mail.importMessage','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:1:{s:4:"type";s:5:"label";}}}s:4:"rows";i:1;s:4:"cols";i:1;}}','size' => '','style' => '','modified' => '1365666381',); + +$templ_data[] = array('name' => 'mail.index','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:3:{i:0;a:5:{s:11:"autoloading";s:28:"mail.mail_ui.ajax_foldertree";s:7:"onclick";s:56:"app.mail.mail_changeFolder(widget.event_args[0],widget);";s:11:"parent_node";s:11:"tree_target";s:4:"name";s:14:"nm[foldertree]";s:4:"type";s:4:"tree";}i:1;a:2:{s:4:"name";s:3:"msg";s:4:"type";s:4:"html";}i:2;a:7:{s:9:"dock_side";s:10:"bottomDock";s:11:"orientation";s:1:"h";s:4:"name";s:8:"splitter";s:4:"type";s:5:"split";s:4:"size";s:1:"2";i:1;a:4:{s:8:"onselect";s:21:"app.mail.mail_preview";s:4:"name";s:2:"nm";s:4:"type";s:9:"nextmatch";s:4:"size";s:15:"mail.index.rows";}i:2;a:4:{s:4:"name";s:18:"spanMessagePreview";s:4:"type";s:4:"vbox";s:4:"size";s:1:"1";i:1;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:1:{s:2:"c2";s:19:"previewDataArea,top";}i:1;a:1:{s:1:"A";a:4:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:0:{}i:1;a:2:{s:1:"A";a:4:{s:4:"type";s:4:"grid";s:4:"data";a:5:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"3";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:4:"From";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:1:":";}i:3;a:3:{s:8:"readonly";s:4:"true";s:4:"name";s:18:"previewFromAddress";s:4:"type";s:9:"url-email";}}}i:2;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"3";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:2:"To";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:1:":";}i:3;a:3:{s:8:"readonly";s:4:"true";s:4:"name";s:16:"previewToAddress";s:4:"type";s:9:"url-email";}}}i:3;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"3";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:4:"Date";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:1:":";}i:3;a:4:{s:5:"align";s:4:"left";s:8:"readonly";s:4:"true";s:4:"name";s:11:"previewDate";s:4:"type";s:9:"date-time";}}}i:4;a:1:{s:1:"A";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"3";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:7:"Subject";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:1:":";}i:3;a:4:{s:5:"align";s:4:"left";s:8:"readonly";s:4:"true";s:4:"name";s:14:"previewSubject";s:4:"type";s:5:"label";}}}}s:4:"cols";i:1;s:4:"rows";i:4;}s:1:"B";a:4:{s:5:"width";s:5:"275px";s:4:"type";s:4:"hbox";s:4:"size";s:1:"1";i:1;a:2:{s:4:"type";s:5:"label";s:5:"label";s:12:"Action Icons";}}}}s:4:"cols";i:2;s:4:"rows";i:1;}}i:2;a:1:{s:1:"A";a:6:{s:11:"frameborder";s:1:"1";s:6:"height";s:4:"auto";s:5:"width";s:4:"100%";s:9:"scrolling";s:4:"auto";s:4:"name";s:13:"messageIFRAME";s:4:"type";s:6:"iframe";}}}s:4:"cols";i:1;s:4:"rows";i:2;}}}}','size' => '','style' => '','modified' => '1365771294',); $templ_data[] = array('name' => 'mail.index.rows','template' => '','lang' => '','group' => '0','version' => '1.9.001','data' => 'a:1:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:9:{s:2:"c1";s:2:"th";s:1:"A";s:2:"25";s:1:"F";s:3:"120";s:1:"E";s:2:"95";s:2:"c2";s:16:"$row_cont[class]";s:1:"G";s:3:"120";s:1:"H";s:2:"50";s:1:"C";s:2:"20";s:1:"B";s:2:"20";}i:1;a:8:{s:1:"A";a:4:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:2:"ID";s:4:"name";s:3:"uid";s:8:"readonly";s:1:"1";}s:1:"B";a:4:{s:4:"type";s:16:"nextmatch-header";s:4:"name";s:6:"status";s:5:"label";s:3:"St.";s:4:"help";s:6:"Status";}s:1:"C";a:4:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:3:"...";s:4:"name";s:11:"attachments";s:4:"help";s:16:"attachments, ...";}s:1:"D";a:3:{s:4:"type";s:20:"nextmatch-sortheader";s:4:"name";s:7:"subject";s:5:"label";s:7:"subject";}s:1:"E";a:4:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:4:"date";s:4:"name";s:4:"date";s:5:"align";s:6:"center";}s:1:"F";a:3:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:2:"to";s:4:"name";s:9:"toaddress";}s:1:"G";a:3:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:4:"from";s:4:"name";s:11:"fromaddress";}s:1:"H";a:4:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:4:"size";s:4:"name";s:4:"size";s:5:"align";s:6:"center";}}i:2;a:8:{s:1:"A";a:3:{s:4:"type";s:5:"label";s:4:"name";s:11:"${row}[uid]";s:8:"readonly";s:1:"1";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:4:"span";s:12:"1,status_img";}s:1:"C";a:2:{s:4:"type";s:4:"html";s:4:"name";s:19:"${row}[attachments]";}s:1:"D";a:2:{s:4:"type";s:5:"label";s:4:"name";s:15:"${row}[subject]";}s:1:"E";a:4:{s:4:"type";s:15:"date-time_today";s:4:"name";s:12:"${row}[date]";s:8:"readonly";s:1:"1";s:5:"align";s:6:"center";}s:1:"F";a:3:{s:4:"type";s:9:"url-email";s:4:"name";s:17:"${row}[toaddress]";s:8:"readonly";s:1:"1";}s:1:"G";a:3:{s:4:"type";s:9:"url-email";s:4:"name";s:19:"${row}[fromaddress]";s:8:"readonly";s:1:"1";}s:1:"H";a:5:{s:4:"type";s:8:"vfs-size";s:4:"name";s:12:"${row}[size]";s:7:"no_lang";s:1:"1";s:8:"readonly";s:1:"1";s:5:"align";s:5:"right";}}}s:4:"rows";i:2;s:4:"cols";i:8;}}','size' => '','style' => '','modified' => '1360252030',); diff --git a/mail/templates/default/index.xet b/mail/templates/default/index.xet index 6abd98288c..ed108e0a98 100644 --- a/mail/templates/default/index.xet +++ b/mail/templates/default/index.xet @@ -41,22 +41,22 @@ - + - + - + - + @@ -91,7 +91,7 @@ - + @@ -99,7 +99,7 @@ -