]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarPHPMailer.php
Release 6.5.16
[Github/sugarcrm.git] / include / SugarPHPMailer.php
1 <?php
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.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38 require_once('include/phpmailer/class.phpmailer.php');
39 require_once('include/OutboundEmail/OutboundEmail.php');
40
41 /**
42  * Sugar mailer
43  * @api
44  */
45 class SugarPHPMailer extends PHPMailer
46 {
47         var $oe; // OutboundEmail
48         var $protocol = "tcp://";
49         var $preppedForOutbound = false;
50         var $disclosureEnabled;
51         var $disclosureText;
52         var $isHostEmpty = false;
53         var $opensslOpened = true;
54
55         /**
56          * Sole constructor
57          */
58         function SugarPHPMailer() {
59                 global $locale;
60                 global $current_user;
61                 global $sugar_config;
62
63                 $admin = new Administration();
64                 $admin->retrieveSettings();
65
66                 if(isset($admin->settings['disclosure_enable']) && !empty($admin->settings['disclosure_enable'])) {
67                         $this->disclosureEnabled = true;
68                         $this->disclosureText = $admin->settings['disclosure_text'];
69                 }
70
71                 $this->oe = new OutboundEmail();
72                 $this->oe->getUserMailerSettings($current_user);
73
74                 $this->SetLanguage('en', 'include/phpmailer/language/');
75                 $this->PluginDir        = 'include/phpmailer/';
76                 $this->Mailer           = 'smtp';
77         // cn: i18n
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;
83                 // cn: gmail fix
84                 $this->protocol = ($this->oe->mail_smtpssl == 1) ? "ssl://" : $this->protocol;
85
86         }
87
88
89         /**
90          * Prefills outbound details
91          */
92         function setMailer() {
93                 global $current_user;
94
95                 require_once("include/OutboundEmail/OutboundEmail.php");
96                 $oe = new OutboundEmail();
97                 $oe = $oe->getUserMailerSettings($current_user);
98
99                 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertently set ssl://
100                 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
101
102                 if($oe->mail_sendtype == "SMTP")
103                 {
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';
110             } // if
111             if ($oe->mail_smtpssl == 2) {
112                 $this->SMTPSecure = 'tls';
113             } // if
114
115                 if($oe->mail_smtpauth_req) {
116                         $this->SMTPAuth = TRUE;
117                         $this->Username = $oe->mail_smtpuser;
118                         $this->Password = $oe->mail_smtppass;
119                 }
120                 }
121                 else
122                         $this->Mailer = "sendmail";
123         }
124
125         /**
126          * Prefills mailer for system
127          */
128         function setMailerForSystem() {
129                 require_once("include/OutboundEmail/OutboundEmail.php");
130                 $oe = new OutboundEmail();
131                 $oe = $oe->getSystemMailerSettings();
132
133                 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
134                 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
135
136                 if($oe->mail_sendtype == "SMTP")
137                 {
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';
144             } // if
145             if ($oe->mail_smtpssl == 2) {
146                 $this->SMTPSecure = 'tls';
147             } // if
148                 if($oe->mail_smtpauth_req) {
149                         $this->SMTPAuth = TRUE;
150                         $this->Username = $oe->mail_smtpuser;
151                         $this->Password = $oe->mail_smtppass;
152                 }
153                 }
154                 else
155                   $this->Mailer = "sendmail";
156         }
157
158     /**
159      * Attaches all fs, string, and binary attachments to the message.
160      * Returns an empty string on failure.
161      * @access private
162      * @return string
163      */
164     function AttachAll() {
165         // Return text of body
166         $mime = array();
167
168         // Add all attachments
169         for($i = 0; $i < count($this->attachment); $i++) {
170             // Check for string attachment
171             $bString = $this->attachment[$i][5];
172             if ($bString) {
173                 $string = $this->attachment[$i][0];
174             } else {
175                                 $path = $this->attachment[$i][0];
176             }
177
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];
185
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);
189
190             if($disposition == "inline") {
191                 $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
192             }
193
194             $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $name, $this->LE.$this->LE);
195
196             // Encode as string attachment
197             if($bString) {
198                 $mime[] = $this->EncodeString($string, $encoding);
199                 if($this->IsError()) { return ""; }
200                 $mime[] = $this->LE.$this->LE;
201             } else {
202                 $mime[] = $this->EncodeFile($path, $encoding);
203
204                 if($this->IsError()) {
205                         return "";
206                 }
207                 $mime[] = $this->LE.$this->LE;
208             }
209         }
210         $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);
211
212         return join("", $mime);
213     }
214
215         /**
216          * handles Charset translation for all visual parts of the email.
217          * @param string charset Default = ''
218          */
219         function prepForOutbound() {
220                 global $locale;
221
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');
226
227                         // handle disclosure
228                         if($this->disclosureEnabled) {
229                                 $this->Body .= "<br />&nbsp;<br />{$this->disclosureText}";
230                                 $this->AltBody .= "\r\r{$this->disclosureText}";
231                         }
232
233                         // body text
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);
239
240                         // HTML email RFC compliance
241                         if($this->ContentType == "text/html") {
242                                 if(strpos($this->Body, '<html') === false) {
243
244                     $langHeader = get_language_header();
245
246                                         $head=<<<eoq
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}>
249 <head>
250         <meta http-equiv="Content-Type" content="text/html; charset={$OBCharset}" />
251 <title>{$subject}</title>
252 </head>
253 <body>
254 eoq;
255                                         $this->Body = $head.$this->Body."</body></html>";
256                                 }
257                         }
258
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);
263                         /*
264                         foreach($this->ReplyTo as $k => $v) {
265                                 $this->ReplyTo[$k][1] = $locale->translateCharset(trim($v[1]), 'UTF-8', $OBCharset);
266                         }
267                         // TO: fields
268                         foreach($this->to as $k => $toArr) {
269                                 $this->to[$k][1]        = $locale->translateCharset(trim($toArr[1]), 'UTF-8', $OBCharset);
270                         }
271                         // CC: fields
272                         foreach($this->cc as $k => $ccAddr) {
273                                 $this->cc[$k][1]        = $locale->translateCharset(trim($ccAddr[1]), 'UTF-8', $OBCharset);
274                         }
275                         // BCC: fields
276                         foreach($this->bcc as $k => $bccAddr) {
277                                 $this->bcc[$k][1]       = $locale->translateCharset(trim($bccAddr[1]), 'UTF-8', $OBCharset);
278                         }
279                         */
280
281                 }
282         }
283
284         /**
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
290          */
291         public function replaceImageByRegex($regex, $local_prefix, $object = false)
292         {
293                 preg_match_all("#<img[^>]*[\s]+src[^=]*=[\s]*[\"']($regex)(.+?)[\"']#si", $this->Body, $matches);
294                 $i = 0;
295         foreach($matches[2] as $match) {
296                         $filename = urldecode($match);
297                         $cid = $filename;
298                         $file_location = $local_prefix.$filename;
299                         if(!file_exists($file_location)) continue;
300                         if($object) {
301                             if(preg_match('#&(?:amp;)?type=([\w]+)#i', $matches[0][$i], $typematch)) {
302                                 switch(strtolower($typematch[1])) {
303                                     case 'documents':
304                                         $beanname = 'DocumentRevisions';
305                                         break;
306                                     case 'notes':
307                                         $beanname = 'Notes';
308                                         break;
309                                 }
310                             }
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;
318                             }
319                             }
320                         } else {
321                             $mime_type = "image/".strtolower(pathinfo($filename, PATHINFO_EXTENSION));
322                         }
323                     if (!$this->embeddedAttachmentExists($cid)) {
324                         $this->AddEmbeddedImage($file_location, $cid, $filename, 'base64', $mime_type);
325                     }
326                     $i++;
327         }
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);
332         }
333         
334         /**
335          * @param notes array of note beans
336          */
337         function handleAttachments($notes) {
338                 global $sugar_config;
339
340                 // cn: bug 4864 - reusing same SugarPHPMailer class, need to clear attachments
341                 $this->ClearAttachments();
342
343                 //replace references to cache/images with cid tag
344         $this->Body = preg_replace(';=\s*"'.preg_quote(sugar_cached('images/'), ';').';','="cid:',$this->Body);
345
346                 $this->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
347
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);
350
351                 if (empty($notes)) {
352                                 return;
353                 }
354                 //Handle regular attachments.
355                 foreach($notes as $note) {
356                                 $mime_type = 'text/plain';
357                                 $file_location = '';
358                                 $filename = '';
359
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;
365                                         } else {
366                                                 $file_location = "upload://{$note->id}";
367                                                 $filename = $note->id.$note->filename;
368                                                 $mime_type = $note->file_mime_type;
369                                         }
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;
374                                 }
375
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);
379                                 } // else
380                         }
381         }
382
383         /**
384          * overloads class.phpmailer's SetError() method so that we can log errors in sugarcrm.log
385          *
386          */
387         function SetError($msg) {
388                 $GLOBALS['log']->fatal("SugarPHPMailer encountered an error: {$msg}");
389                 parent::SetError($msg);
390         }
391
392         function SmtpConnect() {
393                 $connection = parent::SmtpConnect();
394                 if (!$connection) {
395                         global $app_strings;
396                         if(isset($this->oe) && $this->oe->type == "system") {
397                                 $this->SetError($app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
398                         } else {
399                                 $this->SetError($app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
400                         } // else
401                 }
402                 return $connection;
403         } // fn
404
405     /*
406      * overloads PHPMailer::PreSend() to allow for empty messages to go out.
407      */
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
412             $this->Body = " ";
413         }
414         return parent::PreSend();
415     }
416
417     /**
418      * Checks if the embedded file is already attached.
419      * @access protected
420      * @param string $filename Name of the file to check.
421      * @return boolean
422      */
423     protected function embeddedAttachmentExists($filename)
424     {
425         $result = false;
426         for ($i = 0; $i < count($this->attachment); $i++)
427         {
428             if ($this->attachment[$i][1] == $filename)
429             {
430                 $result = true;
431                 break;
432             }
433         }
434
435         return $result;
436     }
437
438 } // end class definition