1*bb01c27cSAndreas Gohr<?php 2*bb01c27cSAndreas Gohr/** 3*bb01c27cSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 4*bb01c27cSAndreas Gohr */ 5*bb01c27cSAndreas Gohr 6*bb01c27cSAndreas Gohr 7*bb01c27cSAndreas Gohr// end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?) 8*bb01c27cSAndreas Gohr// think different 9*bb01c27cSAndreas Gohrif(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n"); 10*bb01c27cSAndreas Gohr#define('MAILHEADER_ASCIIONLY',1); 11*bb01c27cSAndreas Gohr 12*bb01c27cSAndreas Gohr 13*bb01c27cSAndreas Gohrclass Mailer { 14*bb01c27cSAndreas Gohr 15*bb01c27cSAndreas Gohr private $headers = array(); 16*bb01c27cSAndreas Gohr private $attach = array(); 17*bb01c27cSAndreas Gohr private $html = ''; 18*bb01c27cSAndreas Gohr private $text = ''; 19*bb01c27cSAndreas Gohr 20*bb01c27cSAndreas Gohr private $boundary = ''; 21*bb01c27cSAndreas Gohr private $partid = ''; 22*bb01c27cSAndreas Gohr private $sendparam= ''; 23*bb01c27cSAndreas Gohr 24*bb01c27cSAndreas Gohr function __construct(){ 25*bb01c27cSAndreas Gohr $this->partid = md5(uniqid(rand(),true)).'@'.$_SERVER['SERVER_NAME']; 26*bb01c27cSAndreas Gohr $this->boundary = '----------'.md5(uniqid(rand(),true)); 27*bb01c27cSAndreas Gohr } 28*bb01c27cSAndreas Gohr 29*bb01c27cSAndreas Gohr /** 30*bb01c27cSAndreas Gohr * Attach a file 31*bb01c27cSAndreas Gohr * 32*bb01c27cSAndreas Gohr * @param $path Path to the file to attach 33*bb01c27cSAndreas Gohr * @param $mime Mimetype of the attached file 34*bb01c27cSAndreas Gohr * @param $name The filename to use 35*bb01c27cSAndreas Gohr * @param $embed Unique key to reference this file from the HTML part 36*bb01c27cSAndreas Gohr */ 37*bb01c27cSAndreas Gohr public function attachFile($path,$mime,$name='',$embed=''){ 38*bb01c27cSAndreas Gohr if(!$name){ 39*bb01c27cSAndreas Gohr $name = basename($path); 40*bb01c27cSAndreas Gohr } 41*bb01c27cSAndreas Gohr 42*bb01c27cSAndreas Gohr $this->attach[] = array( 43*bb01c27cSAndreas Gohr 'data' => file_get_contents($path), 44*bb01c27cSAndreas Gohr 'mime' => $mime, 45*bb01c27cSAndreas Gohr 'name' => $name, 46*bb01c27cSAndreas Gohr 'embed' => $embed 47*bb01c27cSAndreas Gohr ); 48*bb01c27cSAndreas Gohr } 49*bb01c27cSAndreas Gohr 50*bb01c27cSAndreas Gohr /** 51*bb01c27cSAndreas Gohr * Attach a file 52*bb01c27cSAndreas Gohr * 53*bb01c27cSAndreas Gohr * @param $path The file contents to attach 54*bb01c27cSAndreas Gohr * @param $mime Mimetype of the attached file 55*bb01c27cSAndreas Gohr * @param $name The filename to use 56*bb01c27cSAndreas Gohr * @param $embed Unique key to reference this file from the HTML part 57*bb01c27cSAndreas Gohr */ 58*bb01c27cSAndreas Gohr public function attachContent($data,$mime,$name='',$embed=''){ 59*bb01c27cSAndreas Gohr if(!$name){ 60*bb01c27cSAndreas Gohr list($junk,$ext) = split('/',$mime); 61*bb01c27cSAndreas Gohr $name = count($this->attach).".$ext"; 62*bb01c27cSAndreas Gohr } 63*bb01c27cSAndreas Gohr 64*bb01c27cSAndreas Gohr $this->attach[] = array( 65*bb01c27cSAndreas Gohr 'data' => $data, 66*bb01c27cSAndreas Gohr 'mime' => $mime, 67*bb01c27cSAndreas Gohr 'name' => $name, 68*bb01c27cSAndreas Gohr 'embed' => $embed 69*bb01c27cSAndreas Gohr ); 70*bb01c27cSAndreas Gohr } 71*bb01c27cSAndreas Gohr 72*bb01c27cSAndreas Gohr /** 73*bb01c27cSAndreas Gohr * Set the HTML part of the mail 74*bb01c27cSAndreas Gohr * 75*bb01c27cSAndreas Gohr * Placeholders can be used to reference embedded attachments 76*bb01c27cSAndreas Gohr */ 77*bb01c27cSAndreas Gohr public function setHTMLBody($html){ 78*bb01c27cSAndreas Gohr $this->html = $html; 79*bb01c27cSAndreas Gohr } 80*bb01c27cSAndreas Gohr 81*bb01c27cSAndreas Gohr /** 82*bb01c27cSAndreas Gohr * Set the plain text part of the mail 83*bb01c27cSAndreas Gohr */ 84*bb01c27cSAndreas Gohr public function setTextBody($text){ 85*bb01c27cSAndreas Gohr $this->text = $text; 86*bb01c27cSAndreas Gohr } 87*bb01c27cSAndreas Gohr 88*bb01c27cSAndreas Gohr /** 89*bb01c27cSAndreas Gohr * Ses an email address header with correct encoding 90*bb01c27cSAndreas Gohr * 91*bb01c27cSAndreas Gohr * Unicode characters will be deaccented and encoded base64 92*bb01c27cSAndreas Gohr * for headers. Addresses may not contain Non-ASCII data! 93*bb01c27cSAndreas Gohr * 94*bb01c27cSAndreas Gohr * Example: 95*bb01c27cSAndreas Gohr * setAddress("föö <foo@bar.com>, me@somewhere.com","TBcc"); 96*bb01c27cSAndreas Gohr * 97*bb01c27cSAndreas Gohr * @param string $address Multiple adresses separated by commas 98*bb01c27cSAndreas Gohr * @param string $header Name of the header (To,Bcc,Cc,...) 99*bb01c27cSAndreas Gohr */ 100*bb01c27cSAndreas Gohr function mail_encode_address($address,$header){ 101*bb01c27cSAndreas Gohr // No named recipients for To: in Windows (see FS#652) 102*bb01c27cSAndreas Gohr $names = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? false : true; 103*bb01c27cSAndreas Gohr 104*bb01c27cSAndreas Gohr $headers = ''; 105*bb01c27cSAndreas Gohr $parts = explode(',',$address); 106*bb01c27cSAndreas Gohr foreach ($parts as $part){ 107*bb01c27cSAndreas Gohr $part = trim($part); 108*bb01c27cSAndreas Gohr 109*bb01c27cSAndreas Gohr // parse address 110*bb01c27cSAndreas Gohr if(preg_match('#(.*?)<(.*?)>#',$part,$matches)){ 111*bb01c27cSAndreas Gohr $text = trim($matches[1]); 112*bb01c27cSAndreas Gohr $addr = $matches[2]; 113*bb01c27cSAndreas Gohr }else{ 114*bb01c27cSAndreas Gohr $addr = $part; 115*bb01c27cSAndreas Gohr } 116*bb01c27cSAndreas Gohr // skip empty ones 117*bb01c27cSAndreas Gohr if(empty($addr)){ 118*bb01c27cSAndreas Gohr continue; 119*bb01c27cSAndreas Gohr } 120*bb01c27cSAndreas Gohr 121*bb01c27cSAndreas Gohr // FIXME: is there a way to encode the localpart of a emailaddress? 122*bb01c27cSAndreas Gohr if(!utf8_isASCII($addr)){ 123*bb01c27cSAndreas Gohr msg(htmlspecialchars("E-Mail address <$addr> is not ASCII"),-1); 124*bb01c27cSAndreas Gohr continue; 125*bb01c27cSAndreas Gohr } 126*bb01c27cSAndreas Gohr 127*bb01c27cSAndreas Gohr if(!mail_isvalid($addr)){ 128*bb01c27cSAndreas Gohr msg(htmlspecialchars("E-Mail address <$addr> is not valid"),-1); 129*bb01c27cSAndreas Gohr continue; 130*bb01c27cSAndreas Gohr } 131*bb01c27cSAndreas Gohr 132*bb01c27cSAndreas Gohr // text was given 133*bb01c27cSAndreas Gohr if(!empty($text) && $names){ 134*bb01c27cSAndreas Gohr // add address quotes 135*bb01c27cSAndreas Gohr $addr = "<$addr>"; 136*bb01c27cSAndreas Gohr 137*bb01c27cSAndreas Gohr if(defined('MAILHEADER_ASCIIONLY')){ 138*bb01c27cSAndreas Gohr $text = utf8_deaccent($text); 139*bb01c27cSAndreas Gohr $text = utf8_strip($text); 140*bb01c27cSAndreas Gohr } 141*bb01c27cSAndreas Gohr 142*bb01c27cSAndreas Gohr if(!utf8_isASCII($text)){ 143*bb01c27cSAndreas Gohr //FIXME 144*bb01c27cSAndreas Gohr // put the quotes outside as in =?UTF-8?Q?"Elan Ruusam=C3=A4e"?= vs "=?UTF-8?Q?Elan Ruusam=C3=A4e?=" 145*bb01c27cSAndreas Gohr /* 146*bb01c27cSAndreas Gohr if (preg_match('/^"(.+)"$/', $text, $matches)) { 147*bb01c27cSAndreas Gohr $text = '"=?UTF-8?Q?'.mail_quotedprintable_encode($matches[1], 0).'?="'; 148*bb01c27cSAndreas Gohr } else { 149*bb01c27cSAndreas Gohr $text = '=?UTF-8?Q?'.mail_quotedprintable_encode($text, 0).'?='; 150*bb01c27cSAndreas Gohr } 151*bb01c27cSAndreas Gohr */ 152*bb01c27cSAndreas Gohr $text = '=?UTF-8?B?'.base64_encode($text).'?='; 153*bb01c27cSAndreas Gohr } 154*bb01c27cSAndreas Gohr }else{ 155*bb01c27cSAndreas Gohr $text = ''; 156*bb01c27cSAndreas Gohr } 157*bb01c27cSAndreas Gohr 158*bb01c27cSAndreas Gohr // add to header comma seperated 159*bb01c27cSAndreas Gohr if($headers != ''){ 160*bb01c27cSAndreas Gohr $headers .= ','; 161*bb01c27cSAndreas Gohr $headers .= MAILHEADER_EOL.' '; // avoid overlong mail headers 162*bb01c27cSAndreas Gohr } 163*bb01c27cSAndreas Gohr $headers .= $text.' '.$addr; 164*bb01c27cSAndreas Gohr } 165*bb01c27cSAndreas Gohr 166*bb01c27cSAndreas Gohr if(empty($headers)) return false; 167*bb01c27cSAndreas Gohr 168*bb01c27cSAndreas Gohr $this->headers[$header] = $headers; 169*bb01c27cSAndreas Gohr return $headers; 170*bb01c27cSAndreas Gohr } 171*bb01c27cSAndreas Gohr 172*bb01c27cSAndreas Gohr /** 173*bb01c27cSAndreas Gohr * Add the To: recipients 174*bb01c27cSAndreas Gohr * 175*bb01c27cSAndreas Gohr * @see setAddress 176*bb01c27cSAndreas Gohr * @param string $address Multiple adresses separated by commas 177*bb01c27cSAndreas Gohr */ 178*bb01c27cSAndreas Gohr public function to($address){ 179*bb01c27cSAndreas Gohr $this->setAddress($address, 'To'); 180*bb01c27cSAndreas Gohr } 181*bb01c27cSAndreas Gohr 182*bb01c27cSAndreas Gohr /** 183*bb01c27cSAndreas Gohr * Add the Cc: recipients 184*bb01c27cSAndreas Gohr * 185*bb01c27cSAndreas Gohr * @see setAddress 186*bb01c27cSAndreas Gohr * @param string $address Multiple adresses separated by commas 187*bb01c27cSAndreas Gohr */ 188*bb01c27cSAndreas Gohr public function cc($address){ 189*bb01c27cSAndreas Gohr $this->setAddress($address, 'Cc'); 190*bb01c27cSAndreas Gohr } 191*bb01c27cSAndreas Gohr 192*bb01c27cSAndreas Gohr /** 193*bb01c27cSAndreas Gohr * Add the Bcc: recipients 194*bb01c27cSAndreas Gohr * 195*bb01c27cSAndreas Gohr * @see setAddress 196*bb01c27cSAndreas Gohr * @param string $address Multiple adresses separated by commas 197*bb01c27cSAndreas Gohr */ 198*bb01c27cSAndreas Gohr public function bcc($address){ 199*bb01c27cSAndreas Gohr $this->setAddress($address, 'Bcc'); 200*bb01c27cSAndreas Gohr } 201*bb01c27cSAndreas Gohr 202*bb01c27cSAndreas Gohr /** 203*bb01c27cSAndreas Gohr * Add the mail's Subject: header 204*bb01c27cSAndreas Gohr * 205*bb01c27cSAndreas Gohr * @param string $subject the mail subject 206*bb01c27cSAndreas Gohr */ 207*bb01c27cSAndreas Gohr public function subject($subject){ 208*bb01c27cSAndreas Gohr if(!utf8_isASCII($subject)){ 209*bb01c27cSAndreas Gohr $subject = '=?UTF-8?B?'.base64_encode($subject).'?='; 210*bb01c27cSAndreas Gohr } 211*bb01c27cSAndreas Gohr $this->headers['Subject'] = $subject; 212*bb01c27cSAndreas Gohr } 213*bb01c27cSAndreas Gohr 214*bb01c27cSAndreas Gohr /** 215*bb01c27cSAndreas Gohr * Prepare the mime multiparts for all attachments 216*bb01c27cSAndreas Gohr * 217*bb01c27cSAndreas Gohr * Replaces placeholders in the HTML with the correct CIDs 218*bb01c27cSAndreas Gohr */ 219*bb01c27cSAndreas Gohr protected function prepareAttachments(){ 220*bb01c27cSAndreas Gohr $mime = ''; 221*bb01c27cSAndreas Gohr $part = 1; 222*bb01c27cSAndreas Gohr // embedded attachments 223*bb01c27cSAndreas Gohr foreach($this->attach as $media){ 224*bb01c27cSAndreas Gohr // create content id 225*bb01c27cSAndreas Gohr $cid = 'part'.$part.'.'.$this->partid; 226*bb01c27cSAndreas Gohr 227*bb01c27cSAndreas Gohr // replace wildcards 228*bb01c27cSAndreas Gohr if($media['embed']){ 229*bb01c27cSAndreas Gohr $this->html = str_replace('%%'.$media['embed'].'%%','cid:'.$cid,$this->html); 230*bb01c27cSAndreas Gohr } 231*bb01c27cSAndreas Gohr 232*bb01c27cSAndreas Gohr $mime .= '--'.$this->boundary.MAILHEADER_EOL; 233*bb01c27cSAndreas Gohr $mime .= 'Content-Type: '.$media['mime'].';'.MAILHEADER_EOL; 234*bb01c27cSAndreas Gohr $mime .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL; 235*bb01c27cSAndreas Gohr $mime .= "Content-ID: <$cid>".MAILHEADER_EOL; 236*bb01c27cSAndreas Gohr if($media['embed']){ 237*bb01c27cSAndreas Gohr $mime .= 'Content-Disposition: inline; filename="'.$media['name'].'"'.MAILHEADER_EOL; 238*bb01c27cSAndreas Gohr }else{ 239*bb01c27cSAndreas Gohr $mime .= 'Content-Disposition: attachment; filename="'.$media['name'].'"'.MAILHEADER_EOL; 240*bb01c27cSAndreas Gohr } 241*bb01c27cSAndreas Gohr $mime .= MAILHEADER_EOL; //end of headers 242*bb01c27cSAndreas Gohr $mime .= chunk_split(base64_encode($media['data']),74,MAILHEADER_EOL); 243*bb01c27cSAndreas Gohr 244*bb01c27cSAndreas Gohr $part++; 245*bb01c27cSAndreas Gohr } 246*bb01c27cSAndreas Gohr return $mime; 247*bb01c27cSAndreas Gohr } 248*bb01c27cSAndreas Gohr 249*bb01c27cSAndreas Gohr protected function createBody(){ 250*bb01c27cSAndreas Gohr // check for body 251*bb01c27cSAndreas Gohr if(!$this->text && !$this->html){ 252*bb01c27cSAndreas Gohr return false; 253*bb01c27cSAndreas Gohr } 254*bb01c27cSAndreas Gohr 255*bb01c27cSAndreas Gohr // add general headers 256*bb01c27cSAndreas Gohr $this->headers['MIME-Version'] = '1.0'; 257*bb01c27cSAndreas Gohr 258*bb01c27cSAndreas Gohr if(!$this->html && !count($this->attach)){ // we can send a simple single part message 259*bb01c27cSAndreas Gohr $this->headers['Content-Type'] = 'text/plain; charset=UTF-8'; 260*bb01c27cSAndreas Gohr $this->headers['Content-Transfer-Encoding'] = 'base64'; 261*bb01c27cSAndreas Gohr $body = chunk_split(base64_encode($this->text),74,MAILHEADER_EOL); 262*bb01c27cSAndreas Gohr }else{ // multi part it is 263*bb01c27cSAndreas Gohr 264*bb01c27cSAndreas Gohr // prepare the attachments 265*bb01c27cSAndreas Gohr $attachments = $this->prepareAttachments(); 266*bb01c27cSAndreas Gohr 267*bb01c27cSAndreas Gohr // do we have alternative text content? 268*bb01c27cSAndreas Gohr if($this->text && $this->html){ 269*bb01c27cSAndreas Gohr $this->headers['Content-Type'] = 'multipart/alternative; boundary="'.$this->boundary.'XX"'; 270*bb01c27cSAndreas Gohr $body = "This is a multi-part message in MIME format.".MAILHEADER_EOL; 271*bb01c27cSAndreas Gohr $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL; 272*bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 273*bb01c27cSAndreas Gohr $body .= 'Content-Type: text/plain; charset=UTF-8'; 274*bb01c27cSAndreas Gohr $body .= 'Content-Transfer-Encoding: base64'; 275*bb01c27cSAndreas Gohr $body .= chunk_split(base64_encode($this->text),74,MAILHEADER_EOL); 276*bb01c27cSAndreas Gohr $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL; 277*bb01c27cSAndreas Gohr $body .= 'Content-Type: multipart/related; boundary="'.$this->boundary.'"'.MAILHEADER_EOL; 278*bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 279*bb01c27cSAndreas Gohr }else{ 280*bb01c27cSAndreas Gohr $this->headers['Content-Type'] = 'multipart/related; boundary="'.$this->boundary.'"'; 281*bb01c27cSAndreas Gohr $body = "This is a multi-part message in MIME format.".MAILHEADER_EOL; 282*bb01c27cSAndreas Gohr } 283*bb01c27cSAndreas Gohr 284*bb01c27cSAndreas Gohr $body .= '--'.$this->boundary."\n"; 285*bb01c27cSAndreas Gohr $body .= "Content-Type: text/html; charset=UTF-8\n"; 286*bb01c27cSAndreas Gohr $body .= "Content-Transfer-Encoding: base64\n"; 287*bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 288*bb01c27cSAndreas Gohr $body = chunk_split(base64_encode($this->html),74,MAILHEADER_EOL); 289*bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 290*bb01c27cSAndreas Gohr $body .= $attachments; 291*bb01c27cSAndreas Gohr $body .= '--'.$this->boundary.'--'.MAILHEADER_EOL; 292*bb01c27cSAndreas Gohr 293*bb01c27cSAndreas Gohr // close open multipart/alternative boundary 294*bb01c27cSAndreas Gohr if($this->text && $this->html){ 295*bb01c27cSAndreas Gohr $body .= '--'.$this->boundary.'XX--'.MAILHEADER_EOL; 296*bb01c27cSAndreas Gohr } 297*bb01c27cSAndreas Gohr } 298*bb01c27cSAndreas Gohr 299*bb01c27cSAndreas Gohr return $body; 300*bb01c27cSAndreas Gohr } 301*bb01c27cSAndreas Gohr 302*bb01c27cSAndreas Gohr /** 303*bb01c27cSAndreas Gohr * Create a string from the headers array 304*bb01c27cSAndreas Gohr */ 305*bb01c27cSAndreas Gohr protected function prepareHeaders(){ 306*bb01c27cSAndreas Gohr $headers = ''; 307*bb01c27cSAndreas Gohr foreach($this->headers as $key => $val){ 308*bb01c27cSAndreas Gohr $headers .= "$key: $val".MAILHEADER_EOL; 309*bb01c27cSAndreas Gohr } 310*bb01c27cSAndreas Gohr return $headers; 311*bb01c27cSAndreas Gohr } 312*bb01c27cSAndreas Gohr 313*bb01c27cSAndreas Gohr /** 314*bb01c27cSAndreas Gohr * return a full email with all headers 315*bb01c27cSAndreas Gohr * 316*bb01c27cSAndreas Gohr * This is mainly for debugging and testing 317*bb01c27cSAndreas Gohr */ 318*bb01c27cSAndreas Gohr public function dump(){ 319*bb01c27cSAndreas Gohr $headers = $this->prepareHeaders(); 320*bb01c27cSAndreas Gohr $body = $this->prepareBody(); 321*bb01c27cSAndreas Gohr 322*bb01c27cSAndreas Gohr return $headers.MAILHEADER_EOL.$body; 323*bb01c27cSAndreas Gohr } 324*bb01c27cSAndreas Gohr} 325