Index: branches/5.2.x/core/units/email_events/email_events_event_handler.php =================================================================== diff -u -N -r15145 -r15215 --- branches/5.2.x/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 15145) +++ branches/5.2.x/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 15215) @@ -1,6 +1,6 @@ getEventParam('EmailEventName'); - $type = $event->getEventParam('EmailEventType'); - - $object = $event->getObject( Array('skip_autoload' => true) ); - /* @var $object kDBItem */ - - if (!$object->isLoaded() || ($object->GetDBField('Event') != $name || $object->GetDBField('Type') != $type)) { - // get event parameters by name & type - $load_keys = Array ('Event' => $name, 'Type' => $type); - $object->Load($load_keys); - - if (!$object->isLoaded() || ($object->GetDBField('Enabled') == STATUS_DISABLED)) { - // event record not found OR is disabled - return $false; - } - - if ($object->GetDBField('FrontEndOnly') && $this->Application->isAdmin) { - return $false; - } - } - - return $object; - } - - /** - * Processes email sender - * - * @param kEvent $event - * @param Array $direct_params - */ - function _processSender($event, $direct_params = Array ()) - { - $this->Application->removeObject('u.email-from'); - - $object =& $this->_getEmailEvent($event); - /* @var $object kDBItem */ - - $email = $name = ''; - - // set defaults from event - if ($object->GetDBField('CustomSender')) { - $address = $object->GetDBField('SenderAddress'); - $address_type = $object->GetDBField('SenderAddressType'); - - switch ($address_type) { - case EmailEvent::ADDRESS_TYPE_EMAIL: - $email = $address; - break; - - case EmailEvent::ADDRESS_TYPE_USER: - $sql = 'SELECT FirstName, LastName, Email, PortalUserId - FROM ' . TABLE_PREFIX . 'Users - WHERE Username = ' . $this->Conn->qstr($address); - $user_info = $this->Conn->GetRow($sql); - - if ($user_info) { - // user still exists - $email = $user_info['Email']; - $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); - - $user = $this->Application->recallObject('u.email-from', null, Array('skip_autoload' => true)); - /* @var $user UsersItem */ - - $user->Load($user_info['PortalUserId']); - } - break; - } - - if ($object->GetDBField('SenderName')) { - $name = $object->GetDBField('SenderName'); - } - } - - // update with custom data given during event execution - if (array_key_exists('from_email', $direct_params)) { - $email = $direct_params['from_email']; - } - - if (array_key_exists('from_name', $direct_params)) { - $name = $direct_params['from_name']; - } - - // still nothing, set defaults - if (!$email) { - $email = $this->Application->ConfigValue('DefaultEmailSender'); - } - - if (!$name) { - $name = strip_tags( $this->Application->ConfigValue('Site_Name') ); - } - - $esender = $this->Application->recallObject('EmailSender'); - /* @var $esender kEmailSendingHelper */ - - $esender->SetFrom($email, $name); - - return Array ($email, $name); - } - - /** - * Processes email recipients - * - * @param kEvent $event - * @param Array $direct_params - * @return Array - */ - function _processRecipients($event, &$direct_params = Array ()) - { - $this->Application->removeObject('u.email-to'); - - $object =& $this->_getEmailEvent($event); - /* @var $object kDBItem */ - - $to_email = $to_name = ''; - $all_recipients = Array (); - - $this->_addRecipientsFromXml($all_recipients, $object->GetDBField('Recipients')); - - if ( !array_key_exists(EmailEvent::RECIPIENT_TYPE_TO, $all_recipients) ) { - $all_recipients[EmailEvent::RECIPIENT_TYPE_TO] = Array (); - } - - // remove all "To" recipients, when not allowed - $overwrite_to_email = array_key_exists('overwrite_to_email', $direct_params) ? $direct_params['overwrite_to_email'] : false; - - if ( !$object->GetDBField('CustomRecipient') || $overwrite_to_email ) { - $all_recipients[EmailEvent::RECIPIENT_TYPE_TO] = Array (); - } - - // update with custom data given during event execution (user_id) - $to_user_id = $event->getEventParam('EmailEventToUserId'); - - if ( $to_user_id > 0 ) { - $language_field = $event->getEventParam('EmailEventType') == EmailEvent::EVENT_TYPE_FRONTEND ? 'FrontLanguage' : 'AdminLanguage'; - - $sql = 'SELECT FirstName, LastName, Email, ' . $language_field . ' AS Language - FROM ' . TABLE_PREFIX . 'Users - WHERE PortalUserId = ' . $to_user_id; - $user_info = $this->Conn->GetRow($sql); - - if ( $user_info ) { - $add_recipient = Array ( - 'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL, - 'RecipientAddress' => $user_info['Email'], - 'RecipientName' => trim($user_info['FirstName'] . ' ' . $user_info['LastName']), - ); - - if ( $user_info['Language'] && !isset($direct_params['language_id']) ) { - $direct_params['language_id'] = $user_info['Language']; - } - - array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient); - - $user = $this->Application->recallObject('u.email-to', null, Array('skip_autoload' => true)); - /* @var $user UsersItem */ - - $user->Load($to_user_id); - } - } - elseif ( is_numeric($to_user_id) ) { - // recipient is system user with negative ID (root, guest, etc.) -> send to admin - $this->_addDefaultRecipient($all_recipients); - } - - // update with custom data given during event execution (email + name) - $add_recipient = Array (); - - if ( array_key_exists('to_email', $direct_params) ) { - $add_recipient['RecipientName'] = ''; - $add_recipient['RecipientAddressType'] = EmailEvent::ADDRESS_TYPE_EMAIL; - $add_recipient['RecipientAddress'] = $direct_params['to_email']; - } - - if ( array_key_exists('to_name', $direct_params) ) { - $add_recipient['RecipientName'] = $direct_params['to_name']; - } - - if ( $add_recipient ) { - array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient); - } - - if ( ($object->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN) && !$all_recipients[EmailEvent::RECIPIENT_TYPE_TO] ) { - // admin email event without direct recipient -> send to admin - $this->_addDefaultRecipient($all_recipients); - } - - $esender = $this->Application->recallObject('EmailSender'); - /* @var $esender kEmailSendingHelper */ - - $header_mapping = Array ( - EmailEvent::RECIPIENT_TYPE_TO => 'To', - EmailEvent::RECIPIENT_TYPE_CC => 'Cc', - EmailEvent::RECIPIENT_TYPE_BCC => 'Bcc', - ); - - $default_email = $this->Application->ConfigValue('DefaultEmailSender'); - - foreach ($all_recipients as $recipient_type => $recipients) { - // add recipients to email - $pairs = Array (); - - foreach ($recipients as $recipient) { - $address = $recipient['RecipientAddress']; - $address_type = $recipient['RecipientAddressType']; - $recipient_name = $recipient['RecipientName']; - - switch ($address_type) { - case EmailEvent::ADDRESS_TYPE_EMAIL: - $pairs[] = Array ('email' => $address, 'name' => $recipient_name); - break; - - case EmailEvent::ADDRESS_TYPE_USER: - $sql = 'SELECT FirstName, LastName, Email - FROM ' . TABLE_PREFIX . 'Users - WHERE Username = ' . $this->Conn->qstr($address); - $user_info = $this->Conn->GetRow($sql); - - if ($user_info) { - // user still exists - $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); - - $pairs[] = Array ( - 'email' => $user_info['Email'], - 'name' => $name ? $name : $recipient_name, - ); - } - break; - - case EmailEvent::ADDRESS_TYPE_GROUP: - $sql = 'SELECT u.FirstName, u.LastName, u.Email - FROM ' . TABLE_PREFIX . 'UserGroups g - JOIN ' . TABLE_PREFIX . 'UserGroupRelations ug ON ug.GroupId = g.GroupId - JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = ug.PortalUserId - WHERE g.Name = ' . $this->Conn->qstr($address); - $users = $this->Conn->Query($sql); - - foreach ($users as $user_info) { - $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); - - $pairs[] = Array ( - 'email' => $user_info['Email'], - 'name' => $name ? $name : $recipient_name, - ); - } - break; - } - } - - if ( !$pairs ) { - continue; - } - - if ( $recipient_type == EmailEvent::RECIPIENT_TYPE_TO ) { - $to_email = $pairs[0]['email'] ? $pairs[0]['email'] : $default_email; - $to_name = $pairs[0]['name'] ? $pairs[0]['name'] : $to_email; - } - - $header_name = $header_mapping[$recipient_type]; - - foreach ($pairs as $pair) { - $email = $pair['email'] ? $pair['email'] : $default_email; - $name = $pair['name'] ? $pair['name'] : $email; - - $esender->AddRecipient($header_name, $email, $name); - } - } - - return Array ($to_email, $to_name); - } - - /** - * This is default recipient, when we can't determine actual one - * - * @param Array $recipients - * @return void - */ - function _addDefaultRecipient(&$recipients) - { - $xml = $this->Application->ConfigValue('DefaultEmailRecipients'); - - if ( !$this->_addRecipientsFromXml($recipients, $xml) ) { - $recipient = Array ( - 'RecipientName' => $this->Application->ConfigValue('DefaultEmailSender'), - 'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL, - 'RecipientAddress' => $this->Application->ConfigValue('DefaultEmailSender'), - ); - - array_unshift($recipients[EmailEvent::RECIPIENT_TYPE_TO], $recipient); - } - } - - /** - * Adds multiple recipients from an XML - * - * @param Array $recipients - * @param string $xml - * @return bool - * @access protected - */ - protected function _addRecipientsFromXml(&$recipients, $xml) - { - if ( !$xml ) { - return false; - } - - $minput_helper = $this->Application->recallObject('MInputHelper'); - /* @var $minput_helper MInputHelper */ - - // group recipients by type - $records = $minput_helper->parseMInputXML($xml); - - foreach ($records as $record) { - $recipient_type = $record['RecipientType']; - - if ( !array_key_exists($recipient_type, $recipients) ) { - $recipients[$recipient_type] = Array (); - } - - $recipients[$recipient_type][] = $record; - } - - return true; - } - - /** - * Returns email event message by ID (headers & body in one piece) - * - * @param kEvent $event - * @param int $language_id - * @return string - */ - function _getMessageBody($event, $language_id = null) - { - if (!isset($language_id)) { - $language_id = $this->Application->GetVar('m_lang'); - } - - $object =& $this->_getEmailEvent($event); - - // 1. get message body - $message_body = $this->_formMessageBody($object, $language_id, $object->GetDBField('MessageType')); - - // 2. replace tags if needed - $default_replacement_tags = Array ( - ' ' ' 'GetDBField('ReplacementTags'); - $replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array (); - $replacement_tags = array_merge($default_replacement_tags, $replacement_tags); - - foreach ($replacement_tags as $replace_from => $replace_to) { - $message_body = str_replace($replace_from, $replace_to, $message_body); - } - - return $message_body; - } - - /** - * Prepare email message body - * - * @param kDBItem $object - * @param int $language_id - * @return string - */ - function _formMessageBody(&$object, $language_id) - { - $default_language_id = $this->Application->GetDefaultLanguageId(); - - $fields_hash = Array ( - 'Headers' => $object->GetDBField('Headers'), - ); - - // prepare subject - $subject = $object->GetDBField('l' . $language_id . '_Subject'); - - if (!$subject) { - $subject = $object->GetDBField('l' . $default_language_id . '_Subject'); - } - - $fields_hash['Subject'] = $subject; - - // prepare body - $body = $object->GetDBField('l' . $language_id . '_Body'); - - if (!$body) { - $body = $object->GetDBField('l' . $default_language_id . '_Body'); - } - - $fields_hash['Body'] = $body; - - $email_message_helper = $this->Application->recallObject('EmailMessageHelper'); - /* @var $email_message_helper EmailMessageHelper */ - - $ret = $email_message_helper->buildTemplate($fields_hash); - - // add footer - $footer = $this->_getFooter($language_id, $object->GetDBField('MessageType')); - - if ($ret && $footer) { - $ret .= "\r\n" . $footer; - } - - return $ret; - } - - /** - * Returns email footer - * - * @param int $language_id - * @param string $message_type - * @return string - */ - function _getFooter($language_id, $message_type) - { - static $footer = null; - - if (!isset($footer)) { - $default_language_id = $this->Application->GetDefaultLanguageId(); - - $sql = 'SELECT l' . $language_id . '_Body, l' . $default_language_id . '_Body - FROM ' . $this->Application->getUnitOption('emailevents', 'TableName') . ' em - WHERE Event = "COMMON.FOOTER"'; - $footer_data = $this->Conn->GetRow($sql); - - $footer = $footer_data['l' . $language_id . '_Body']; - - if (!$footer) { - $footer = $footer_data['l' . $default_language_id . '_Body']; - } - - if ($message_type == 'text') { - $esender = $this->Application->recallObject('EmailSender'); - /* @var $esender kEmailSendingHelper */ - - $footer = $esender->ConvertToText($footer); - } - } - - return $footer; - } - - /** - * 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 = Array ()) - { - $message_language = $this->_getSendLanguage($direct_params); - $this->_changeLanguage($message_language); - - $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; // backup parser params - - $this->Application->Parser->SetParams( array_merge($parser_params, $direct_params) ); - - $message = implode('&|&', explode("\n\n", $message, 2)); // preserves double \n in case when tag is located in subject field - $message = $this->Application->Parser->Parse($message, 'email_template', 0); - - $this->Application->Parser->SetParams($parser_params); // restore parser params - - // 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 - - // 3. separate headers from body - $message_headers = Array (); - list($headers, $message_body) = explode('&|&', $message, 2); - - $category_helper = $this->Application->recallObject('CategoryHelper'); - /* @var $category_helper CategoryHelper */ - - $message_body = $category_helper->replacePageIds($message_body); - - $headers = explode("\n", $headers); - foreach ($headers as $header) { - $header = explode(':', $header, 2); - $message_headers[ trim($header[0]) ] = trim($header[1]); - } - - $this->_changeLanguage(); - return Array ($message_headers, $message_body); - } - - /** - * Raised when email message should be sent - * - * @param kEvent $event - * @return void - * @access protected - */ - protected function OnEmailEvent($event) - { - $email_event_name = $event->getEventParam('EmailEventName'); - if ( strpos($email_event_name, '_') !== false ) { - throw new Exception('Invalid email event name ' . $email_event_name . '. Use only UPPERCASE characters and dots as email event names'); - } - - $object =& $this->_getEmailEvent($event); - - if ( !is_object($object) ) { - // email event not found OR it's won't be send under given circumstances - return ; - } - - // additional parameters from kApplication->EmailEvent - $send_params = $event->getEventParam('DirectSendParams'); - - // 1. get information about message sender and recipient - list ($from_email, $from_name) = $this->_processSender($event, $send_params); - list ($to_email, $to_name) = $this->_processRecipients($event, $send_params); - - // 2. prepare message to be sent - $message_language = $this->_getSendLanguage($send_params); - $message_template = $this->_getMessageBody($event, $message_language); - if ( !trim($message_template) ) { - trigger_error('Message template is empty', E_USER_WARNING); - return ; - } - - list ($message_headers, $message_body) = $this->ParseMessageBody($message_template, $send_params); - if ( !trim($message_body) ) { - trigger_error('Message template is empty after parsing', E_USER_WARNING); - return ; - } - - // 3. set headers & send message - $esender = $this->Application->recallObject('EmailSender'); - /* @var $esender kEmailSendingHelper */ - - $message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message'; - $esender->SetSubject($message_subject); - - if ( $this->Application->isDebugMode() ) { - // set special header with event name, so it will be easier to determine what's actually was received - $message_headers['X-Event-Name'] = $email_event_name . ' - ' . ($object->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN ? 'ADMIN' : 'USER'); - } - - foreach ($message_headers as $header_name => $header_value) { - $esender->SetEncodedHeader($header_name, $header_value); - } - - $esender->CreateTextHtmlPart($message_body, $object->GetDBField('MessageType') == 'html'); - - $log_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( $this->removeSendingParams($send_params) ), - ); - - $esender->setLogData($log_fields_hash); - $event->status = $esender->Deliver() ? kEvent::erSUCCESS : kEvent::erFAIL; - } - - /** - * Removes parameters, used during e-mail sending - * - * @param Array $params - * @return Array - * @access protected - */ - protected function removeSendingParams($params) - { - $send_keys = Array ('from_email', 'from_name', 'to_email', 'to_name', 'message'); - - foreach ($send_keys as $send_key) { - unset($params[$send_key]); - } - - return $params; - } - - function _getSendLanguage($send_params) - { - if (array_key_exists('language_id', $send_params)) { - return $send_params['language_id']; - } - - return $this->Application->GetVar('m_lang'); - } - - function _changeLanguage($language_id = null) - { - static $prev_language_id = null; - - if ( !isset($language_id) ) { - // restore language - $language_id = $prev_language_id; - } - - $this->Application->SetVar('m_lang', $language_id); - - $language = $this->Application->recallObject('lang.current'); - /* @var $language LanguagesItem */ - - $language->Load($language_id); - - $this->Application->Phrases->LanguageId = $language_id; - $this->Application->Phrases->Phrases = Array (); - - $prev_language_id = $language_id; // for restoring it later - } - - /** * Process emails from queue * * @param kEvent $event Index: branches/5.2.x/core/kernel/utility/email.php =================================================================== diff -u -N --- branches/5.2.x/core/kernel/utility/email.php (revision 0) +++ branches/5.2.x/core/kernel/utility/email.php (revision 15215) @@ -0,0 +1,805 @@ + Array (), + EmailEvent::RECIPIENT_TYPE_CC => Array (), + EmailEvent::RECIPIENT_TYPE_BCC => Array (), + ); + + /** + * Creates e-mail instance + */ + public function __construct() + { + parent::__construct(); + + $this->sender = $this->Application->recallObject('EmailSender'); + } + + /** + * Resets state of e-mail + * + * @return void + * @access protected + */ + protected function _resetState() + { + $this->fromEmail = $this->fromName = ''; + $this->Application->removeObject('u.email-from'); + + $this->recipients = Array ( + EmailEvent::RECIPIENT_TYPE_TO => Array (), + EmailEvent::RECIPIENT_TYPE_CC => Array (), + EmailEvent::RECIPIENT_TYPE_BCC => Array (), + ); + + $this->toEmail = $this->toEmail = ''; + $this->Application->removeObject('u.email-to'); + } + + /** + * Finds e-mail event matching user data + * + * @param string $name + * @param int $type + * @return bool + * @throws InvalidArgumentException + * @access public + */ + public function findEvent($name, $type) + { + if ( !$name || !preg_match('/^[A-Z\.]+$/', $name) ) { + throw new InvalidArgumentException('Invalid e-mail event name "' . $name . '". Only UPPERCASE characters and dots are allowed.'); + } + + if ( $type != EmailEvent::EVENT_TYPE_ADMIN && $type != EmailEvent::EVENT_TYPE_FRONTEND ) { + throw new InvalidArgumentException('Invalid e-mail event type'); + } + + $this->emailEvent = $this->Application->recallObject('emailevents', null, Array ('skip_autoload' => true)); + + if ( !$this->emailEvent->isLoaded() || !$this->_sameEvent($name, $type) ) { + // get event parameters by name & type + $this->emailEvent->Load(Array ('Event' => $name, 'Type' => $type)); + } + + return $this->_eventUsable(); + } + + /** + * Detects, that given event data matches currently used event + * + * @param string $name + * @param int $type + * @return bool + * @access protected + */ + protected function _sameEvent($name, $type) + { + return $this->emailEvent->GetDBField('Event') == $name && $this->emailEvent->GetDBField('Type') == $type; + } + + /** + * Determines if we can use e-mail event we've found based on user data + * + * @return bool + * @access protected + */ + protected function _eventUsable() + { + if ( !$this->emailEvent->isLoaded() || $this->emailEvent->GetDBField('Enabled') == STATUS_DISABLED ) { + return false; + } + + if ( $this->emailEvent->GetDBField('FrontEndOnly') && $this->Application->isAdmin ) { + return false; + } + + return true; + } + + /** + * Sets e-mail event params + * + * @param Array $params + * @access public + */ + public function setParams($params) + { + $this->params = $params; + } + + /** + * Returns any custom parameters, that are passed when invoked e-mail event sending + * + * @return Array + * @access protected + */ + protected function _getCustomParams() + { + $ret = $this->params; + $send_keys = Array ('from_email', 'from_name', 'to_email', 'to_name', 'overwrite_to_email', 'language_id'); + + foreach ($send_keys as $send_key) { + unset($ret[$send_key]); + } + + return $ret; + } + + /** + * Sends e-mail now or puts it in queue + * + * @param int $recipient_user_id + * @param bool $immediate_send + * @return bool + * @access public + */ + public function send($recipient_user_id = null, $immediate_send = true) + { + $this->recipientUserId = $recipient_user_id; + + $this->_resetState(); + $this->_processSender(); + $this->_processRecipients(); + $this->_changeLanguage(false); + + // 1. set headers + $message_headers = $this->_getHeaders(); + $message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message'; + $this->sender->SetSubject($message_subject); + + foreach ($message_headers as $header_name => $header_value) { + $this->sender->SetEncodedHeader($header_name, $header_value); + } + + // 2. set body + $is_html = $this->emailEvent->GetDBField('MessageType') == 'html'; + $message_body = $this->_getMessageBody($is_html); + + if ( $message_body === false ) { + return false; + } + + $this->sender->CreateTextHtmlPart($message_body, $is_html); + $this->_changeLanguage(true); + + // 3. set log + $log_fields_hash = Array ( + 'fromuser' => $this->fromName . ' (' . $this->fromEmail . ')', + 'addressto' => $this->toName . ' (' . $this->toEmail . ')', + 'subject' => $message_subject, + 'timestamp' => TIMENOW, + 'event' => $this->emailEvent->GetDBField('Event'), + 'EventParams' => serialize($this->_getCustomParams()), + ); + + $this->sender->setLogData($log_fields_hash); + + return $this->sender->Deliver(null, $immediate_send); + } + + /** + * Processes email sender + * + * @return void + * @access protected + */ + protected function _processSender() + { + if ( $this->emailEvent->GetDBField('CustomSender') ) { + $this->_processCustomSender(); + } + + // update with custom data given during event execution + if ( isset($this->params['from_email']) ) { + $this->fromEmail = $this->params['from_email']; + } + + if ( isset($this->params['from_name']) ) { + $this->fromName = $this->params['from_name']; + } + + // still nothing, set defaults + $this->_ensureDefaultSender(); + $this->sender->SetFrom($this->fromEmail, $this->fromName); + } + + /** + * Processes custom e-mail sender + * + * @return void + * @access protected + */ + protected function _processCustomSender() + { + $address = $this->emailEvent->GetDBField('SenderAddress'); + $address_type = $this->emailEvent->GetDBField('SenderAddressType'); + + switch ($address_type) { + case EmailEvent::ADDRESS_TYPE_EMAIL: + $this->fromEmail = $address; + break; + + case EmailEvent::ADDRESS_TYPE_USER: + $sql = 'SELECT FirstName, LastName, Email, PortalUserId + FROM ' . TABLE_PREFIX . 'Users + WHERE Username = ' . $this->Conn->qstr($address); + $user_info = $this->Conn->GetRow($sql); + + if ( $user_info ) { + // user still exists + $this->fromEmail = $user_info['Email']; + $this->fromName = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); + + $user = $this->Application->recallObject('u.email-from', null, Array ('skip_autoload' => true)); + /* @var $user UsersItem */ + + $user->Load($user_info['PortalUserId']); + } + break; + } + + if ( $this->emailEvent->GetDBField('SenderName') ) { + $this->fromName = $this->emailEvent->GetDBField('SenderName'); + } + } + + /** + * Ensures, that sender name & e-mail are not empty + * + * @return void + * @access protected + */ + protected function _ensureDefaultSender() + { + if ( !$this->fromEmail ) { + $this->fromEmail = $this->Application->ConfigValue('DefaultEmailSender'); + } + + if ( !$this->fromName ) { + $this->fromName = strip_tags($this->Application->ConfigValue('Site_Name')); + } + } + + /** + * Processes email recipients + * + * @return void + * @access protected + */ + protected function _processRecipients() + { + $this->_collectRecipients(); + + $header_mapping = Array ( + EmailEvent::RECIPIENT_TYPE_TO => 'To', + EmailEvent::RECIPIENT_TYPE_CC => 'Cc', + EmailEvent::RECIPIENT_TYPE_BCC => 'Bcc', + ); + + $default_email = $this->Application->ConfigValue('DefaultEmailSender'); + + foreach ($this->recipients as $recipient_type => $recipients) { + // add recipients to email + $pairs = $this->_transformRecipientsIntoPairs($recipients); + + if ( !$pairs ) { + continue; + } + + if ( $recipient_type == EmailEvent::RECIPIENT_TYPE_TO ) { + $this->toEmail = $pairs[0]['email'] ? $pairs[0]['email'] : $default_email; + $this->toName = $pairs[0]['name'] ? $pairs[0]['name'] : $this->toEmail; + } + + $header_name = $header_mapping[$recipient_type]; + + foreach ($pairs as $pair) { + $email = $pair['email'] ? $pair['email'] : $default_email; + $name = $pair['name'] ? $pair['name'] : $email; + + $this->sender->AddRecipient($header_name, $email, $name); + } + } + } + + /** + * Collects e-mail recipients from various sources + * + * @return void + * @access protected + */ + protected function _collectRecipients() + { + $this->_addRecipientsFromXml($this->emailEvent->GetDBField('Recipients')); + $this->_overwriteToRecipient(); + $this->_addRecipientByUserId(); + $this->_addRecipientFromParams(); + + if ( ($this->emailEvent->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN) && !$this->recipients[EmailEvent::RECIPIENT_TYPE_TO] ) { + // admin email event without direct recipient -> send to admin + $this->_addDefaultRecipient(); + } + } + + /** + * Adds multiple recipients from an XML + * + * @param string $xml + * @return bool + * @access protected + */ + protected function _addRecipientsFromXml($xml) + { + if ( !$xml ) { + return false; + } + + $minput_helper = $this->Application->recallObject('MInputHelper'); + /* @var $minput_helper MInputHelper */ + + // group recipients by type + $records = $minput_helper->parseMInputXML($xml); + + foreach ($records as $record) { + $this->recipients[$record['RecipientType']][] = $record; + } + + return true; + } + + /** + * Remove all "To" recipients, when not allowed + * + * @return void + * @access protected + */ + protected function _overwriteToRecipient() + { + $overwrite_to_email = isset($this->params['overwrite_to_email']) ? $this->params['overwrite_to_email'] : false; + + if ( !$this->emailEvent->GetDBField('CustomRecipient') || $overwrite_to_email ) { + $this->recipients[EmailEvent::RECIPIENT_TYPE_TO] = Array (); + } + } + + /** + * Update with custom data given during event execution (user_id) + * + * @return void + * @access protected + */ + protected function _addRecipientByUserId() + { + if ( !is_numeric($this->recipientUserId) ) { + return; + } + + if ( $this->recipientUserId <= 0 ) { + // recipient is system user with negative ID (root, guest, etc.) -> send to admin + $this->_addDefaultRecipient(); + + return; + } + + $language_field = $this->emailEvent->GetDBField('Type') == EmailEvent::EVENT_TYPE_FRONTEND ? 'FrontLanguage' : 'AdminLanguage'; + + $sql = 'SELECT FirstName, LastName, Email, ' . $language_field . ' AS Language + FROM ' . TABLE_PREFIX . 'Users + WHERE PortalUserId = ' . $this->recipientUserId; + $user_info = $this->Conn->GetRow($sql); + + if ( !$user_info ) { + return; + } + + $add_recipient = Array ( + 'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL, + 'RecipientAddress' => $user_info['Email'], + 'RecipientName' => trim($user_info['FirstName'] . ' ' . $user_info['LastName']), + ); + + if ( $user_info['Language'] && !isset($this->params['language_id']) ) { + $this->params['language_id'] = $user_info['Language']; + } + + array_unshift($this->recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient); + + $user = $this->Application->recallObject('u.email-to', null, Array('skip_autoload' => true)); + /* @var $user UsersItem */ + + $user->Load($this->recipientUserId); + } + + /** + * Update with custom data given during event execution (email + name) + * + * @return void + * @access protected + */ + protected function _addRecipientFromParams() + { + $add_recipient = Array (); + + if ( array_key_exists('to_email', $this->params) ) { + $add_recipient['RecipientName'] = ''; + $add_recipient['RecipientAddressType'] = EmailEvent::ADDRESS_TYPE_EMAIL; + $add_recipient['RecipientAddress'] = $this->params['to_email']; + } + + if ( array_key_exists('to_name', $this->params) ) { + $add_recipient['RecipientName'] = $this->params['to_name']; + } + + if ( $add_recipient ) { + array_unshift($this->recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient); + } + } + + /** + * This is default recipient, when we can't determine actual one + * + * @return void + * @access protected + */ + protected function _addDefaultRecipient() + { + $xml = $this->Application->ConfigValue('DefaultEmailRecipients'); + + if ( !$this->_addRecipientsFromXml($xml) ) { + $recipient = Array ( + 'RecipientName' => $this->Application->ConfigValue('DefaultEmailSender'), + 'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL, + 'RecipientAddress' => $this->Application->ConfigValue('DefaultEmailSender'), + ); + + array_unshift($this->recipients[EmailEvent::RECIPIENT_TYPE_TO], $recipient); + } + } + + /** + * Transforms recipients into name/e-mail pairs + * + * @param Array $recipients + * @return Array + * @access protected + */ + protected function _transformRecipientsIntoPairs($recipients) + { + if ( !$recipients ) { + return Array (); + } + + $pairs = Array (); + + foreach ($recipients as $recipient) { + $address = $recipient['RecipientAddress']; + $address_type = $recipient['RecipientAddressType']; + $recipient_name = $recipient['RecipientName']; + + switch ($address_type) { + case EmailEvent::ADDRESS_TYPE_EMAIL: + $pairs[] = Array ('email' => $address, 'name' => $recipient_name); + break; + + case EmailEvent::ADDRESS_TYPE_USER: + $sql = 'SELECT FirstName, LastName, Email + FROM ' . TABLE_PREFIX . 'Users + WHERE Username = ' . $this->Conn->qstr($address); + $user_info = $this->Conn->GetRow($sql); + + if ( $user_info ) { + // user still exists + $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); + + $pairs[] = Array ( + 'email' => $user_info['Email'], + 'name' => $name ? $name : $recipient_name, + ); + } + break; + + case EmailEvent::ADDRESS_TYPE_GROUP: + $sql = 'SELECT u.FirstName, u.LastName, u.Email + FROM ' . TABLE_PREFIX . 'UserGroups g + JOIN ' . TABLE_PREFIX . 'UserGroupRelations ug ON ug.GroupId = g.GroupId + JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = ug.PortalUserId + WHERE g.Name = ' . $this->Conn->qstr($address); + $users = $this->Conn->Query($sql); + + foreach ($users as $user_info) { + $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); + + $pairs[] = Array ( + 'email' => $user_info['Email'], + 'name' => $name ? $name : $recipient_name, + ); + } + break; + } + } + + return $pairs; + } + /** + * Change system language temporarily to send e-mail on user language + * + * @param bool $restore + * @return void + * @access protected + */ + protected function _changeLanguage($restore = false) + { + static $prev_language_id = null; + + if ( !isset($prev_language_id) ) { + $prev_language_id = $this->Application->GetVar('m_lang'); + } + + // ensure that language is set + if ( !isset($this->params['language_id']) ) { + $this->params['language_id'] = $this->Application->GetVar('m_lang'); + } + + $language_id = $restore ? $prev_language_id : $this->params['language_id']; + $this->Application->SetVar('m_lang', $language_id); + + $language = $this->Application->recallObject('lang.current'); + /* @var $language LanguagesItem */ + + $language->Load($language_id); + + $this->Application->Phrases->LanguageId = $language_id; + $this->Application->Phrases->Phrases = Array (); + } + + /** + * Parses message headers into array + * + * @return Array + * @access protected + */ + protected function _getHeaders() + { + $headers = $this->emailEvent->GetDBField('Headers'); + $headers = 'Subject: ' . $this->emailEvent->GetField('Subject') . ($headers ? "\n" . $headers : ''); + $headers = explode("\n", $this->_parseText($headers)); + + $ret = Array (); + + foreach ($headers as $header) { + $header = explode(':', $header, 2); + $ret[ trim($header[0]) ] = trim($header[1]); + } + + if ( $this->Application->isDebugMode() ) { + // set special header with event name, so it will be easier to determine what's actually was received + $event_type = $this->emailEvent->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN ? 'ADMIN' : 'USER'; + $ret['X-Event-Name'] = $this->emailEvent->GetDBField('Event') . ' - ' . $event_type; + } + + return $ret; + } + + /** + * Applies design to given e-mail text + * + * @param string $text + * @param bool $is_html + * @return string + * @access protected + */ + protected function _applyMessageDesign($text, $is_html = true) + { + static $design_templates = Array(); + + $design_key = $is_html ? 'html' : 'text'; + + if ( !isset($design_templates[$design_key]) ) { + $primary_language_id = $this->Application->GetDefaultLanguageId(); + + $sql = 'SELECT IF(l' . $this->params['language_id'] . '_Body <> "", l' . $this->params['language_id'] . '_Body, l' . $primary_language_id . '_Body) + FROM ' . $this->emailEvent->TableName . ' + WHERE Event = "COMMON.FOOTER"'; + $footer = $this->Conn->GetOne($sql); + + if ( !$is_html ) { + $footer = $this->sender->ConvertToText($footer); + } + + $design_templates[$design_key] = '$body'; + + if ( $footer ) { + $design_templates[$design_key] .= "\r\n" . $footer; + } + } + + return $this->_parseText(str_replace('$body', $text, $design_templates[$design_key])); + } + + /** + * Returns message body + * + * @param bool $is_html + * @return bool|string + * @access protected + */ + protected function _getMessageBody($is_html = true) + { + $message_body = $this->emailEvent->GetField('Body'); + + if ( !trim($message_body) ) { + trigger_error('Message template is empty', E_USER_WARNING); + + return false; + } + + $message_body = $this->_applyMessageDesign($message_body, $is_html); + + if ( !trim($message_body) ) { + trigger_error('Message template is empty after parsing', E_USER_WARNING); + + return false; + } + + return $message_body; + } + + /** + * Parse message template and return headers (as array) and message body part + * + * @param string $text + * @return string + * @access protected + */ + protected function _parseText($text) + { + $text = $this->_substituteReplacementTags($text); + + if ( !$text ) { + return ''; + } + + // init for cases, when e-mail is sent from event before page template rendering + $this->Application->InitParser(); + + $parser_params = $this->Application->Parser->Params; // backup parser params + $this->Application->Parser->SetParams( array_merge($parser_params, $this->params) ); + + $text = $this->Application->Parser->Parse($text, 'email_template'); + $this->Application->Parser->SetParams($parser_params); // restore parser params + + $category_helper = $this->Application->recallObject('CategoryHelper'); + /* @var $category_helper CategoryHelper */ + + return $category_helper->replacePageIds($this->_normalizeLineEndings($text)); + } + + /** + * Substitutes replacement tags in given text + * + * @param string $text + * @return string + * @access protected + */ + protected function _substituteReplacementTags($text) + { + $default_replacement_tags = Array ( + ' ' ' 'emailEvent->GetDBField('ReplacementTags'); + $replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array (); + $replacement_tags = array_merge($default_replacement_tags, $replacement_tags); + + foreach ($replacement_tags as $replace_from => $replace_to) { + $text = str_replace($replace_from, $replace_to, $text); + } + + return $text; + } + + /** + * Normalizes line endings in given text + * + * @param string $text + * @return string + * @access protected + */ + protected function _normalizeLineEndings($text) + { + // remove trailing line endings + $text = preg_replace('/(\n|\r)+/', "\\1", $text); + + // convert Unix/Windows/Mac line ending into Unix line endings + return str_replace(Array ("\r\n", "\r"), "\n", $text); + } +} \ No newline at end of file Index: branches/5.2.x/core/units/users/users_event_handler.php =================================================================== diff -u -N -r15145 -r15215 --- branches/5.2.x/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 15145) +++ branches/5.2.x/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 15215) @@ -1,6 +1,6 @@ Application->RecallVar('user_id'); - $email_event =& $this->Application->EmailEventUser('USER.SUGGEST', $user_id, $send_params); - $email_event =& $this->Application->EmailEventAdmin('USER.SUGGEST'); + $email_sent = $this->Application->EmailEventUser('USER.SUGGEST', $user_id, $send_params); + $this->Application->EmailEventAdmin('USER.SUGGEST'); - if ( $email_event->status == kEvent::erSUCCESS ) { + if ( $email_sent ) { $event->SetRedirectParam('pass', 'all'); $event->redirect = $this->Application->GetVar('template_success'); } Index: branches/5.2.x/core/kernel/application.php =================================================================== diff -u -N -r15173 -r15215 --- branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15173) +++ branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15215) @@ -1,6 +1,6 @@ registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php'); // email sending + $this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php'); $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender'); $this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket'); @@ -2594,11 +2595,9 @@ * @return kEvent * @access public */ - public function &EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ()) + public function EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ()) { - $event =& $this->EmailEvent($email_event_name, EmailEvent::EVENT_TYPE_ADMIN, $to_user_id, $send_params); - - return $event; + return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_ADMIN, $to_user_id, $send_params); } /** @@ -2610,11 +2609,9 @@ * @return kEvent * @access public */ - public function &EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ()) + public function EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ()) { - $event =& $this->EmailEvent($email_event_name, EmailEvent::EVENT_TYPE_FRONTEND, $to_user_id, $send_params); - - return $event; + return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_FRONTEND, $to_user_id, $send_params); } /** @@ -2628,26 +2625,18 @@ * @return kEvent * @access protected */ - protected function &EmailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ()) + protected function _emailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ()) { - $params = Array ( - 'EmailEventName' => $email_event_name, - 'EmailEventToUserId' => $to_user_id, - 'EmailEventType' => $email_event_type, - 'DirectSendParams' => $send_params, - ); + $email = $this->makeClass('kEmail'); + /* @var $email kEmail */ - if ( array_key_exists('use_special', $send_params) ) { - $event_str = 'emailevents.' . $send_params['use_special'] . ':OnEmailEvent'; + if ( !$email->findEvent($email_event_name, $email_event_type) ) { + return false; } - else { - $event_str = 'emailevents:OnEmailEvent'; - } - $event = new kEvent($event_str, $params); - $this->HandleEvent($event); + $email->setParams($send_params); - return $event; + return $email->send($to_user_id); } /** Index: branches/5.2.x/core/install/upgrades.sql =================================================================== diff -u -N -r15177 -r15215 --- branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 15177) +++ branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 15215) @@ -2658,3 +2658,5 @@ DROP Html, CHANGE Status Status TINYINT(1) NOT NULL DEFAULT '1', CHANGE CategoryId CategoryId INT(11) NULL; + +# ===== v 5.2.0-B3 =====