Fisheye: Tag 7748 refers to a dead (removed) revision in file `branches/unlabeled/unlabeled-1.3.62/core/kernel/utility/smtp_client.php'. Fisheye: No comparison available. Pass `N' to diff? Index: branches/unlabeled/unlabeled-1.5.2/core/install/install_data.sql =================================================================== diff -u -r7712 -r7748 --- branches/unlabeled/unlabeled-1.5.2/core/install/install_data.sql (.../install_data.sql) (revision 7712) +++ branches/unlabeled/unlabeled-1.5.2/core/install/install_data.sql (.../install_data.sql) (revision 7748) @@ -63,6 +63,7 @@ INSERT INTO ConfigurationAdmin VALUES ('NoPermissionTemplate', 'la_Text_Website', 'la_config_nopermission_template', 'text', '', '', 10.17, 0, 0); INSERT INTO ConfigurationAdmin (VariableName, heading, prompt, element_type, validation, ValueList, DisplayOrder, GroupDisplayOrder, Install) VALUES ('UseOutputCompression', 'la_Text_Website', 'la_config_UseOutputCompression', 'checkbox', '', '', 10.18, 0, 1); INSERT INTO ConfigurationAdmin (VariableName, heading, prompt, element_type, validation, ValueList, DisplayOrder, GroupDisplayOrder, Install) VALUES ('OutputCompressionLevel', 'la_Text_Website', 'la_config_OutputCompressionLevel', 'text', '', '', 10.19, 0, 1); +INSERT INTO ConfigurationAdmin VALUES ('MailFunctionHeaderSeparator', 'la_Text_smtp_server', 'la_config_MailFunctionHeaderSeparator', 'radio', NULL, '1=la_Linux,2=la_Windows', 30.08, 0, 0); INSERT INTO ConfigurationValues VALUES (NULL, 'Columns_Category', '2', 'In-Portal', 'Categories'); INSERT INTO ConfigurationValues VALUES (NULL, 'DomainSelect','1','In-Portal','in-portal:configure_general'); @@ -198,6 +199,7 @@ INSERT INTO ConfigurationValues VALUES (NULL, 'NoPermissionTemplate', 'no_permission', 'In-Portal', 'in-portal:configure_general'); INSERT INTO ConfigurationValues (VariableName, VariableValue, ModuleOwner, Section) VALUES ('UseOutputCompression', '0', 'In-Portal', 'in-portal:configure_general'); INSERT INTO ConfigurationValues (VariableName, VariableValue, ModuleOwner, Section) VALUES ('OutputCompressionLevel', '7', 'In-Portal', 'in-portal:configure_general'); +INSERT INTO ConfigurationValues VALUES (0, 'MailFunctionHeaderSeparator', 1, 'In-Portal', 'in-portal:configure_general'); INSERT INTO Events VALUES (30, 'USER.ADD', 1, 0, 'In-Portal:Users', 'la_event_user.add', 0); INSERT INTO Events VALUES (32, 'USER.ADD', 2, 0, 'In-Portal:Users', 'la_event_user.add', 1); Index: branches/unlabeled/unlabeled-1.1.2/admin/install/upgrades/inportal_upgrade_v4.0.1.sql =================================================================== diff -u -r7712 -r7748 --- branches/unlabeled/unlabeled-1.1.2/admin/install/upgrades/inportal_upgrade_v4.0.1.sql (.../inportal_upgrade_v4.0.1.sql) (revision 7712) +++ branches/unlabeled/unlabeled-1.1.2/admin/install/upgrades/inportal_upgrade_v4.0.1.sql (.../inportal_upgrade_v4.0.1.sql) (revision 7748) @@ -3,4 +3,9 @@ ALTER TABLE EmailMessage ADD `Subject` TINYTEXT NULL ; +ALTER TABLE EmailLog ADD EventParams TEXT NOT NULL; + +INSERT INTO ConfigurationAdmin VALUES ('MailFunctionHeaderSeparator', 'la_Text_smtp_server', 'la_config_MailFunctionHeaderSeparator', 'radio', NULL, '1=la_Linux,2=la_Windows', 30.08, 0, 0); +INSERT INTO ConfigurationValues VALUES (0, 'MailFunctionHeaderSeparator', 1, 'In-Portal', 'in-portal:configure_general'); + UPDATE Modules SET Version = '4.0.1' WHERE Name = 'In-Portal'; \ No newline at end of file Index: branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/socket.php =================================================================== diff -u --- branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/socket.php (revision 0) +++ branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/socket.php (revision 7748) @@ -0,0 +1,361 @@ +fp)) { + @fclose($this->fp); + $this->fp = null; + } + + // convert hostname to ip address + if (!$addr) { + return $this->raiseError('host address cannot be empty'); + } elseif (strspn($addr, '.0123456789') == strlen($addr) || strstr($addr, '/') !== false) { + $this->addr = $addr; + } else { + $this->addr = @gethostbyname($addr); + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + if ($timeout !== null) { + $this->timeout = $timeout; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + + if ($this->timeout) { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); + } + + if (!$fp) { + return $this->raiseError($errstr, Array($errno)); + } + + $this->fp = $fp; + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + socket_set_blocking($this->fp, $this->blocking); + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setTimeout($seconds, $microseconds) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_set_timeout($this->fp, $seconds, $microseconds); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + *

