Index: branches/RC/core/kernel/utility/email_send.php =================================================================== diff -u -r8929 -r9247 --- branches/RC/core/kernel/utility/email_send.php (.../email_send.php) (revision 8929) +++ branches/RC/core/kernel/utility/email_send.php (.../email_send.php) (revision 9247) @@ -5,21 +5,21 @@ * */ class kEmailSendingHelper extends kHelper { - + /** * headers of main header part * * @var Array */ var $headers = Array (); - + /** * Tells if all message parts were combined together * * @var int */ var $bodyPartNumber = false; - + /** * Composed message parts * @@ -33,114 +33,114 @@ * @var string */ var $line_break = "\n"; - + /** * Charset used for message composing * * @var string */ var $charset = 'utf-8'; - + /** * Name of mailer program (X-Mailer header) * * @var string */ var $mailerName = ''; - + /** * Options used for message content-type & structure guessing * * @var Array */ var $guessOptions = Array (); - + /** * Send messages using selected method * * @var string */ var $sendMethod = 'Mail'; - + /** * Parameters used to initiate SMTP server connection * * @var Array */ var $smtpParams = Array (); - + /** * List of supported authentication methods, in preferential order. * @var array * @access public */ var $smtpAuthMethods = Array('CRAM-MD5', 'LOGIN', 'PLAIN'); - + /** * The socket resource being used to connect to the SMTP server. * @var kSocket * @access private */ var $smtpSocket = null; - + /** * The most recent server response code. * @var int * @access private */ var $smtpResponceCode = -1; - + /** * The most recent server response arguments. * @var array * @access private */ var $smtpRespoceArguments = Array(); - + /** * Stores detected features of the SMTP server. * @var array * @access private */ var $smtpFeatures = Array(); - - + + function kEmailSendingHelper() { parent::kHelper(); // set default guess options $this->guessOptions = Array ( - 'attachments' => Array(), + 'attachments' => Array (), 'inline_attachments' => Array (), 'text_part' => false, 'html_part' => false, ); - + // read SMTP server connection params from config $smtp_mapping = Array ('server' => 'Smtp_Server', 'port' => 'Smtp_Port'); if ($this->Application->ConfigValue('Smtp_Authenticate')) { $smtp_mapping['username'] = 'Smtp_User'; $smtp_mapping['password'] = 'Smtp_Pass'; } - + foreach ($smtp_mapping as $smtp_name => $config_name) { $this->smtpParams[$smtp_name] = $this->Application->ConfigValue($config_name); } $this->smtpParams['use_auth'] = isset($this->smtpParams['username']) ? true : false; $this->smtpParams['localhost'] = 'localhost'; // The value to give when sending EHLO or HELO. - + $this->sendMethod = $this->smtpParams['server'] && $this->smtpParams['port'] ? 'SMTP' : 'Mail'; - + if ($this->sendMethod == 'SMTP') { // create connection object if we will use SMTP $this->smtpSocket =& $this->Application->makeClass('Socket'); } - + $this->SetCharset(null, true); } - - + + /** * Returns new message id header by sender's email address * @@ -151,12 +151,12 @@ { list ($micros, $seconds) = explode(' ', microtime()); list ($user, $domain) = explode('@', $email_address, 2); - + $message_id = strftime('%Y%m%d%H%M%S', $seconds).substr($micros, 1, 5).'.'.preg_replace('/[^A-Za-z]+/', '-', $user).'@'.$domain; - + $this->SetHeader('Message-ID', '<'.$message_id.'>'); } - + /** * Returns extension of given filename * @@ -168,23 +168,23 @@ $last_dot = strrpos($filename, '.'); return $last_dot !== false ? substr($filename, $last_dot + 1) : ''; } - + /** * Creates boundary for part by number (only if it's missing) * * @param int $part_number - * + * */ - + function CreatePartBoundary($part_number) { $part =& $this->parts[$part_number]; if (!isset($part['BOUNDARY'])) { $part['BOUNDARY'] = md5(uniqid($part_number.time())); - + } } - + /** * Returns ready to use headers associative array of any message part by it's number * @@ -194,14 +194,14 @@ function GetPartHeaders($part_number) { $part =& $this->parts[$part_number]; - + if (!isset($part['Content-Type'])) { return $this->SetError('MISSING_CONTENT_TYPE'); } - + $full_type = strtolower($part['Content-Type']); list ($type, $sub_type) = explode('/', $full_type); - + $headers['Content-Type'] = $full_type; switch ($type) { case 'text': @@ -217,12 +217,12 @@ if (isset($part['NAME'])) { $headers['Content-Type'] .= '; name="'.$part['NAME'].'"'; } - + // 2. set content-transfer-encoding header if (isset($part['Content-Transfer-Encoding'])) { $headers['Content-Transfer-Encoding'] = $part['Content-Transfer-Encoding']; } - + // 3. set content-disposition header if (isset($part['DISPOSITION']) && $part['DISPOSITION']) { $headers['Content-Disposition'] = $part['DISPOSITION']; @@ -231,7 +231,7 @@ } } break; - + case 'multipart': switch ($sub_type) { case 'alternative': @@ -241,35 +241,35 @@ $this->CreatePartBoundary($part_number); $headers['Content-Type'] .= '; boundary="'.$part['BOUNDARY'].'"'; break; - + default: return $this->SetError('INVALID_MULTIPART_SUBTYPE', Array($sub_type)); } break; - + default: return $this->SetError('INVALID_CONTENT_TYPE', Array($full_type)); } - + // set content-id if any if (isset($part['Content-ID'])) { $headers['Content-ID'] = '<'.$part['Content-ID'].'>'; } - + return $headers; } - + function GetPartBody($part_number) { $part =& $this->parts[$part_number]; - + if (!isset($part['Content-Type'])) { return $this->SetError('MISSING_CONTENT_TYPE'); } - + $full_type = strtolower($part['Content-Type']); list ($type, $sub_type) = explode('/', $full_type); - + $body = ''; switch ($type) { // compose text/binary content @@ -284,17 +284,17 @@ // content provided via absolute path to content containing file $filename = $part['FILENAME']; $file_size = filesize($filename); - + $body = file_get_contents($filename); if ($body === false) { return $this->SetError('FILE_PART_OPEN_ERROR', Array($filename)); } - + $actual_size = strlen($body); if (($file_size === false || $actual_size > $file_size) && get_magic_quotes_runtime()) { $body = stripslashes($body); } - + if ($file_size !== false && $actual_size != $file_size) { return $this->SetError('FILE_PART_DATA_ERROR', Array($filename)); } @@ -306,19 +306,19 @@ } $body =& $part['DATA']; } - + // 2. get part transfer encoding $encoding = isset($part['Content-Transfer-Encoding']) ? strtolower($part['Content-Transfer-Encoding']) : ''; if (!in_array($encoding, Array ('', 'base64', 'quoted-printable', '7bit'))) { return $this->SetError('INVALID_ENCODING', Array($encoding)); } - + if ($encoding == 'base64') { // split base64 encoded text by 76 symbols at line (MIME requirement) $body = chunk_split( base64_encode($body) ); } break; - + case 'multipart': // compose multipart message switch ($sub_type) { @@ -328,41 +328,41 @@ case 'parallel': $this->CreatePartBoundary($part_number); $boundary = $this->line_break.'--'.$part['BOUNDARY']; - + foreach ($part['PARTS'] as $multipart_number) { $body .= $boundary.$this->line_break; $part_headers = $this->GetPartHeaders($multipart_number); if ($part_headers === false) { // some of sub-part headers were invalid return false; } - + foreach ($part_headers as $header_name => $header_value) { $body .= $header_name.': '.$header_value.$this->line_break; } - + $part_body = $this->GetPartBody($multipart_number); if ($part_body === false) { // part body was invalid return false; } - + $body .= $this->line_break.$part_body; } $body .= $boundary.'--'.$this->line_break; break; - + default: return $this->SetError('INVALID_MULTIPART_SUBTYPE', Array($sub_type)); } break; default: return $this->SetError('INVALID_CONTENT_TYPE', Array($full_type)); } - + return $body; } - + /** * Applies quoted-printable encoding to specified text * @@ -397,14 +397,14 @@ '.' => 1, */ ); - + $b = $space = $break_lines = 0; for ($i = 0; $i < $ln; $i++) { if (isset($s[$text[$i]])) { $b = 1; break; } - + switch ($o = ord($text[$i])) { case 9: case 32: @@ -421,16 +421,16 @@ } } } - + if($i == $ln) { return $text; } - + if ($space > 0) { return substr($text, 0, $space).($space < $ln ? $this->QuotedPrintableEncode(substr($text, $space), $header_charset, 0) : ''); } } - + for ($w = $e = '', $n = 0, $l = 0, $i = 0; $i < $ln; $i++) { $c = $text[$i]; $o = ord($c); @@ -460,12 +460,12 @@ $e .= '='.$this->line_break; $l = 0; } - + $e .= sprintf('=%02X', ord($w)); $l += 3; $w = ''; } - + $e .= $c; if ($h) { $e .= "\t"; @@ -486,7 +486,7 @@ } break; } - + if (strlen($w)) { if ($break_lines && $l + 1 > 75) { $e .= '='.$this->line_break; @@ -496,7 +496,7 @@ $l++; $w = ''; } - + if (strlen($c)) { if ($en) { $c = sprintf('=%02X', $o); @@ -521,10 +521,10 @@ } $e .= sprintf('=%02X', ord($w)); } - + return $h && $n ? '=?'.$header_charset.'?q?'.$e.'?=' : $e; } - + /** * Sets message header + encodes is by quoted-printable using charset specified * @@ -538,10 +538,10 @@ // actually for headers base64 method may give shorter result $value = $this->QuotedPrintableEncode($value, $encoding_charset); } - + $this->headers[$name] = $value; } - + /** * Sets header + automatically encodes it using default charset * @@ -552,7 +552,7 @@ { $this->SetHeader($name, $value, $this->charset); } - + /** * Sets header which value is email and username +autoencode * @@ -564,19 +564,19 @@ { $this->SetHeader($header, $this->QuotedPrintableEncode($name, $this->charset).' <'.$address.'>'); } - + function SetMultipleEncodedEmailHeader($header, $addresses) { $value = ''; foreach ($addresses as $name => $address) { $value .= $this->QuotedPrintableEncode($name, $this->charset).' <'.$address.'>, '; } $value = preg_replace('/(.*),$/', '\\1', $value); - + $this->SetHeader($header, $value); } - - + + /** * Adds new part to message and returns it's number * @@ -590,7 +590,7 @@ $this->parts[$part_number] =& $part_definition; return $part_number; } - + /** * Returns text version of HTML document * @@ -619,7 +619,7 @@ "'&(copy|#169);'i", "'&#(\d+);'e" ); - + $replace = Array ( "\\1\t\\2", "\n", @@ -640,17 +640,17 @@ chr(169), "chr(\\1)" ); - + return strip_tags( preg_replace ($search, $replace, $html) ); } - + /** * Add text OR html part to message (optionally encoded) * * @param string $text part's text - * @param bool $is_html this html part or not + * @param bool $is_html this html part or not * @param bool $encode encode message using quoted-printable encoding - * + * * @return int number of created part */ function CreateTextHtmlPart($text, $is_html = false, $encode = true) @@ -659,31 +659,31 @@ // if adding HTML part, then create plain-text part too $this->CreateTextHtmlPart($this->ConvertToText($text)); } - + // in case if text is from $_REQUEST, then line endings are "\r\n", but we need "\n" here $text = str_replace("\r\n", "\n", $text); // possible case $text = str_replace("\r", "\n", $text); // impossible case, but just in case replace this too - + $definition = Array ( 'Content-Type' => $is_html ? 'text/html' : 'text/plain', 'CHARSET' => $this->charset, 'DATA' => $encode ? $this->QuotedPrintableEncode($text) : $text, ); - + if ($encode) { $definition['Content-Transfer-Encoding'] = 'quoted-printable'; } - + $guess_name = $is_html ? 'html_part' : 'text_part'; $part_number = $this->guessOptions[$guess_name] !== false ? $this->guessOptions[$guess_name] : false; - + $part_number = $this->AddPart($definition, $part_number); $this->guessOptions[$guess_name] = $part_number; - + return $part_number; } - + /** * Adds attachment part to message * @@ -692,7 +692,7 @@ * @param string $content_type content type for attachment * @param string $content body of file to be attached * @param bool $inline is attachment inline or not - * + * * @return int number of created part */ function AddAttachment($file = '', $attach_name = '', $content_type = '', $content = '', $inline = false) @@ -701,42 +701,42 @@ 'Disposition' => $inline ? 'inline' : 'attachment', 'Content-Type' => $content_type ? $content_type : 'automatic/name', ); - + if ($file) { // filename of attachment given $definition['FileName'] = $file; } - + if ($attach_name) { // name of attachment given $definition['Name'] = $attach_name; } - + if ($content) { // attachment data is given $definition['Data'] = $content; } - + $definition =& $this->GetFileDefinition($definition); $part_number = $this->AddPart($definition); - + if ($inline) { // it's inline attachment and needs content-id to be addressed by in message $this->parts[$part_number]['Content-ID'] = md5(uniqid($part_number.time())).'.'.$this->GetFilenameExtension($attach_name ? $attach_name : $file); } - + $this->guessOptions[$inline ? 'inline_attachments' : 'attachments'][] = $part_number; - + return $part_number; } - + /** * Adds another MIME message as attachment to message being composed * * @param string $file name of the file with attachment body * @param string $attach_name name for attachment (name of file is used, when not specified) * @param string $content body of file to be attached - * + * * @return int number of created part */ function AddMessageAttachment($file = '', $attach_name = '', $content = '') @@ -745,7 +745,7 @@ unset($this->parts[$part_number]['Content-ID']); // messages don't have content-id, but have inline disposition return $part_number; } - + /** * Creates multipart of specified type and returns it's number * @@ -759,15 +759,15 @@ if (!in_array($multipart_type, $types)) { return $this->SetError('INVALID_MULTIPART_SUBTYPE', Array($multipart_type)); } - + $definition = Array ( 'Content-Type' => 'multipart/'.$multipart_type, 'PARTS' => $part_numbers, ); - + return $this->AddPart($definition); } - + /** * Creates missing content-id header for inline attachments * @@ -780,7 +780,7 @@ $part['Content-ID'] = md5(uniqid($part_number.time())).'.'.$this->GetFilenameExtension($part['NAME']); } } - + /** * Returns attachment part based on file used in attachment * @@ -803,31 +803,31 @@ // filename not specified || no filename + no direct file content return $this->SetError('MISSING_FILE_DATA'); } - + $encoding = 'base64'; if (isset($file['Content-Type'])) { $content_type = $file['Content-Type']; list ($type, $sub_type) = explode('/', $content_type); - + switch ($type) { case 'text': case 'image': case 'audio': case 'video': case 'application': break; - + case 'message': $encoding = '7bit'; break; - + case 'automatic': if (!$name) { return $this->SetError('MISSING_FILE_NAME'); } $this->guessContentType($name, $content_type, $encoding); break; - + default: return $this->SetError('INVALID_CONTENT_TYPE', Array($content_type)); } @@ -836,13 +836,13 @@ // encoding not passed in file part, then assume, that it's binary $content_type = 'application/octet-stream'; } - + $definition = Array ( 'Content-Type' => $content_type, 'Content-Transfer-Encoding' => $encoding, 'NAME' => $name, // attachment name ); - + if (isset($file['Disposition'])) { $disposition = strtolower($file['Disposition']); if ($disposition == 'inline' || $disposition == 'attachment') { @@ -853,24 +853,24 @@ return $this->SetError('INVALID_DISPOSITION', Array($file['Disposition'])); } } - + if (isset($file['FileName'])) { $definition['FILENAME'] = $file['FileName']; } elseif (isset($file['Data'])) { $definition['DATA'] =& $file['Data']; } - + return $definition; } - + /** * Returns content-type based on filename extension * * @param string $filename * @param string $content_type * @param string $encoding - * + * * @todo Regular expression used is not completely finished, that's why if extension used for * comparing in some other extension (from list) part, that partial match extension will be returned. * Because of two extension that begins with same 2 letters always belong to same content type @@ -879,7 +879,7 @@ function guessContentType($filename, &$content_type, &$encoding) { $file_extension = strtolower( $this->GetFilenameExtension($filename) ); - + $mapping = '(xls:application/excel)(hqx:application/macbinhex40)(doc,dot,wrd:application/msword)(pdf:application/pdf) (pgp:application/pgp)(ps,eps,ai:application/postscript)(ppt:application/powerpoint)(rtf:application/rtf) (tgz,gtar:application/x-gtar)(gz:application/x-gzip)(php,php3:application/x-httpd-php)(js:application/x-javascript) @@ -888,7 +888,7 @@ (gif:image/gif)(iff:image/iff)(jb2:image/jb2)(jpg,jpe,jpeg:image/jpeg)(jpx:image/jpx)(png:image/png)(tif,tiff:image/tiff) (wbmp:image/vnd.wap.wbmp)(xbm:image/xbm)(css:text/css)(txt:text/plain)(htm,html:text/html)(xml:text/xml) (mpg,mpe,mpeg:video/mpeg)(qt,mov:video/quicktime)(avi:video/x-ms-video)(eml:message/rfc822)'; - + if (preg_match('/[\(,]'.$file_extension.'[,]{0,1}.*?:(.*?)\)/s', $mapping, $regs)) { if ($file_extension == 'eml') { $encoding = '7bit'; @@ -899,7 +899,7 @@ $content_type = 'application/octet-stream'; } } - + /** * Using guess options combines all added parts together and returns combined part number * @@ -909,7 +909,7 @@ { if ($this->bodyPartNumber === false) { $part_number = false; // number of generated body part - + // 1. set text content of message if ($this->guessOptions['text_part'] !== false && $this->guessOptions['html_part'] !== false) { // text & html parts present -> compose into alternative part @@ -923,29 +923,29 @@ // only text part is defined, then leave as is $part_number = $this->guessOptions['text_part']; } - + if ($part_number === false) { return $this->SetError('MESSAGE_TEXT_MISSING'); } - + // 2. if inline attachments found, then create related multipart from text & inline attachments if ($this->guessOptions['inline_attachments']) { $parts = array_merge(Array($part_number), $this->guessOptions['inline_attachments']); $part_number = $this->CreateMultipart($parts, 'related'); } - + // 3. if normal attachments found, then create mixed multipart from text & attachments if ($this->guessOptions['attachments']) { $parts = array_merge(Array($part_number), $this->guessOptions['attachments']); $part_number = $this->CreateMultipart($parts, 'mixed'); } - + $this->bodyPartNumber = $part_number; } - + return $this->bodyPartNumber; } - + /** * Returns message headers and body part (by reference in parameters) * @@ -959,17 +959,17 @@ if ($part_number === false) { return $this->SetError('MESSAGE_COMPOSE_ERROR'); } - + $message_headers = $this->GetPartHeaders($part_number); - + // join message headers and body headers $message_headers = array_merge_recursive2($this->headers, $message_headers); - + $message_headers['MIME-Version'] = '1.0'; if ($this->mailerName) { $message_headers['X-Mailer'] = $this->mailerName; } - + $this->GenerateMessageID($message_headers['From']); $valid_headers = $this->ValidateHeaders($message_headers); if ($valid_headers) { @@ -980,14 +980,14 @@ $message_headers[$header_name] = $message_headers['From']; } } - + $message_body = $this->GetPartBody($part_number); return true; } - + return false; } - + /** * Checks that all required headers are set and not empty * @@ -1000,18 +1000,18 @@ if (!$from) { return $this->SetError('HEADER_MISSING', Array('From')); } - + if (!isset($message_headers['To'])) { return $this->SetError('HEADER_MISSING', Array('To')); } - + if (!isset($message_headers['Subject'])) { return $this->SetError('HEADER_MISSING', Array('Subject')); } - + return true; } - + /** * Returns full message source (headers + body) for sending to SMTP server * @@ -1026,16 +1026,16 @@ foreach ($message_headers as $header_name => $header_value) { $message .= $header_name.': '.$header_value.$this->line_break; } - + // add message body $message .= $this->line_break.$message_body; - + return $message; } - + return false; } - + /** * Sets just happened error code * @@ -1047,41 +1047,41 @@ { $error_msgs = Array ( 'MAIL_NOT_FOUND' => 'the mail() function is not available in this PHP installation', - + 'MISSING_CONTENT_TYPE' => 'it was added a part without Content-Type: defined', 'INVALID_CONTENT_TYPE' => 'Content-Type: %s not yet supported', 'INVALID_MULTIPART_SUBTYPE' => 'multipart Content-Type sub_type %s not yet supported', 'FILE_PART_OPEN_ERROR' => 'could not open part file %s', 'FILE_PART_DATA_ERROR' => 'the length of the file that was read does not match the size of the part file %s due to possible data corruption', 'FILE_PART_DATA_MISSING' => 'it was added a part without a body PART', 'INVALID_ENCODING' => '%s is not yet a supported encoding type', - + 'MISSING_FILE_DATA' => 'file part data is missing', 'MISSING_FILE_NAME' => 'it is not possible to determine content type from the name', 'INVALID_DISPOSITION' => '%s is not a supported message part content disposition', - + 'MESSAGE_TEXT_MISSING' => 'text part of message was not defined', 'MESSAGE_COMPOSE_ERROR' => 'unknown message composing error', - + 'HEADER_MISSING' => 'header %s is required', - + // SMTP errors 'INVALID_COMMAND' => 'Commands cannot contain newlines', 'CONNECTION_TERMINATED' => 'Connection was unexpectedly closed', 'HELO_ERROR' => 'HELO was not accepted: %s', 'AUTH_METHOD_NOT_SUPPORTED' => '%s is not a supported authentication method', 'AUTH_METHOD_NOT_IMPLEMENTED' => '%s is not a implemented authentication method', ); - + if (!is_array($params)) { $params = Array (); } - + trigger_error('mail error: '.vsprintf($error_msgs[$code], $params), $fatal ? E_USER_ERROR : E_USER_WARNING); - + return false; } - + /** * Simple method of message sending * @@ -1095,16 +1095,16 @@ { $this->SetSubject($subject); $this->SetFrom($from_email, trim($from_name) ? trim($from_name) : $from_email); - + if (!isset($this->headers['Return-Path'])) { $this->SetReturnPath($from_email); } - + $this->SetEncodedEmailHeader('To', $to_email, $to_name ? $to_name : $to_email); - + return $this->Deliver(); } - + /** * Prepares class for sending another message * @@ -1120,99 +1120,99 @@ 'text_part' => false, 'html_part' => false, ); - + $this->SetCharset(null, true); } - + /** * Sends message via php mail function * * @param Array $message_headers * @param string $body - * + * * @return bool */ function SendMail($message_headers, &$body) { if (!function_exists('mail')) { return $this->SetError('MAIL_NOT_FOUND'); } - + $to = $message_headers['To']; $subject = $message_headers['Subject']; $return_path = $message_headers['Return-Path']; unset($message_headers['To'], $message_headers['Subject']); - + $headers = ''; $header_separator = $this->Application->ConfigValue('MailFunctionHeaderSeparator') == 1 ? "\n" : "\r\n"; foreach ($message_headers as $header_name => $header_value) { $headers .= $header_name.': '.$header_value.$header_separator; } - + if ($return_path) { if (constOn('SAFE_MODE') || (defined('PHP_OS') && substr(PHP_OS, 0, 3) == 'WIN')) { // safe mode restriction OR is windows $return_path = ''; } } - + return mail($to, $subject, $body, $headers, $return_path ? '-f'.$return_path : null); } - + /** * Sends message via SMTP server * * @param Array $message_headers * @param string $body - * + * * @return bool */ function SendSMTP($message_headers, &$message_body) { if (!$this->SmtpConnect()) { return false; } - + $from = $this->ExtractRecipientEmail($message_headers['From']); if (!$this->SmtpSetFrom($from)) { return false; } - + $recipients = ''; $recipient_headers = Array ('To', 'Cc', 'Bcc'); foreach ($recipient_headers as $recipient_header) { if (isset($message_headers[$recipient_header])) { $recipients .= ' '.$message_headers[$recipient_header]; } } - + $recipients_accepted = 0; $recipients = $this->ExtractRecipientEmail($recipients, true); foreach ($recipients as $recipient) { if ($this->SmtpAddTo($recipient)) { $recipients_accepted++; } } - + if ($recipients_accepted == 0) { // none of recipients were accepted return false; } - + $headers = ''; foreach ($message_headers as $header_name => $header_value) { $headers .= $header_name.': '.$header_value.$this->line_break; } - + if (!$this->SmtpSendMessage($headers . "\r\n" . $message_body)) { return false; } - + $this->SmtpDisconnect(); - + return true; } - + /** * Send a command to the server with an optional string of * arguments. A carriage return / linefeed (CRLF) sequence will @@ -1230,14 +1230,14 @@ if (!empty($args)) { $command .= ' ' . $args; } - + if (strcspn($command, "\r\n") !== strlen($command)) { return $this->SetError('INVALID_COMMAND'); } - + return $this->smtpSocket->write($command . "\r\n") === false ? false : true; } - + /** * Read a reply from the SMTP server. The reply consists of a response code and a response message. * @@ -1250,37 +1250,37 @@ { $this->smtpResponceCode = -1; $this->smtpRespoceArguments = array(); - + while ($line = $this->smtpSocket->readLine()) { // If we receive an empty line, the connection has been closed. if (empty($line)) { $this->SmtpDisconnect(); return $this->SetError('CONNECTION_TERMINATED', null, false); } - + // Read the code and store the rest in the arguments array. $code = substr($line, 0, 3); $this->smtpRespoceArguments[] = trim(substr($line, 4)); - + // Check the syntax of the response code. if (is_numeric($code)) { $this->smtpResponceCode = (int)$code; } else { $this->smtpResponceCode = -1; break; } - + // If this is not a multiline response, we're done. if (substr($line, 3, 1) != '-') { break; } } - + // Compare the server's response code with the valid code. if (is_int($valid) && ($this->smtpResponceCode === $valid)) { return true; } - + // If we were given an array of valid response codes, check each one. if (is_array($valid)) { foreach ($valid as $valid_code) { @@ -1289,44 +1289,44 @@ } } } - + return false; } - + /** * Attempt to connect to the SMTP server. * * @param int $timeout The timeout value (in seconds) for the socket connection. * @param bool $persistent Should a persistent socket connection be used ? * * @return bool - * + * */ function SmtpConnect($timeout = null, $persistent = false) { $result = $this->smtpSocket->connect($this->smtpParams['server'], $this->smtpParams['port'], $persistent, $timeout); if (!$result) { return false; } - + if ($this->SmtpParseResponse(220) === false) { return false; } elseif ($this->SmtpNegotiate() === false) { return false; } - + if ($this->smtpParams['use_auth']) { $result = $this->SmtpAuthentificate($this->smtpParams['username'], $this->smtpParams['password']); if (!$result) { // authentification failed return false; } } - + return true; } - + /** * Attempt to disconnect from the SMTP server. * @@ -1340,10 +1340,10 @@ elseif ($this->SmtpParseResponse(221) === false) { return false; } - + return $this->smtpSocket->disconnect(); } - + /** * Attempt to send the EHLO command and obtain a list of ESMTP * extensions available, and failing that just send HELO. @@ -1355,34 +1355,34 @@ if (!$this->SmtpSendCommand('EHLO', $this->smtpParams['localhost'])) { return false; } - + if (!$this->SmtpParseResponse(250)) { // If we receive a 503 response, we're already authenticated. if ($this->smtpResponceCode === 503) { return true; } - + // If the EHLO failed, try the simpler HELO command. if (!$this->SmtpSendCommand('HELO', $this->smtpParams['localhost'])) { return false; } - + if (!$this->SmtpParseResponse(250)) { return $this->SetError('HELO_ERROR', Array($this->smtpResponceCode), false); } - + return true; } - + foreach ($this->smtpRespoceArguments as $argument) { $verb = strtok($argument, ' '); $arguments = substr($argument, strlen($verb) + 1, strlen($argument) - strlen($verb) - 1); $this->smtpFeatures[$verb] = $arguments; } - + return true; } - + /** * Attempt to do SMTP authentication. * @@ -1398,9 +1398,9 @@ // server doesn't understand AUTH command, then don't authentificate return true; } - + $available_methods = explode(' ', $this->smtpFeatures['AUTH']); // methods supported by SMTP server - + if (empty($method)) { foreach ($this->smtpAuthMethods as $supported_method) { // check if server supports methods, that we have implemented @@ -1412,17 +1412,17 @@ } else { $method = strtoupper($method); } - + if (!in_array($method, $available_methods)) { // coosen method is not supported by server return $this->SetError('AUTH_METHOD_NOT_SUPPORTED', Array($method)); } - + switch ($method) { case 'CRAM-MD5': $result = $this->_authCRAM_MD5($uid, $pwd); break; - + case 'LOGIN': $result = $this->_authLogin($uid, $pwd); break; @@ -1433,10 +1433,10 @@ return $this->SetError('AUTH_METHOD_NOT_IMPLEMENTED', Array($method)); break; } - + return $result; } - + /** * Function which implements HMAC MD5 digest * @@ -1449,20 +1449,20 @@ if (strlen($key) > 64) { $key = pack('H32', md5($key)); } - + if (strlen($key) < 64) { $key = str_pad($key, 64, chr(0)); } - + $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64); $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64); - + $inner = pack('H32', md5($k_ipad . $data)); $digest = md5($k_opad . $inner); - + return $digest; } - + /** * Authenticates the user using the CRAM-MD5 method. * @@ -1476,28 +1476,28 @@ if (!$this->SmtpSendCommand('AUTH', 'CRAM-MD5')) { return false; } - + // 334: Continue authentication request if (!$this->SmtpParseResponse(334)) { // 503: Error: already authenticated return $this->smtpResponceCode === 503 ? true : false; } - + $challenge = base64_decode($this->smtpRespoceArguments[0]); $auth_str = base64_encode($uid . ' ' . $this->_HMAC_MD5($pwd, $challenge)); - + if (!$this->SmtpSendCommand($auth_str)) { return false; } - + // 235: Authentication successful if (!$this->SmtpParseResponse(235)) { return false; } - + return true; } - + /** * Authenticates the user using the LOGIN method. * @@ -1511,34 +1511,34 @@ if (!$this->SmtpSendCommand('AUTH', 'LOGIN')) { return false; } - + // 334: Continue authentication request if (!$this->SmtpParseResponse(334)) { // 503: Error: already authenticated return $this->smtpResponceCode === 503 ? true : false; } - + if (!$this->SmtpSendCommand(base64_encode($uid))) { return false; } - + // 334: Continue authentication request if (!$this->SmtpParseResponse(334)) { return false; } - + if (!$this->SmtpSendCommand(base64_encode($pwd))) { return false; } - + // 235: Authentication successful if (!$this->SmtpParseResponse(235)) { return false; } - + return true; } - + /** * Authenticates the user using the PLAIN method. * @@ -1552,27 +1552,27 @@ if (!$this->SmtpSendCommand('AUTH', 'PLAIN')) { return false; } - + // 334: Continue authentication request if (!$this->SmtpParseResponse(334)) { // 503: Error: already authenticated return $this->smtpResponceCode === 503 ? true : false; } - + $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd); - + if (!$this->SmtpSendCommand($auth_str)) { return false; } - + // 235: Authentication successful if (!$this->SmtpParseResponse(235)) { return false; } - + return true; } - + /** * Send the MAIL FROM: command. * @@ -1587,17 +1587,17 @@ if (is_string($params)) { $args .= ' ' . $params; } - + if (!$this->SmtpSendCommand('MAIL', $args)) { return false; } if (!$this->SmtpParseResponse(250)) { return false; } - + return true; } - + /** * Send the RCPT TO: command. * @@ -1612,18 +1612,18 @@ if (is_string($params)) { $args .= ' ' . $params; } - + if (!$this->SmtpSendCommand('RCPT', $args)) { return false; } - + if (!$this->SmtpParseResponse(array(250, 251))) { return false; } - + return true; } - + /** * Send the DATA command. * @@ -1644,33 +1644,33 @@ return $this->SetError('Message size excedes the server limit', null, false); } } - + // Quote the data based on the SMTP standards - + // Change Unix (\n) and Mac (\r) linefeeds into Internet-standard CRLF (\r\n) linefeeds. $data = preg_replace(Array('/(?SmtpSendCommand('DATA')) { return false; } if (!$this->SmtpParseResponse(354)) { return false; } - + if ($this->smtpSocket->write($data . "\r\n.\r\n") === false) { return false; } if (!$this->SmtpParseResponse(250)) { return false; } - + return true; } - + /** * Sets global charset for every message part * @@ -1682,13 +1682,13 @@ if ($is_system) { $language =& $this->Application->recallObject('lang.current'); /* @var $language LanguagesItem */ - + $charset = $language->GetDBField('Charset') ? $language->GetDBField('Charset') : 'ISO-8859-1'; } - + $this->charset = $charset; } - + /** * Allows to extract recipient's name from text by specifying it's email * @@ -1705,7 +1705,7 @@ } return $name; } - + /** * Takes $text and returns an email address from it * Set $multiple to true to retrieve all found addresses @@ -1736,7 +1736,7 @@ } } } - + /** * Returns array of recipient names and emails * @@ -1748,13 +1748,13 @@ { // by MIME specs recipients should be separated using "," symbol, // but users can write ";" too (like in OutLook) - + if (!trim($list)) { return false; } - + $list = explode(',', str_replace($separator, ',', $list)); - + $ret = Array (); foreach ($list as $recipient) { $email = $this->ExtractRecipientEmail($recipient); @@ -1765,14 +1765,14 @@ $name = $this->ExtractRecipientName($recipient, $email); $ret[] = Array('Name' => $name, 'Email' => $email); } - - return $ret; + + return $ret; } - + /* methods for nice header setting */ - + /** - * Sets "From" header. + * Sets "From" header. * * @param string $email * @param string $first_last_name FirstName and LastName or just FirstName @@ -1782,12 +1782,12 @@ { $name = rtrim($first_last_name.' '.$last_name, ' '); $this->SetEncodedEmailHeader('From', $email, $name ? $name : $email); - + if (!isset($this->headers['Return-Path'])) { $this->SetReturnPath($email); } } - + /** * Sets "Return-Path" header (useful for spammers) * @@ -1797,7 +1797,7 @@ { $this->SetHeader('Return-Path', $email); } - + /** * Adds one more recipient into "To" header * @@ -1810,7 +1810,7 @@ $name = rtrim($first_last_name.' '.$last_name, ' '); $this->AddRecipient('To', $email, $name); } - + /** * Adds one more recipient into "Cc" header * @@ -1823,7 +1823,7 @@ $name = rtrim($first_last_name.' '.$last_name, ' '); $this->AddRecipient('Cc', $email, $name); } - + /** * Adds one more recipient into "Bcc" header * @@ -1836,7 +1836,7 @@ $name = rtrim($first_last_name.' '.$last_name, ' '); $this->AddRecipient('Bcc', $email, $name); } - + /** * Adds one more recipient to specified header * @@ -1849,14 +1849,14 @@ if (!$name) { $name = $email; } - + $value = isset($this->headers[$header_name]) ? $this->headers[$header_name] : ''; $value .= ', '.$this->QuotedPrintableEncode($name, $this->charset).' <'.$email.'>'; - + $value = preg_replace('/^,(.*)/', '\\1', $value); // remove first comma $this->SetHeader($header_name, $value); } - + /** * Sets "Subject" header. * @@ -1866,7 +1866,7 @@ { $this->setEncodedHeader('Subject', $subject); } - + /** * Sets HTML part of message * @@ -1876,7 +1876,7 @@ { $this->CreateTextHtmlPart($html, true); } - + /** * Sets Plain-Text part of message * @@ -1886,7 +1886,7 @@ { $this->CreateTextHtmlPart($plain_text); } - + /** * Sets HTML and optionally plain part of the message * @@ -1900,10 +1900,10 @@ $this->SetPlain($plain_text); } } - + /** * Performs mail delivery (supports delayed delivery) - * + * * @param string $mesasge message, if not given, then use composed one * @param bool $immediate_send send message now * @param bool $immediate_clear clear message parts after message is sent @@ -1931,7 +1931,7 @@ // direct message not given, then assemble message from available parts $composed = $this->GetHeadersAndBody($message_headers, $message_body); } - + if ($composed) { if ($immediate_send) { $send_method = 'Send'.$this->sendMethod; @@ -1953,13 +1953,13 @@ $fields_hash['MessageHeaders'] = serialize($message_headers); $fields_hash['MessageBody'] =& $message_body; $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'EmailQueue'); - + if ($immediate_clear) { $this->Clear(); } } } - + // if not immediate send, then send result is positive :) return !$immediate_send ? true : false; }