2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
38 require_once('include/phpmailer/class.phpmailer.php');
39 require_once('include/OutboundEmail/OutboundEmail.php');
45 class SugarPHPMailer extends PHPMailer
47 var $oe; // OutboundEmail
48 var $protocol = "tcp://";
49 var $preppedForOutbound = false;
50 var $disclosureEnabled;
52 var $isHostEmpty = false;
53 var $opensslOpened = true;
58 function SugarPHPMailer() {
63 $admin = new Administration();
64 $admin->retrieveSettings();
66 if(isset($admin->settings['disclosure_enable']) && !empty($admin->settings['disclosure_enable'])) {
67 $this->disclosureEnabled = true;
68 $this->disclosureText = $admin->settings['disclosure_text'];
71 $this->oe = new OutboundEmail();
72 $this->oe->getUserMailerSettings($current_user);
74 $this->SetLanguage('en', 'include/phpmailer/language/');
75 $this->PluginDir = 'include/phpmailer/';
76 $this->Mailer = 'smtp';
78 $this->CharSet = $locale->getPrecedentPreference('default_email_charset');
79 $this->Encoding = 'quoted-printable';
80 $this->IsHTML(false); // default to plain-text email
81 $this->Hostname = $sugar_config['host_name'];
82 $this->WordWrap = 996;
84 $this->protocol = ($this->oe->mail_smtpssl == 1) ? "ssl://" : $this->protocol;
90 * Prefills outbound details
92 function setMailer() {
95 require_once("include/OutboundEmail/OutboundEmail.php");
96 $oe = new OutboundEmail();
97 $oe = $oe->getUserMailerSettings($current_user);
99 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertently set ssl://
100 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
102 if($oe->mail_sendtype == "SMTP")
104 //Set mail send type information
105 $this->Mailer = "smtp";
106 $this->Host = $oe->mail_smtpserver;
107 $this->Port = $oe->mail_smtpport;
108 if ($oe->mail_smtpssl == 1) {
109 $this->SMTPSecure = 'ssl';
111 if ($oe->mail_smtpssl == 2) {
112 $this->SMTPSecure = 'tls';
115 if($oe->mail_smtpauth_req) {
116 $this->SMTPAuth = TRUE;
117 $this->Username = $oe->mail_smtpuser;
118 $this->Password = $oe->mail_smtppass;
122 $this->Mailer = "sendmail";
126 * Prefills mailer for system
128 function setMailerForSystem() {
129 require_once("include/OutboundEmail/OutboundEmail.php");
130 $oe = new OutboundEmail();
131 $oe = $oe->getSystemMailerSettings();
133 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
134 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
136 if($oe->mail_sendtype == "SMTP")
138 //Set mail send type information
139 $this->Mailer = "smtp";
140 $this->Host = $oe->mail_smtpserver;
141 $this->Port = $oe->mail_smtpport;
142 if ($oe->mail_smtpssl == 1) {
143 $this->SMTPSecure = 'ssl';
145 if ($oe->mail_smtpssl == 2) {
146 $this->SMTPSecure = 'tls';
148 if($oe->mail_smtpauth_req) {
149 $this->SMTPAuth = TRUE;
150 $this->Username = $oe->mail_smtpuser;
151 $this->Password = $oe->mail_smtppass;
155 $this->Mailer = "sendmail";
159 * Attaches all fs, string, and binary attachments to the message.
160 * Returns an empty string on failure.
164 function AttachAll() {
165 // Return text of body
168 // Add all attachments
169 for($i = 0; $i < count($this->attachment); $i++) {
170 // Check for string attachment
171 $bString = $this->attachment[$i][5];
173 $string = $this->attachment[$i][0];
175 $path = $this->attachment[$i][0];
178 // cn: overriding parent class' method to perform encode on the following
179 $filename = $this->EncodeHeader(trim($this->attachment[$i][1]));
180 $name = $this->EncodeHeader(trim($this->attachment[$i][2]));
181 $encoding = $this->attachment[$i][3];
182 $type = $this->attachment[$i][4];
183 $disposition = $this->attachment[$i][6];
184 $cid = $this->attachment[$i][7];
186 $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE);
187 $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE);
188 $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);
190 if($disposition == "inline") {
191 $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
194 $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $name, $this->LE.$this->LE);
196 // Encode as string attachment
198 $mime[] = $this->EncodeString($string, $encoding);
199 if($this->IsError()) { return ""; }
200 $mime[] = $this->LE.$this->LE;
202 $mime[] = $this->EncodeFile($path, $encoding);
204 if($this->IsError()) {
207 $mime[] = $this->LE.$this->LE;
210 $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);
212 return join("", $mime);
216 * handles Charset translation for all visual parts of the email.
217 * @param string charset Default = ''
219 function prepForOutbound() {
222 if($this->preppedForOutbound == false) {
223 //bug 28534. We should not set it to true to circumvent the following conversion as each email is independent.
224 //$this->preppedForOutbound = true; // flag so we don't redo this
225 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
228 if($this->disclosureEnabled) {
229 $this->Body .= "<br /> <br />{$this->disclosureText}";
230 $this->AltBody .= "\r\r{$this->disclosureText}";
234 $this->Body = from_html($locale->translateCharset(trim($this->Body), 'UTF-8', $OBCharset));
235 $this->AltBody = from_html($locale->translateCharset(trim($this->AltBody), 'UTF-8', $OBCharset));
236 $subjectUTF8 = from_html(trim($this->Subject));
237 $subject = $locale->translateCharset($subjectUTF8, 'UTF-8', $OBCharset);
238 $this->Subject = $locale->translateCharset($subjectUTF8, 'UTF-8', $OBCharset);
240 // HTML email RFC compliance
241 if($this->ContentType == "text/html") {
242 if(strpos($this->Body, '<html') === false) {
244 $langHeader = get_language_header();
247 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
248 <html xmlns="http://www.w3.org/1999/xhtml" {$langHeader}>
250 <meta http-equiv="Content-Type" content="text/html; charset={$OBCharset}" />
251 <title>{$subject}</title>
255 $this->Body = $head.$this->Body."</body></html>";
259 // Headers /////////////////////////////////
260 // the below is done in PHPMailer::CreateHeader();
261 //$this->Subject = $locale->translateCharsetMIME(trim($this->Subject), 'UTF-8', $locale->getPrecedentPreference('default_email_charset'));
262 $this->FromName = $locale->translateCharset(trim($this->FromName), 'UTF-8', $OBCharset);
264 foreach($this->ReplyTo as $k => $v) {
265 $this->ReplyTo[$k][1] = $locale->translateCharset(trim($v[1]), 'UTF-8', $OBCharset);
268 foreach($this->to as $k => $toArr) {
269 $this->to[$k][1] = $locale->translateCharset(trim($toArr[1]), 'UTF-8', $OBCharset);
272 foreach($this->cc as $k => $ccAddr) {
273 $this->cc[$k][1] = $locale->translateCharset(trim($ccAddr[1]), 'UTF-8', $OBCharset);
276 foreach($this->bcc as $k => $bccAddr) {
277 $this->bcc[$k][1] = $locale->translateCharset(trim($bccAddr[1]), 'UTF-8', $OBCharset);
285 * Replace images with locations specified by regex with cid: images
286 * and attach needed files
287 * @param string $regex Regular expression
288 * @param string $local_prefix Prefix where local files are stored
289 * @param bool $object Use attachment object
291 public function replaceImageByRegex($regex, $local_prefix, $object = false)
293 preg_match_all("#<img[^>]*[\s]+src[^=]*=[\s]*[\"']($regex)(.+?)[\"']#si", $this->Body, $matches);
295 foreach($matches[2] as $match) {
296 $filename = urldecode($match);
298 $file_location = $local_prefix.$filename;
299 if(!file_exists($file_location)) continue;
301 if(preg_match('#&(?:amp;)?type=([\w]+)#i', $matches[0][$i], $typematch)) {
302 switch(strtolower($typematch[1])) {
304 $beanname = 'DocumentRevisions';
311 $mime_type = "application/octet-stream";
312 if(isset($beanname)) {
313 $bean = SugarModule::get($beanname)->loadBean();
314 $bean->retrieve($filename);
315 if(!empty($bean->id)) {
316 $mime_type = $bean->file_mime_type;
317 $filename = $bean->filename;
321 $mime_type = "image/".strtolower(pathinfo($filename, PATHINFO_EXTENSION));
323 if (!$this->embeddedAttachmentExists($cid)) {
324 $this->AddEmbeddedImage($file_location, $cid, $filename, 'base64', $mime_type);
328 //replace references to cache with cid tag
329 $this->Body = preg_replace("|\"$regex|i",'"cid:',$this->Body);
330 // remove bad img line from outbound email
331 $this->Body = preg_replace('#<img[^>]+src[^=]*=\"\/([^>]*?[^>]*)>#sim', '', $this->Body);
335 * @param notes array of note beans
337 function handleAttachments($notes) {
338 global $sugar_config;
340 // cn: bug 4864 - reusing same SugarPHPMailer class, need to clear attachments
341 $this->ClearAttachments();
343 //replace references to cache/images with cid tag
344 $this->Body = preg_replace(';=\s*"'.preg_quote(sugar_cached('images/'), ';').';','="cid:',$this->Body);
346 $this->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
348 //Replace any embeded images using the secure entryPoint for src url.
349 $this->replaceImageByRegex("(?:{$sugar_config['site_url']})?index.php[?]entryPoint=download&(?:amp;)?[^\"]+?id=", "upload://", true);
354 //Handle regular attachments.
355 foreach($notes as $note) {
356 $mime_type = 'text/plain';
360 if($note->object_name == 'Note') {
361 if (! empty($note->file->temp_file_location) && is_file($note->file->temp_file_location)) {
362 $file_location = $note->file->temp_file_location;
363 $filename = $note->file->original_file_name;
364 $mime_type = $note->file->mime_type;
366 $file_location = "upload://{$note->id}";
367 $filename = $note->id.$note->filename;
368 $mime_type = $note->file_mime_type;
370 } elseif($note->object_name == 'DocumentRevision') { // from Documents
371 $filename = $note->id.$note->filename;
372 $file_location = "upload://$filename";
373 $mime_type = $note->file_mime_type;
376 $filename = substr($filename, 36, strlen($filename)); // strip GUID for PHPMailer class to name outbound file
377 if (!$note->embed_flag) {
378 $this->AddAttachment($file_location, $filename, 'base64', $mime_type);
384 * overloads class.phpmailer's SetError() method so that we can log errors in sugarcrm.log
387 function SetError($msg) {
388 $GLOBALS['log']->fatal("SugarPHPMailer encountered an error: {$msg}");
389 parent::SetError($msg);
392 function SmtpConnect() {
393 $connection = parent::SmtpConnect();
396 if(isset($this->oe) && $this->oe->type == "system") {
397 $this->SetError($app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
399 $this->SetError($app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
406 * overloads PHPMailer::PreSend() to allow for empty messages to go out.
408 protected function PreSend() {
409 //check to see if message body is empty
410 if(empty($this->Body)){
411 //PHPMailer will throw an error if the body is empty, so insert a blank space if body is empty
414 return parent::PreSend();
418 * Checks if the embedded file is already attached.
420 * @param string $filename Name of the file to check.
423 protected function embeddedAttachmentExists($filename)
426 for ($i = 0; $i < count($this->attachment); $i++)
428 if ($this->attachment[$i][1] == $filename)
438 } // end class definition