+ * timed_out (bool) - The socket timed out waiting for data
+ * blocked (bool) - The socket was blocked
+ * eof (bool) - Indicates EOF event
+ * unread_bytes (int) - Number of bytes left in the socket buffer
+ *

+ * + * @access public + * @return mixed Array containing information about existing socket resource or an error object otherwise + */ + function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_get_status($this->fp); + } + + /** + * Get a specified line of data + * + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function gets($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fgets($this->fp, $size); + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (is_null($blocksize) && !OS_WINDOWS) { + return fwrite($this->fp, $data); + } else { + if (is_null($blocksize)) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + if ($written === false) { + return false; + } + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing "\r\n". + * + * @access public + * @return mixed fputs result, or an error + */ + function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . "\r\n"); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * @access public + * @return bool + */ + function eof() + { + return (is_resource($this->fp) && feof($this->fp)); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= @fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + return rtrim($line, "\r\n"); + } + } + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + while (!feof($this->fp)) { + $data .= @fread($this->fp, $this->lineLength); + } + return $data; + } + + function raiseError($text, $params = Array()) + { + trigger_error(vsprintf($text, $params), E_USER_WARNING); + return false; + } + } + +?> \ No newline at end of file Index: branches/unlabeled/unlabeled-1.4.2/core/kernel/constants.php =================================================================== diff -u -r7652 -r7748 --- branches/unlabeled/unlabeled-1.4.2/core/kernel/constants.php (.../constants.php) (revision 7652) +++ branches/unlabeled/unlabeled-1.4.2/core/kernel/constants.php (.../constants.php) (revision 7748) @@ -45,4 +45,8 @@ // don't show debugger buttons on front (if not overrided in "debug.php") safeDefine('DBG_TOOLBAR_BUTTONS', 0); } + + // common usage regular expressions + define('REGEX_EMAIL_USER', '[-a-zA-Z0-9!\#$%&*+\/=?^_`{|}~.]+'); + define('REGEX_EMAIL_DOMAIN', '[a-zA-Z0-9]{1}[-.a-zA-Z0-9_]*\.[a-zA-Z]{2,6}'); ?> \ No newline at end of file Index: branches/unlabeled/unlabeled-1.4.2/core/install/install_schema.sql =================================================================== diff -u -r7652 -r7748 --- branches/unlabeled/unlabeled-1.4.2/core/install/install_schema.sql (.../install_schema.sql) (revision 7652) +++ branches/unlabeled/unlabeled-1.4.2/core/install/install_schema.sql (.../install_schema.sql) (revision 7748) @@ -286,9 +286,10 @@ EmailLogId int(11) NOT NULL auto_increment, fromuser varchar(200) default NULL, addressto varchar(255) default NULL, - subject varchar(255) default NULL, - timestamp bigint(20) default '0', + `subject` varchar(255) default NULL, + `timestamp` bigint(20) default '0', event varchar(100) default NULL, + EventParams text NOT NULL, PRIMARY KEY (EmailLogId) ); Index: branches/unlabeled/unlabeled-1.33.2/core/units/categories/categories_tag_processor.php =================================================================== diff -u -r7650 -r7748 --- branches/unlabeled/unlabeled-1.33.2/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 7650) +++ branches/unlabeled/unlabeled-1.33.2/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 7748) @@ -184,13 +184,11 @@ // $cat_id = $this->Application->Parser->GetParam('cat_id'); $cat_id = $this->Application->GetVar($this->getPrefixSpecial().'_id'); } - if($cat_id == 'Root') - { - $object =& $this->Application->recallObject('mod.'.$params['module']); - $params['m_cat_id'] = $object->GetDBField('RootCat'); + if ("$cat_id" == 'Root') { + $params['m_cat_id'] = $this->Application->findModule('Name', $params['module'], 'RootCat'); unset($params['module']); } - else{ + else { $params['m_cat_id'] = $cat_id; } unset($params['cat_id']); Fisheye: Tag 7748 refers to a dead (removed) revision in file `branches/unlabeled/unlabeled-1.16.8/core/kernel/utility/email.php'. Fisheye: No comparison available. Pass `N' to diff? Index: branches/unlabeled/unlabeled-1.29.2/core/units/email_events/email_events_event_handler.php =================================================================== diff -u -r7696 -r7748 --- branches/unlabeled/unlabeled-1.29.2/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 7696) +++ branches/unlabeled/unlabeled-1.29.2/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 7748) @@ -1,5 +1,12 @@ getEventParam('EmailEventName'); - if( strpos($email_event, '_') !== false ) - { - trigger_error('Invalid email event name '.$email_event.'. Use only UPPERCASE characters and dots as email event names', E_USER_ERROR); + function GetMessageRecipients(&$event, &$event_id) + { + $email_event =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $email_event kDBItem */ + + // get event parameters by name & type + $load_keys = Array ( + 'Event' => $event->getEventParam('EmailEventName'), + 'Type' => $event->getEventParam('EmailEventType'), + ); + $email_event->Load($load_keys); + + if (!$email_event->isLoaded()) { + // event record not found + return false; } + + $enabled = $email_event->GetDBField('Enabled'); + if ($enabled == EVENT_STATUS_DISABLED) { + return false; + } + if ($enabled == EVENT_STATUS_FRONTEND && $this->Application->IsAdmin()) { + return false; + } + // initial values $to_user_id = $event->getEventParam('EmailEventToUserId'); - $email_event_type = $event->getEventParam('EmailEventType'); - - $message_object = &$this->Application->recallObject('emailmessages', null, Array('skip_autoload' => true)); - $event_table = $this->Application->getUnitOption('emailevents', 'TableName'); - - $event_object =& $event->getObject( Array('skip_autoload' => true) ); - $event_object->Load(array('Event'=>$email_event, 'Type'=>$email_event_type)); - - $event_id = $event_object->GetDBField('EventId'); - $from_user_id = $event_object->GetDBField('FromUserId'); - $type = $event_object->GetDBField('Type'); - $enabled = $event_object->GetDBField('Enabled'); - - $direct_send_params = $event->getEventParam('DirectSendParams'); - - if ($enabled == 0) return; // disabled event - if ($enabled == 2 && $this->Application->IsAdmin() ) return; // event only for front-end - - if ($type == 1){ + $from_user_id = $email_event->GetDBField('FromUserId'); + + if ($email_event->GetDBField('Type') == EVENT_TYPE_ADMIN) { // For type "Admin" recipient is a user from field FromUserId which means From/To user in Email events list $to_user_id = $from_user_id; $from_user_id = -1; } - /* - if (!($to_user_id > 0) && !$direct_send_params){ - // if we can not determine recepient we will not send email - return; + + $event_id = $email_event->GetDBField('EventId'); + + return Array ($from_user_id, $to_user_id); + } + + /** + * Returns user name, email by id, or ones, that specified in $direct_params + * + * @param int $user_id + * @param string $user_type type of user = {to,from} + * @param Array $direct_params + * @return Array + */ + function GetRecipientInfo($user_id, $user_type, $direct_params = null) + { + // load user, because it can be addressed from email template tags + $user =& $this->Application->recallObject('u.email-'.$user_type, null, Array('skip_autoload' => true)); + /* @var $user UsersItem */ + + $email = $name = ''; + $result = $user_id > 0 ? $user->Load($user_id) : $user->Clear(); + if ($user->IsLoaded()) { + $email = $user->GetDBField('Email'); + $name = trim($user->GetDBField('FirstName').' '.$user->GetDBField('LastName')); } - */ - //Parse Message Template - $message_object->Load(array('EventId' => $event_id, 'LanguageId' => $this->Application->GetVar('m_lang'))); - $message_type = $message_object->GetDBField('MessageType'); - - $email_object = &$this->Application->recallObject('kEmailMessage'); - $email_object->Clear(); - - // add footer: begin + + if (is_array($direct_params)) { + if (isset($direct_params[$user_type.'_email'])) { + $email = $direct_params[$user_type.'_email']; + } + + if (isset($direct_params[$user_type.'_name'])) { + $name = $direct_params[$user_type.'_name']; + } + } + + if (!$email) { + // if email is empty, then use admins email + $email = $this->Application->ConfigValue('Smtp_AdminMailFrom'); + } + + if (!$name) { + $name = $user_type == 'from' ? strip_tags($this->Application->ConfigValue('Site_Name')) : $email; + } + + return Array ($email, $name); + } + + /** + * Returns email event message by ID (headers & body in one piece) + * + * @param int $event_id + * @param string $message_type contains message type = {text,html} + */ + function GetMessageBody($event_id, &$message_type) + { + $current_language = $this->Application->GetVar('m_lang'); + $message =& $this->Application->recallObject('emailmessages', null, Array('skip_autoload' => true)); + /* @var $message kDBItem */ + + $message->Load( Array('EventId' => $event_id, 'LanguageId' => $current_language) ); + if (!$message->isLoaded()) { + // event translation on required language not found + return false; + } + + $message_type = $message->GetDBField('MessageType'); + + // 1. get message body + $message_body = $message->GetDBField('Template'); + + // 2. add footer $sql = 'SELECT em.Template - FROM '.$message_object->TableName.' em + FROM '.$message->TableName.' em LEFT JOIN '.TABLE_PREFIX.'Events e ON e.EventId = em.EventId - WHERE em.LanguageId = '.$message_object->GetDBField('LanguageId').' AND e.Event = "COMMON.FOOTER"'; - $footer = explode("\n\n", $this->Conn->GetOne($sql)); - $footer = $message_object->GetDBField('MessageType') == 'text' ? $email_object->convertHTMLtoPlain($footer[1]) : $footer[1]; - $message_template = $message_object->GetDBField('Template')."\r\n".$footer; - // add footer: end - - $from_user_object = &$this->Application->recallObject('u.email-from', null, Array('skip_autoload' => true)); - $from_user_object->Load($from_user_id); - // here if we don't have from_user loaded, it takes a default user from config values - if ( $from_user_object->IsLoaded() ) { - $from_user_email = $from_user_object->GetDBField('Email'); - $from_user_name = trim($from_user_object->GetDBField('FirstName').' '.$from_user_object->GetDBField('LastName')); + WHERE em.LanguageId = '.$current_language.' AND e.Event = "COMMON.FOOTER"'; + + list (, $footer) = explode("\n\n", $this->Conn->GetOne($sql)); + if ($message_type == 'text') { + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + $footer = $esender->ConvertToText($footer); } - else { - $from_user_email = $this->Application->ConfigValue('Smtp_AdminMailFrom'); + + if ($footer) { + $message_body .= "\r\n".$footer; } + + // 3. replace tags if needed + // $replacement_tags = $message->GetDBField('ReplacementTags'); + // $replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array (); + + $replacement_tags = Array ( + ' ' ' 'Application->recallObject('u.email-to', null, Array('skip_autoload' => true)); - $to_user_object->Load($to_user_id); - $to_user_email = $to_user_object->GetDBField('Email'); - $to_user_name = trim($to_user_object->GetDBField('FirstName').' '.$to_user_object->GetDBField('LastName')); - - if($direct_send_params){ - $to_user_email = ( $direct_send_params['to_email'] ? $direct_send_params['to_email'] : $to_user_email ); - $to_user_name = ( $direct_send_params['to_name'] ? $direct_send_params['to_name'] : $to_user_name ); - $from_user_email = ( $direct_send_params['from_email'] ? $direct_send_params['from_email'] : $from_user_email); - $from_user_name = ( $direct_send_params['from_name'] ? $direct_send_params['from_name'] : $from_user_name ); - $message_body_additional = $direct_send_params['message']; + foreach ($replacement_tags as $replace_from => $replace_to) { + $message_body = str_replace($replace_from, $replace_to, $message_body); } - - $to_user_email = $to_user_email ? $to_user_email : $this->Application->ConfigValue('Smtp_AdminMailFrom'); - - $this->Application->makeClass('Template'); + + return $message_body; + } + + /** + * Parse message template and return headers (as array) and message body part + * + * @param string $message + * @param Array $direct_params + * @return Array + */ + function ParseMessageBody($message, $direct_params = null) + { + $direct_params['message_text'] = isset($direct_params['message']) ? $direct_params['message'] : ''; // parameter alias + + // 1. parse template $this->Application->InitParser(); - $parser_params = $this->Application->Parser->Params; - $direct_send_params['message_text'] = $message_body_additional; - $this->Application->Parser->Params = array_merge_recursive2($this->Application->Parser->Params, $direct_send_params); - $message_template = str_replace('Application->Parser->Parse($message_template, 'email_template', 0); - $this->Application->Parser->Params = $parser_params; + $parser_params = $this->Application->Parser->Params; // backup parser params + $this->Application->Parser->Params = array_merge_recursive2($this->Application->Parser->Params, $direct_params); + $message = $this->Application->Parser->Parse($message, 'email_template', 0); + $this->Application->Parser->Params = $parser_params; // restore parser params - $message_template = str_replace("\r", "", $message_template); + // 2. replace line endings, that are send with data submitted via request + $message = str_replace("\r\n", "\n", $message); // possible case + $message = str_replace("\r", "\n", $message); // impossible case, but just in case replace this too - list($message_headers, $message_body) = explode("\n\n", $message_template, 2); + // 3. separate headers from body + $message_headers = Array (); + list($headers, $message_body) = explode("\n\n", $message, 2); + + $headers = explode("\n", $headers); + foreach ($headers as $header) { + $header = explode(':', $header, 2); + $message_headers[ trim($header[0]) ] = trim($header[1]); + } + + return Array ($message_headers, $message_body); + } + + /** + * Raised when email message shoul be sent + * + * @param kEvent $event + */ + function OnEmailEvent(&$event) + { + $email_event_name = $event->getEventParam('EmailEventName'); + if (strpos($email_event_name, '_') !== false) { + trigger_error('Invalid email event name '.$email_event_name.'. Use only UPPERCASE characters and dots as email event names', E_USER_ERROR); + } - - $email_object->setFrom($from_user_email, $from_user_name); - $email_object->setTo($to_user_email, $to_user_name); - $email_object->setSubject('Mail message'); - - $email_object->setHeaders($message_headers); - - if ($message_type == 'html'){ - $email_object->setHTMLBody($message_body); + // additional parameters from kApplication->EmailEvent + $send_params = $event->getEventParam('DirectSendParams'); + + // 1. get information about message sender and recipient + $recipients = $this->GetMessageRecipients($event, $event_id); + if ($recipients === false) { + // if not valid recipients found, then don't send event + return false; } - else { - $email_object->setTextBody($message_body); + + list ($from_id, $to_id) = $recipients; + list ($from_email, $from_name) = $this->GetRecipientInfo($from_id, 'from', $send_params); + list ($to_email, $to_name) = $this->GetRecipientInfo($to_id, 'to', $send_params); + + // 2. prepare message to be sent + $message_template = $this->GetMessageBody($event_id, $message_type); + if (!trim($message_template)) { + return false; } + + list ($message_headers, $message_body) = $this->ParseMessageBody($message_template, $send_params); + if (!trim($message_body)) { + return false; + } + + // 3. set headers & send message + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + $esender->SetFrom($from_email, $from_name); + $esender->AddTo($to_email, $to_name); + + $message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message'; + $esender->SetSubject($message_subject); - $smtp_object = &$this->Application->recallObject('kSmtpClient'); - $smtp_object->debug = $this->Application->isDebugMode() && constOn('DBG_SMTP'); - - $smtp_server = $this->Application->ConfigValue('Smtp_Server'); - $smtp_port = $this->Application->ConfigValue('Smtp_Port'); - - $smtp_authenticate = $this->Application->ConfigValue('Smtp_Authenticate'); - if ($smtp_authenticate){ - $smtp_user = $this->Application->ConfigValue('Smtp_User'); - $smtp_pass = $this->Application->ConfigValue('Smtp_Pass'); - }else{ - $smtp_user = ''; - $smtp_pass = ''; + foreach ($message_headers as $header_name => $header_value) { + $esender->SetEncodedHeader($header_name, $header_value); } + $esender->CreateTextHtmlPart($message_body, $message_type == 'html'); - if ($smtp_server){ - if ($email_object->sendSMTP($smtp_object, $smtp_server, $smtp_user, $smtp_pass, $smtp_authenticate)){ - $event->status=erSUCCESS; - } - else { - $event->status=erFAIL; - } - }else{ - if($email_object->send()){ - $event->status=erSUCCESS; - } - else { - $event->status=erFAIL; - } - } + $event->status = $esender->Deliver() ? erSUCCESS : erFAIL; if ($event->status == erSUCCESS){ - if (!$from_user_name) { - $from_user_name = strip_tags( $this->Application->ConfigValue('Site_Name') ); + // all keys, that are not used in email sending are written to log record + $send_keys = Array ('from_email', 'from_name', 'to_email', 'to_name', 'message'); + foreach ($send_keys as $send_key) { + unset($send_params[$send_key]); } - $sql = 'INSERT INTO '.TABLE_PREFIX.'EmailLog SET - fromuser = '.$this->Conn->qstr($from_user_name.' ('.$from_user_email.')').', - addressto = '.$this->Conn->qstr($to_user_name.' ('.$to_user_email.')').', - subject = '.$this->Conn->qstr($email_object->Subject).', - timestamp = UNIX_TIMESTAMP(), - event = '.$this->Conn->qstr($email_event); - $this->Conn->Query($sql); + + $fields_hash = Array ( + 'fromuser' => $from_name.' ('.$from_email.')', + 'addressto' => $to_name.' ('.$to_email.')', + 'subject' => $message_subject, + 'timestamp' => adodb_mktime(), + 'event' => $email_event_name, + 'EventParams' => serialize($send_params), + ); + + $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'EmailLog'); } $this->Application->removeObject('u.email-from'); $this->Application->removeObject('u.email-to'); - return $event; } } ?> \ No newline at end of file Index: branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/email_send.php =================================================================== diff -u --- branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/email_send.php (revision 0) +++ branches/unlabeled/unlabeled-1.1.4/core/kernel/utility/email_send.php (revision 7748) @@ -0,0 +1,1939 @@ +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('Socket'); + } + + $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 = 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 + * + * @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.'>, '; + } + $value = preg_replace('/(.*),$/', '\\1', $value); + + $this->SetHeader($header, $value); + } + + + /** + * Adds new part to message and returns it's number + * + * @param Array $part_definition + * @param int $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; + } + + /** + * Returns text version of HTML document + * + * @param string $html + * @return string + */ + function ConvertToText($html) + { + $search = Array ( + "'(<\/td>.*)[\r\n]+(.*[\r\n]{0,2})|(<\/p>)|(<\/div>)|(<\/tr>)'i", + "'(.*?)'si", + "''si", + "'(.*?)'si", + "''si", +// "'^[\s\n\r\t]+'", //strip all spacers & newlines in the begin of document +// "'[\s\n\r\t]+$'", //strip all spacers & newlines in the end of document + "'&(quot|#34);'i", + "'&(amp|#38);'i", + "'&(lt|#60);'i", + "'&(gt|#62);'i", + "'&(nbsp|#160);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&#(\d+);'e" + ); + + $replace = Array ( + "\\1\t\\2", + "\n", + "", + "", + "", + "", +// "", +// "", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + 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 $encode encode message using quoted-printable encoding + * + * @return int number of created part + */ + function CreateTextHtmlPart($text, $is_html = false, $encode = true) + { + if ($is_html) { + // 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 + * + * @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_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) + { + $definition = Array ( + '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 = '') + { + $part_number = $this->AddAttachment($file, $attach_name, 'message/rfc822', $content, true); + 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 + * + * @param Array $part_numbers + * @param string $multipart_type = {alternative,related,mixed,paralell} + * @return int + */ + function CreateMultipart($part_numbers, $multipart_type) + { + $types = Array ('alternative', 'related' , 'mixed', 'paralell'); + 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 + * + * @param int $part_number + */ + function CreateContentID($part_number) + { + $part =& $this->parts[$part_number]; + if (!isset($part['Content-ID']) && $part['DISPOSITION'] == 'inline') { + $part['Content-ID'] = md5(uniqid($part_number.time())).'.'.$this->GetFilenameExtension($part['NAME']); + } + } + + /** + * Returns attachment part based on file used in attachment + * + * @param Array $file + * @return Array + */ + function &GetFileDefinition ($file) + { + $name = ''; + if (isset($file['Name'])) { + // if name is given directly, then use it + $name = $file['Name']; + } + else { + // auto-guess attachment name based on source filename + $name = isset($file['FileName']) ? basename($file['FileName']) : ''; + } + + if (!$name || (!isset($file['FileName']) && !isset($file['Data']))) { + // 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)); + } + } + else { + // 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') { + // valid disposition header value + $definition['DISPOSITION'] = $file['Disposition']; + } + else { + 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 + * this unfinished regular expression still gives correct result in any case. + */ + 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) + (ppd,psd:application/x-photoshop)(swf,swc,rf:application/x-shockwave-flash)(tar:application/x-tar)(zip:application/zip) + (mid,midi,kar:audio/midi)(mp2,mp3,mpga:audio/mpeg)(ra:audio/x-realaudio)(wav:audio/wav)(bmp:image/bitmap)(bmp:image/bitmap) + (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'; + } + $content_type = $regs[1]; + } + else { + $content_type = 'application/octet-stream'; + } + } + + /** + * Using guess options combines all added parts together and returns combined part number + * + * @return int + */ + function PrepareMessageBody() + { + 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 + $parts = Array ( + $this->guessOptions['text_part'], + $this->guessOptions['html_part'], + ); + $part_number = $this->CreateMultipart($parts, 'alternative'); + } + elseif ($this->guessOptions['text_part'] !== false) { + // 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) + * + * @param Array $message_headers + * @param string $message_body + * @return bool + */ + function GetHeadersAndBody(&$message_headers, &$message_body) + { + $part_number = $this->PrepareMessageBody(); + 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) { + // set missing headers from existing + $from_headers = Array ('Reply-To', 'Errors-To'); + foreach ($from_headers as $header_name) { + if (!isset($message_headers[$header_name])) { + $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 + * + * @param Array $message_headers + * @return bool + */ + function ValidateHeaders($message_headers) + { + $from = isset($message_headers['From']) ? $message_headers['From'] : ''; + 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 + * + * @return string + */ + function GetMessage() + { + $composed = $this->GetHeadersAndBody($message_headers, $message_body); + if ($composed) { + // add headers to resulting message + $message = ''; + 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 + * + * @param string $code + * @param Array $params additional error params + * @return bool + */ + function SetError($code, $params = null, $fatal = true) + { + $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 + * + * @param string $from_email + * @param string $to_email + * @param string $subject + * @param string $from_name + * @param string $to_name + */ + function Send($from_email, $to_email, $subject, $from_name = '', $to_name = '') + { + $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 + * + */ + function Clear() + { + $this->headers = Array (); + $this->bodyPartNumber = false; + $this->parts = Array(); + $this->guessOptions = Array (); + + $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 + * be appended to each command string before it is sent to the + * SMTP server. + * + * @param string $command The SMTP command to send to the server. + * @param string $args A string of optional arguments to append to the command. + * + * @return bool + * + */ + function SmtpSendCommand($command, $args = '') + { + 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. + * + * @param mixed $valid The set of valid response codes. These may be specified as an array of integer values or as a single integer value. + * + * @return bool + * + */ + function SmtpParseResponse($valid) + { + $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) { + if ($this->smtpResponceCode === $valid_code) { + return true; + } + } + } + + 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. + * + * @return bool + */ + function SmtpDisconnect() + { + if ($this->SmtpSendCommand('QUIT') === false) { + return false; + } + 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. + * + * @return bool + */ + function SmtpNegotiate() + { + 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. + * + * @param string The userid to authenticate as. + * @param string The password to authenticate with. + * @param string The requested authentication method. If none is specified, the best supported method will be used. + * + * @return bool + */ + function SmtpAuthentificate($uid, $pwd , $method = '') + { + if (empty($this->smtpFeatures['AUTH'])) { + // 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 + if (in_array($supported_method, $available_methods)) { + $method = $supported_method; + break; + } + } + } 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; + case 'PLAIN': + $result = $this->_authPlain($uid, $pwd); + break; + default: + return $this->SetError('AUTH_METHOD_NOT_IMPLEMENTED', Array($method)); + break; + } + + return $result; + } + + /** + * Function which implements HMAC MD5 digest + * + * @param string $key The secret key + * @param string $data The data to protect + * @return string The HMAC MD5 digest + */ + function _HMAC_MD5($key, $data) + { + 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. + * + * @param string The userid to authenticate as. + * @param string The password to authenticate with. + * + * @return bool + */ + function _authCRAM_MD5($uid, $pwd) + { + 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. + * + * @param string The userid to authenticate as. + * @param string The password to authenticate with. + * + * @return bool + */ + function _authLogin($uid, $pwd) + { + 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. + * + * @param string The userid to authenticate as. + * @param string The password to authenticate with. + * + * @return bool + */ + function _authPlain($uid, $pwd) + { + 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. + * + * @param string $sender The sender (reverse path) to set. + * @param string $params String containing additional MAIL parameters, such as the NOTIFY flags defined by RFC 1891 or the VERP protocol. + * + * @return bool + */ + function SmtpSetFrom($sender, $params = null) + { + $args = "FROM:<$sender>"; + 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. + * + * @param string $recipient The recipient (forward path) to add. + * @param string $params String containing additional RCPT parameters, such as the NOTIFY flags defined by RFC 1891. + * + * @return bool + */ + function SmtpAddTo($recipient, $params = null) + { + $args = "TO:<$recipient>"; + 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. + * + * @param string $data The message body to send. + * + * @return bool + */ + function SmtpSendMessage($data) + { + /* RFC 1870, section 3, subsection 3 states "a value of zero + * indicates that no fixed maximum message size is in force". + * Furthermore, it says that if "the parameter is omitted no + * information is conveyed about the server's fixed maximum + * message size". */ + if (isset($this->smtpFeatures['SIZE']) && ($this->smtpFeatures['SIZE'] > 0)) { + if (strlen($data) >= $this->smtpFeatures['SIZE']) { + $this->SmtpDisconnect(); + 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 + * + * @param string $charset + * @param bool set charset to default for current langauge + */ + function SetCharset($charset, $is_system = false) + { + 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 + * + * @param string $text + * @param string $email + * @return string + */ + function ExtractRecipientName($text, $email = '') + { + $lastspace = strrpos($text, ' '); + $name = trim(substr($text, 0, $lastspace - strlen($text)), " \r\n\t\0\x0b\"'"); + if (empty($name)) { + $name = $email; + } + return $name; + } + + /** + * Takes $text and returns an email address from it + * Set $multiple to true to retrieve all found addresses + * Returns false if no addresses were found + * + * @param string $text + * @param bool $multiple + * @param bool $allowOnlyDomain + * @return string + */ + function ExtractRecipientEmail($text, $multiple = false, $allowOnlyDomain = false) { + if ($allowOnlyDomain) { + $pattern = '/(('.REGEX_EMAIL_USER.'@)?'.REGEX_EMAIL_DOMAIN.')/i'; + } else { + $pattern = '/('.REGEX_EMAIL_USER.'@'.REGEX_EMAIL_DOMAIN.')/i'; + } + if ($multiple) { + if (preg_match_all($pattern, $text, $findemail) >= 1) { + return $findemail[1]; + } else { + return false; + } + } else { + if (preg_match($pattern, $text, $findemail) == 1) { + return $findemail[1]; + } else { + return false; + } + } + } + + /** + * Returns array of recipient names and emails + * + * @param string $list + * @param string $separator + * @return Array + */ + function GetRecipients($list, $separator = ';') + { + // 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); + if (!$email) { + // invalid email format -> error + return false; + } + $name = $this->ExtractRecipientName($recipient, $email); + $ret[] = Array('Name' => $name, 'Email' => $email); + } + + return $ret; + } + + /* methods for nice header setting */ + + /** + * Sets "From" header. + * + * @param string $email + * @param string $first_last_name FirstName and LastName or just FirstName + * @param string $last_name LastName (if not specified in previous parameter) + */ + function SetFrom($email, $first_last_name, $last_name = '') + { + $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) + * + * @param string $email + */ + function SetReturnPath($email) + { + $this->SetHeader('Return-Path', $email); + } + + /** + * Adds one more recipient into "To" header + * + * @param string $email + * @param string $first_last_name FirstName and LastName or just FirstName + * @param string $last_name LastName (if not specified in previous parameter) + */ + function AddTo($email, $first_last_name = '', $last_name = '') + { + $name = rtrim($first_last_name.' '.$last_name, ' '); + $this->AddRecipient('To', $email, $name); + } + + /** + * Adds one more recipient into "Cc" header + * + * @param string $email + * @param string $first_last_name FirstName and LastName or just FirstName + * @param string $last_name LastName (if not specified in previous parameter) + */ + function AddCc($email, $first_last_name = '', $last_name = '') + { + $name = rtrim($first_last_name.' '.$last_name, ' '); + $this->AddRecipient('Cc', $email, $name); + } + + /** + * Adds one more recipient into "Bcc" header + * + * @param string $email + * @param string $first_last_name FirstName and LastName or just FirstName + * @param string $last_name LastName (if not specified in previous parameter) + */ + function AddBcc($email, $first_last_name = '', $last_name = '') + { + $name = rtrim($first_last_name.' '.$last_name, ' '); + $this->AddRecipient('Bcc', $email, $name); + } + + /** + * Adds one more recipient to specified header + * + * @param string $header_name + * @param string $email + * @param string $name + */ + function AddRecipient($header_name, $email, $name = '') + { + 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. + * + * @param string $subject message subject + */ + function SetSubject($subject) + { + $this->setEncodedHeader('Subject', $subject); + } + + /** + * Sets HTML part of message + * + * @param string $html + */ + function SetHTML($html) + { + $this->CreateTextHtmlPart($html, true); + } + + /** + * Sets Plain-Text part of message + * + * @param string $plain_text + */ + function SetPlain($plain_text) + { + $this->CreateTextHtmlPart($plain_text); + } + + /** + * Sets HTML and optionally plain part of the message + * + * @param string $html + * @param string $plain_text + */ + function SetBody($html, $plain_text = '') + { + $this->SetHTML($html); + if ($plain_text) { + $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 + * + */ + function Deliver($message = null, $immediate_send = true, $immediate_clear = true) + { + if (isset($message)) { + // if message is given directly, then use it + $message_headers = Array (); + list ($headers, $message_body) = explode("\n\n", $message, 2); + $headers = explode("\n", $headers); + foreach ($headers as $header) { + $header = explode(':', $header, 2); + $message_headers[ trim($header[0]) ] = trim($header[1]); + } + $composed = true; + } else { + // direct message not given, then assemble message from available parts + $composed = $this->GetHeadersAndBody($message_headers, $message_body); + } + + if ($composed && $immediate_send) { + $send_method = 'Send'.$this->sendMethod; + $result = $this->$send_method($message_headers, $message_body); + + if ($immediate_clear) { + $this->Clear(); + } + return $result; + } + + // if not immediate send, then send result is positive :) + return !$immediate_send ? true : false; + } + } + +?> \ No newline at end of file Index: branches/unlabeled/unlabeled-1.170.2/core/kernel/application.php =================================================================== diff -u -r7725 -r7748 --- branches/unlabeled/unlabeled-1.170.2/core/kernel/application.php (.../application.php) (revision 7725) +++ branches/unlabeled/unlabeled-1.170.2/core/kernel/application.php (.../application.php) (revision 7748) @@ -504,8 +504,8 @@ $this->registerClass('Template', KERNEL_PATH.'/parser/template.php'); $this->registerClass('TemplateParser', KERNEL_PATH.'/parser/template_parser.php',null, 'kDBTagProcessor'); - $this->registerClass('kEmailMessage', KERNEL_PATH.'/utility/email.php'); - $this->registerClass('kSmtpClient', KERNEL_PATH.'/utility/smtp_client.php'); + $this->registerClass('kEmailSendingHelper', KERNEL_PATH.'/utility/email_send.php', 'EmailSender', Array('kHelper')); + $this->registerClass('kSocket', KERNEL_PATH.'/utility/socket.php', 'Socket'); if (file_exists(MODULES_PATH.'/in-commerce/units/currencies/currency_rates.php')) { $this->registerClass('kCurrencyRates', MODULES_PATH.'/in-commerce/units/currencies/currency_rates.php');