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 $html_message_body = $this->_getMessageBody(true); $plain_message_body = $this->_getMessageBody(false); if ( $html_message_body === false && $plain_message_body === false ) { trigger_error('Message template is empty (maybe after parsing).', E_USER_WARNING); return false; } if ( $html_message_body !== false ) { $this->sender->CreateTextHtmlPart($html_message_body, true); } if ( $plain_message_body !== false ) { $this->sender->CreateTextHtmlPart($plain_message_body, false); } $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]) ) { $footer_email = $this->Application->recallObject('emailevents.footer', null, Array ('skip_autoload' => true)); /* @var $footer_email kDBItem */ $footer_email->Load('COMMON.FOOTER', 'Event'); $footer = $footer_email->GetField($is_html ? 'HtmlBody' : 'PlainTextBody'); if ( !$is_html && !$footer ) { $footer = $this->sender->ConvertToText($footer_email->GetField('HtmlBody')); } $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 = false) { $message_body = $this->emailEvent->GetField($is_html ? 'HtmlBody' : 'PlainTextBody'); if ( !trim($message_body) && !$is_html ) { // no plain text part available -> make it from html part then $message_body = $this->sender->ConvertToText($this->emailEvent->GetField('HtmlBody')); } if ( !trim($message_body) ) { return false; } $message_body = $this->_applyMessageDesign($message_body, $is_html); return trim($message_body) ? $message_body : false; } /** * 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); } }