guessOptions = 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('kSocket'); } $this->SetCharset(null, true); } /** * Returns new message id header by sender's email address * * @param string $email_address email address * @return string */ function GenerateMessageID($email_address) { 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 * * @param string $filename * @return string */ function GetFilenameExtension($filename) { $last_dot = mb_strrpos($filename, '.'); return $last_dot !== false ? mb_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 * * @param int $part_number * @return Array */ 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': case 'image': case 'audio': case 'video': case 'application': case 'message': // 1. update content-type header if (isset($part['CHARSET'])) { $headers['Content-Type'] .= '; charset='.$part['CHARSET']; } 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']; if (isset($part['NAME'])) { $headers['Content-Disposition'] .= '; filename="'.$part['NAME'].'"'; } } break; case 'multipart': switch ($sub_type) { case 'alternative': case 'related': case 'mixed': case 'parallel': $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 case 'text': case 'image': case 'audio': case 'video': case 'application': case 'message': // 1. get content of part if (isset($part['FILENAME'])) { // 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)); } } else { // content provided directly as one of part keys if (!isset($part['DATA'])) { return $this->SetError('FILE_PART_DATA_MISSING'); } $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) { case 'alternative': case 'related': case 'mixed': 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 * * @param string $text * @param string $header_charset * @param int $break_lines * @return unknown */ function QuotedPrintableEncode($text, $header_charset = '', $break_lines = 1) { $ln = strlen($text); $h = strlen($header_charset) > 0; if ($h) { $s = Array ( '=' => 1, '?' => 1, '_' => 1, '(' => 1, ')' => 1, '<' => 1, '>' => 1, '@' => 1, ',' => 1, ';' => 1, '"' => 1, '\\' => 1, /* '/' => 1, '[' => 1, ']' => 1, ':' => 1, '.' => 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: $space = $i + 1; $b = 1; break 2; case 10: case 13: break 2; default: if ($o < 32 || $o > 127) { $b = 1; break 2; } } } 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); $en = 0; switch ($o) { case 9: case 32: if (!$h) { $w = $c; $c = ''; } else { if ($b) { if ($o == 32) { $c = '_'; } else { $en = 1; } } } break; case 10: case 13: if (strlen($w)) { if ($break_lines && $l + 3 > 75) { $e .= '='.$this->line_break; $l = 0; } $e .= sprintf('=%02X', ord($w)); $l += 3; $w = ''; } $e .= $c; if ($h) { $e .= "\t"; } $l = 0; continue 2; case 46: case 70: case 102: $en = (!$h && ($l == 0 || $l + 1 > 75)); break; default: if ($o > 127 || $o < 32 || !strcmp($c, '=')) { $en = 1; } elseif ($h && isset($s[$c])) { $en = 1; } break; } if (strlen($w)) { if ($break_lines && $l + 1 > 75) { $e .= '='.$this->line_break; $l = 0; } $e .= $w; $l++; $w = ''; } if (strlen($c)) { if ($en) { $c = sprintf('=%02X', $o); $el = 3; $n = 1; $b = 1; } else { $el = 1; } if ($break_lines && $l + $el > 75) { $e .= '='.$this->line_break; $l = 0; } $e .= $c; $l += $el; } } if (strlen($w)) { if ($break_lines && $l + 3 > 75) { $e .= '='.$this->line_break; } $e .= sprintf('=%02X', ord($w)); } return $h && $n ? '=?'.$header_charset.'?q?'.$e.'?=' : $e; } /** * Sets message header + encodes is by quoted-printable using charset specified * * @param string $name * @param string $value * @param string $encoding_charset */ function SetHeader($name, $value, $encoding_charset = '') { if ($encoding_charset) { // 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 * * @param string $name * @param string $value */ function SetEncodedHeader($name, $value) { $this->SetHeader($name, $value, $this->charset); } /** * Sets header which value is email and username +autoencode * * @param string $header * @param string $address * @param string $name */ function SetEncodedEmailHeader($header, $address, $name) { $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 . '>, '; } $this->SetHeader($header, substr($value, 0, -2)); } /** * Adds new part to message and returns it's number * * @param Array $part_definition * @param int|bool $part_number number of new part * @return int */ function AddPart(&$part_definition, $part_number = false) { $part_number = $part_number !== false ? $part_number : count($this->parts); $this->parts[$part_number] =& $part_definition; return $part_number; } /** * Detects if such part already was added. * * @param array $part_definition Part definition. * * @return boolean */ protected function HasPart(array $part_definition) { foreach ( $this->parts as $part ) { if ( $part === $part_definition ) { return true; } } return false; } /** * Returns text version of HTML document * * @param string $html * @param bool $keep_inp_tags * @return string */ function ConvertToText($html, $keep_inp_tags = false) { if ( $keep_inp_tags && preg_match_all('/(<[\\/]?)inp2:([^>]*?)([\\/]?>)/s', $html, $regs) ) { $found_tags = Array (); foreach ($regs[0] as $index => $tag) { $tag_placeholder = '%' . md5($index . ':' . $tag) . '%'; $found_tags[$tag_placeholder] = $tag; // we can have duplicate tags -> replace only 1st occurrence (str_replace can't do that) $html = preg_replace('/' . preg_quote($tag, '/') . '/', $tag_placeholder, $html, 1); } $html = $this->_convertToText($html); foreach ($found_tags as $tag_placeholder => $tag) { $html = str_replace($tag_placeholder, $tag, $html); } return $html; } return $this->_convertToText($html); } /** * Returns text version of HTML document * * @param string $html * @return string */ protected function _convertToText($html) { $search = Array ( "'(<\/td>.*)[\r\n]+(.*