]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarPHPMailer.php
Release 6.4.0
[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-2011 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 /*********************************************************************************
39
40  * Description:  TODO: To be written.
41  * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
42  * All Rights Reserved.
43  * Contributor(s): ______________________________________..
44  ********************************************************************************/
45 require_once('include/phpmailer/class.phpmailer.php');
46 require_once('include/OutboundEmail/OutboundEmail.php');
47
48 /**
49  * Sugar mailer
50  * @api
51  */
52 class SugarPHPMailer extends PHPMailer
53 {
54         var $oe; // OutboundEmail
55         var $protocol = "tcp://";
56         var $preppedForOutbound = false;
57         var $disclosureEnabled;
58         var $disclosureText;
59         var $isHostEmpty = false;
60         var $opensslOpened = true;
61
62         /**
63          * Sole constructor
64          */
65         function SugarPHPMailer() {
66                 global $locale;
67                 global $current_user;
68                 global $sugar_config;
69
70                 $admin = new Administration();
71                 $admin->retrieveSettings();
72
73                 if(isset($admin->settings['disclosure_enable']) && !empty($admin->settings['disclosure_enable'])) {
74                         $this->disclosureEnabled = true;
75                         $this->disclosureText = $admin->settings['disclosure_text'];
76                 }
77
78                 $this->oe = new OutboundEmail();
79                 $this->oe->getUserMailerSettings($current_user);
80
81                 $this->SetLanguage('en', 'include/phpmailer/language/');
82                 $this->PluginDir        = 'include/phpmailer/';
83                 $this->Mailer           = 'smtp';
84         // cn: i18n
85         $this->CharSet          = $locale->getPrecedentPreference('default_email_charset');
86                 $this->Encoding         = 'quoted-printable';
87         $this->IsHTML(false);  // default to plain-text email
88         $this->Hostname = $sugar_config['host_name'];
89         $this->WordWrap         = 996;
90                 // cn: gmail fix
91                 $this->protocol = ($this->oe->mail_smtpssl == 1) ? "ssl://" : $this->protocol;
92
93         }
94
95
96         /**
97          * Prefills outbound details
98          */
99         function setMailer() {
100                 global $current_user;
101
102                 require_once("include/OutboundEmail/OutboundEmail.php");
103                 $oe = new OutboundEmail();
104                 $oe = $oe->getUserMailerSettings($current_user, $mailer_id, $ieId);
105
106                 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
107                 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
108
109                 if($oe->mail_sendtype == "SMTP")
110                 {
111                 //Set mail send type information
112                 $this->Mailer = "smtp";
113                 $this->Host = $oe->mail_smtpserver;
114                 $this->Port = $oe->mail_smtpport;
115             if ($oe->mail_smtpssl == 1) {
116                 $this->SMTPSecure = 'ssl';
117             } // if
118             if ($oe->mail_smtpssl == 2) {
119                 $this->SMTPSecure = 'tls';
120             } // if
121
122                 if($oe->mail_smtpauth_req) {
123                         $this->SMTPAuth = TRUE;
124                         $this->Username = $oe->mail_smtpuser;
125                         $this->Password = $oe->mail_smtppass;
126                 }
127                 }
128                 else
129                         $this->Mailer = "sendmail";
130         }
131
132         /**
133          * Prefills mailer for system
134          */
135         function setMailerForSystem() {
136                 require_once("include/OutboundEmail/OutboundEmail.php");
137                 $oe = new OutboundEmail();
138                 $oe = $oe->getSystemMailerSettings();
139
140                 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
141                 $this->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
142
143                 if($oe->mail_sendtype == "SMTP")
144                 {
145                 //Set mail send type information
146                 $this->Mailer = "smtp";
147                 $this->Host = $oe->mail_smtpserver;
148                 $this->Port = $oe->mail_smtpport;
149             if ($oe->mail_smtpssl == 1) {
150                 $this->SMTPSecure = 'ssl';
151             } // if
152             if ($oe->mail_smtpssl == 2) {
153                 $this->SMTPSecure = 'tls';
154             } // if
155                 if($oe->mail_smtpauth_req) {
156                         $this->SMTPAuth = TRUE;
157                         $this->Username = $oe->mail_smtpuser;
158                         $this->Password = $oe->mail_smtppass;
159                 }
160                 }
161                 else
162                   $this->Mailer = "sendmail";
163         }
164
165     /**
166      * Attaches all fs, string, and binary attachments to the message.
167      * Returns an empty string on failure.
168      * @access private
169      * @return string
170      */
171     function AttachAll() {
172         // Return text of body
173         $mime = array();
174
175         // Add all attachments
176         for($i = 0; $i < count($this->attachment); $i++) {
177             // Check for string attachment
178             $bString = $this->attachment[$i][5];
179             if ($bString) {
180                 $string = $this->attachment[$i][0];
181             } else {
182                                 $path = $this->attachment[$i][0];
183             }
184
185                         // cn: overriding parent class' method to perform encode on the following
186             $filename    = $this->EncodeHeader(trim($this->attachment[$i][1]));
187             $name        = $this->EncodeHeader(trim($this->attachment[$i][2]));
188             $encoding    = $this->attachment[$i][3];
189             $type        = $this->attachment[$i][4];
190             $disposition = $this->attachment[$i][6];
191             $cid         = $this->attachment[$i][7];
192
193             $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE);
194             $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE);
195             $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);
196
197             if($disposition == "inline") {
198                 $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
199             }
200
201             $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $name, $this->LE.$this->LE);
202
203             // Encode as string attachment
204             if($bString) {
205                 $mime[] = $this->EncodeString($string, $encoding);
206                 if($this->IsError()) { return ""; }
207                 $mime[] = $this->LE.$this->LE;
208             } else {
209                 $mime[] = $this->EncodeFile($path, $encoding);
210
211                 if($this->IsError()) {
212                         return "";
213                 }
214                 $mime[] = $this->LE.$this->LE;
215             }
216         }
217         $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);
218
219         return join("", $mime);
220     }
221
222         /**
223          * handles Charset translation for all visual parts of the email.
224          * @param string charset Default = ''
225          */
226         function prepForOutbound() {
227                 global $locale;
228
229                 if($this->preppedForOutbound == false) {
230                         //bug 28534. We should not set it to true to circumvent the following convertion as each email is independent.
231                         //$this->preppedForOutbound = true; // flag so we don't redo this
232                         $OBCharset = $locale->getPrecedentPreference('default_email_charset');
233
234                         // handle disclosure
235                         if($this->disclosureEnabled) {
236                                 $this->Body .= "<br />&nbsp;<br />{$this->disclosureText}";
237                                 $this->AltBody .= "\r\r{$this->disclosureText}";
238                         }
239
240                         // body text
241                         $this->Body             = from_html($locale->translateCharset(trim($this->Body), 'UTF-8', $OBCharset));
242                         $this->AltBody          = from_html($locale->translateCharset(trim($this->AltBody), 'UTF-8', $OBCharset));
243             $subjectUTF8                = from_html(trim($this->Subject));
244             $subject                    = $locale->translateCharset($subjectUTF8, 'UTF-8', $OBCharset);
245             $this->Subject              = $locale->translateCharset($subjectUTF8, 'UTF-8', $OBCharset);
246
247                         // HTML email RFC compliance
248                         if($this->ContentType == "text/html") {
249                                 if(strpos($this->Body, '<html') === false) {
250
251                     $langHeader = get_language_header();
252
253                                         $head=<<<eoq
254 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
255 <html xmlns="http://www.w3.org/1999/xhtml" {$langHeader}>
256 <head>
257         <meta http-equiv="Content-Type" content="text/html; charset={$OBCharset}" />
258 <title>{$subject}</title>
259 </head>
260 <body>
261 eoq;
262                                         $this->Body = $head.$this->Body."</body></html>";
263                                 }
264                         }
265
266                         // Headers /////////////////////////////////
267                         // the below is done in PHPMailer::CreateHeader();
268                         //$this->Subject                        = $locale->translateCharsetMIME(trim($this->Subject), 'UTF-8', $locale->getPrecedentPreference('default_email_charset'));
269                         $this->FromName         = $locale->translateCharset(trim($this->FromName), 'UTF-8', $OBCharset);
270                         /*
271                         foreach($this->ReplyTo as $k => $v) {
272                                 $this->ReplyTo[$k][1] = $locale->translateCharset(trim($v[1]), 'UTF-8', $OBCharset);
273                         }
274                         // TO: fields
275                         foreach($this->to as $k => $toArr) {
276                                 $this->to[$k][1]        = $locale->translateCharset(trim($toArr[1]), 'UTF-8', $OBCharset);
277                         }
278                         // CC: fields
279                         foreach($this->cc as $k => $ccAddr) {
280                                 $this->cc[$k][1]        = $locale->translateCharset(trim($ccAddr[1]), 'UTF-8', $OBCharset);
281                         }
282                         // BCC: fields
283                         foreach($this->bcc as $k => $bccAddr) {
284                                 $this->bcc[$k][1]       = $locale->translateCharset(trim($bccAddr[1]), 'UTF-8', $OBCharset);
285                         }
286                         */
287
288                 }
289         }
290
291         /**
292          * Replace images with locations specified by regex with cid: images
293          * and attach needed files
294          * @param string $regex Regular expression
295          * @param string $local_prefix Prefix where local files are stored
296          * @param bool $object Use attachment object
297          */
298         public function replaceImageByRegex($regex, $local_prefix, $object = false)
299         {
300                 preg_match_all("#<img[^>]*[\s]+src[^=]*=[\s]*[\"']($regex)(.+?)[\"']#si", $this->Body, $matches);
301                 $i = 0;
302         foreach($matches[2] as $match) {
303                         $filename = urldecode($match);
304                         $cid = $filename;
305                         $file_location = $local_prefix.$filename;
306                         if(!file_exists($file_location)) continue;
307                         if($object) {
308                             if(preg_match('#&(?:amp;)?type=([\w]+)#i', $matches[0][$i], $typematch)) {
309                                 switch(strtolower($typematch[1])) {
310                                     case 'documents':
311                                         $beanname = 'DocumentRevisions';
312                                         break;
313                                     case 'notes':
314                                         $beanname = 'Notes';
315                                         break;
316                                 }
317                             }
318                             $mime_type = "application/octet-stream";
319                             if(isset($beanname)) {
320                                 $bean = SugarModule::get($beanname)->loadBean();
321                             $bean->retrieve($filename);
322                             if(!empty($bean->id)) {
323                                     $mime_type = $bean->file_mime_type;
324                                     $filename = $bean->filename;
325                             }
326                             }
327                         } else {
328                             $mime_type = "image/".strtolower(pathinfo($filename, PATHINFO_EXTENSION));
329                         }
330                     $this->AddEmbeddedImage($file_location, $cid, $filename, 'base64', $mime_type);
331                     $i++;
332         }
333                 //replace references to cache with cid tag
334                 $this->Body = preg_replace("|\"$regex|i",'"cid:',$this->Body);
335                 // remove bad img line from outbound email
336                 $this->Body = preg_replace('#<img[^>]+src[^=]*=\"\/([^>]*?[^>]*)>#sim', '', $this->Body);
337         }
338
339         /**
340          * @param notes array of note beans
341          */
342         function handleAttachments($notes) {
343                 global $sugar_config;
344
345         //replace references to cache/images with cid tag
346         $this->Body = str_replace(sugar_cached('images/'),'cid:',$this->Body);
347
348                 if (empty($notes)) {
349                                 return;
350                 }
351                 // cn: bug 4864 - reusing same SugarPHPMailer class, need to clear attachments
352                 $this->ClearAttachments();
353
354                 $this->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
355
356                 //Replace any embeded images using the secure entryPoint for src url.
357                 $this->replaceImageByRegex("(?:{$sugar_config['site_url']})?index.php[?]entryPoint=download&(?:amp;)?[^\"]+?id=", "upload://", true);
358
359                 //Handle regular attachments.
360                 foreach($notes as $note) {
361                                 $mime_type = 'text/plain';
362                                 $file_location = '';
363                                 $filename = '';
364
365                                 if($note->object_name == 'Note') {
366                                         if (! empty($note->file->temp_file_location) && is_file($note->file->temp_file_location)) {
367                                                 $file_location = $note->file->temp_file_location;
368                                                 $filename = $note->file->original_file_name;
369                                                 $mime_type = $note->file->mime_type;
370                                         } else {
371                                                 $file_location = "upload://{$note->id}";
372                                                 $filename = $note->id.$note->filename;
373                                                 $mime_type = $note->file_mime_type;
374                                         }
375                                 } elseif($note->object_name == 'DocumentRevision') { // from Documents
376                                         $filename = $note->id.$note->filename;
377                                         $file_location = "upload://$filename";
378                                         $mime_type = $note->file_mime_type;
379                                 }
380
381                                 $filename = substr($filename, 36, strlen($filename)); // strip GUID     for PHPMailer class to name outbound file
382                                 if (!$note->embed_flag) {
383                                         $this->AddAttachment($file_location, $filename, 'base64', $mime_type);
384                                 } // else
385                         }
386         }
387
388         /**
389          * overloads class.phpmailer's SetError() method so that we can log errors in sugarcrm.log
390          *
391          */
392         function SetError($msg) {
393                 $GLOBALS['log']->fatal("SugarPHPMailer encountered an error: {$msg}");
394                 parent::SetError($msg);
395         }
396
397         function SmtpConnect() {
398                 $connection = parent::SmtpConnect();
399                 if (!$connection) {
400                         global $app_strings;
401                         if(isset($this->oe) && $this->oe->type == "system") {
402                                 $this->SetError($app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
403                         } else {
404                                 $this->SetError($app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
405                         } // else
406                 }
407                 return $connection;
408         } // fn
409
410 } // end class definition