Index: branches/5.3.x/core/kernel/utility/email.php =================================================================== --- branches/5.3.x/core/kernel/utility/email.php (revision 16596) +++ branches/5.3.x/core/kernel/utility/email.php (revision 16597) @@ -1,971 +1,981 @@ Array (), EmailTemplate::RECIPIENT_TYPE_CC => Array (), EmailTemplate::RECIPIENT_TYPE_BCC => Array (), ); /** * Stores log data. * * @var array */ protected $logData = 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->logData = array(); $this->fromEmail = $this->fromName = ''; $this->Application->removeObject('u.email-from'); $this->recipients = Array ( EmailTemplate::RECIPIENT_TYPE_TO => Array (), EmailTemplate::RECIPIENT_TYPE_CC => Array (), EmailTemplate::RECIPIENT_TYPE_BCC => Array (), ); $this->toEmail = $this->toEmail = ''; $this->Application->removeObject('u.email-to'); } /** * Finds e-mail template matching user data * * @param string $name * @param int $type * @return bool * @throws InvalidArgumentException * @access public */ public function findTemplate($name, $type) { if ( !$name || !preg_match('/^[A-Z\.]+$/', $name) ) { throw new InvalidArgumentException('Invalid e-mail template name "' . $name . '". Only UPPERCASE characters and dots are allowed.'); } if ( $type != EmailTemplate::TEMPLATE_TYPE_ADMIN && $type != EmailTemplate::TEMPLATE_TYPE_FRONTEND ) { throw new InvalidArgumentException('Invalid e-mail template type'); } // use "-item" special prevent error, when e-mail sent out from e-mail templates list $this->emailTemplate = $this->Application->recallObject('email-template.-item', null, Array ('skip_autoload' => true)); if ( !$this->emailTemplate->isLoaded() || !$this->_sameTemplate($name, $type) ) { // get template parameters by name & type $this->emailTemplate->Load(Array ('TemplateName' => $name, 'Type' => $type)); } return $this->_templateUsable(); } /** * Detects, that given e-mail template data matches currently used e-mail template * * @param string $name * @param int $type * @return bool * @access protected */ protected function _sameTemplate($name, $type) { return $this->emailTemplate->GetDBField('TemplateName') == $name && $this->emailTemplate->GetDBField('Type') == $type; } /** * Determines if we can use e-mail template we've found based on user data * * @return bool * @access protected */ protected function _templateUsable() { if ( !$this->emailTemplate->isLoaded() || $this->emailTemplate->GetDBField('Enabled') == STATUS_DISABLED ) { return false; } if ( $this->emailTemplate->GetDBField('FrontEndOnly') && $this->Application->isAdmin ) { return false; } return true; } /** * Sets e-mail template params * * @param Array $params * @access public */ public function setParams($params) { $this->params = $params; } /** * Returns any custom parameters, that are passed when invoked e-mail template 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', 'use_custom_design', 'delivery', 'PrefixSpecial', 'item_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 - * @return bool - * @access public + * @param integer $recipient_user_id Recipient user id. + * @param boolean $immediate_send Immediate send. + * + * @return boolean */ - public function send($recipient_user_id = null) + public function send($recipient_user_id = null, $immediate_send = null) { $this->recipientUserId = $recipient_user_id; $this->_resetState(); $this->_processSender(); $this->_processRecipients(); $this->_changeLanguage(false); // 1. set headers try { $message_headers = $this->_getHeaders(); } catch ( Exception $e ) { return $this->setError('Error parsing e-mail message headers'); } $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); } if ( $this->_storeEmailLog() ) { // 1. prepare log $this->logData = Array ( 'From' => $this->fromName . ' (' . $this->fromEmail . ')', 'To' => $this->toName . ' (' . $this->toEmail . ')', 'OtherRecipients' => serialize($this->recipients), 'Subject' => $message_subject, 'Status' => EmailLogStatus::SENT, 'ErrorMessage' => '', 'SentOn' => TIMENOW, 'TemplateName' => $this->emailTemplate->GetDBField('TemplateName'), 'EventType' => $this->emailTemplate->GetDBField('Type'), 'EventParams' => serialize($this->_getCustomParams()), 'ToUserId' => $this->recipientUserId, 'ItemPrefix' => $this->getItemPrefix(), 'ItemId' => isset($this->params['item_id']) ? $this->params['item_id'] : null, ); $this->params['email_access_key'] = $this->_generateAccessKey(); } // 3. set body try { $html_message_body = $this->_getMessageBody(true); $plain_message_body = $this->_getMessageBody(false); } catch ( Exception $e ) { return $this->setError('Error parsing e-mail message body'); } if ( $html_message_body === false && $plain_message_body === false ) { return $this->setError('Message template is empty (maybe after parsing).'); } 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); if ( $this->_storeEmailLog() ) { // 4. set log $this->logData['HtmlBody'] = $html_message_body; $this->logData['TextBody'] = $plain_message_body; $this->logData['AccessKey'] = $this->params['email_access_key']; $this->sender->setLogData($this->logData); } - $delivery = isset($this->params['delivery']) ? $this->params['delivery'] : $this->Application->ConfigValue('EmailDelivery'); + if ( !isset($immediate_send) ) { + if ( isset($this->params['delivery']) ) { + $delivery = $this->params['delivery']; + } + else { + $delivery = $this->Application->ConfigValue('EmailDelivery'); + } + + $immediate_send = $delivery == EmailDelivery::IMMEDIATE; + } - return $this->sender->Deliver(null, $delivery == EmailDelivery::IMMEDIATE); + return $this->sender->Deliver(null, $immediate_send); } /** * Extracts prefix from a given PrefixSpecial parameter. * * @return string */ protected function getItemPrefix() { $prefix_special = isset($this->params['PrefixSpecial']) ? $this->params['PrefixSpecial'] : ''; if ( !$prefix_special ) { return ''; } $prefix_info = $this->Application->processPrefix($prefix_special); return $prefix_info['prefix']; } /** * Determines whatever we should keep e-mail log or not * * @return bool * @access protected */ protected function _storeEmailLog() { return $this->Application->ConfigValue('EnableEmailLog'); } /** * Marks e-mail sending as failed. * * @param string $error_message Error message. * * @return boolean */ protected function setError($error_message) { if ( $this->_storeEmailLog() ) { $this->logData['Status'] = EmailLogStatus::ERROR; $this->logData['ErrorMessage'] = $error_message; $this->Conn->doInsert($this->logData, TABLE_PREFIX . 'EmailLog'); } return false; } /** * Generates access key for accessing e-mail later * * @return string * @access protected */ protected function _generateAccessKey() { $ret = ''; $use_fields = Array ('From', 'To', 'Subject'); foreach ($use_fields as $use_field) { $ret .= $this->logData[$use_field] . ':'; } return md5($ret . microtime(true)); } /** * Processes email sender * * @return void * @access protected */ protected function _processSender() { if ( $this->emailTemplate->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->emailTemplate->GetDBField('SenderAddress'); $address_type = $this->emailTemplate->GetDBField('SenderAddressType'); switch ($address_type) { case EmailTemplate::ADDRESS_TYPE_EMAIL: $this->fromEmail = $address; break; case EmailTemplate::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']); /** @var UsersItem $user */ $user = $this->Application->recallObject( 'u.email-from', null, array('live_table' => true, 'skip_autoload' => true) ); $user->Load($user_info['PortalUserId']); } break; } if ( $this->emailTemplate->GetDBField('SenderName') ) { $this->fromName = $this->emailTemplate->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 = $this->getHeaderMapping(); $default_email = $this->Application->ConfigValue('DefaultEmailSender'); $this->recipients = array_map(Array ($this, '_transformRecipientsIntoPairs'), $this->recipients); foreach ($this->recipients as $recipient_type => $recipients) { // add recipients to email if ( !$recipients ) { continue; } if ( $recipient_type == EmailTemplate::RECIPIENT_TYPE_TO ) { $this->toEmail = $recipients[0]['email'] ? $recipients[0]['email'] : $default_email; $this->toName = $recipients[0]['name'] ? $recipients[0]['name'] : $this->toEmail; } $header_name = $header_mapping[$recipient_type]; foreach ($recipients as $recipient) { $email = $recipient['email'] ? $recipient['email'] : $default_email; $name = $recipient['name'] ? $recipient['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->emailTemplate->GetDBField('Recipients')); $this->_overwriteToRecipient(); $this->_addRecipientByUserId(); $this->_addRecipientFromParams(); $this->_moveDirectRecipients(); if ( ($this->emailTemplate->GetDBField('Type') == EmailTemplate::TEMPLATE_TYPE_ADMIN) && !$this->recipients[EmailTemplate::RECIPIENT_TYPE_TO] ) { // admin email template 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; } /** @var MInputHelper $minput_helper */ $minput_helper = $this->Application->recallObject('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->emailTemplate->GetDBField('CustomRecipient') || $overwrite_to_email ) { $this->recipients[EmailTemplate::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->emailTemplate->GetDBField('Type') == EmailTemplate::TEMPLATE_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' => EmailTemplate::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[EmailTemplate::RECIPIENT_TYPE_TO], $add_recipient); /** @var UsersItem $user */ $user = $this->Application->recallObject( 'u.email-to', null, array('live_table' => true, 'skip_autoload' => true) ); $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 ( isset($this->params['to_email']) && $this->params['to_email'] ) { $add_recipient['RecipientName'] = ''; $add_recipient['RecipientAddressType'] = EmailTemplate::ADDRESS_TYPE_EMAIL; $add_recipient['RecipientAddress'] = $this->params['to_email']; } if ( isset($this->params['to_name']) && $this->params['to_name'] ) { $add_recipient['RecipientName'] = $this->params['to_name']; } if ( $add_recipient ) { array_unshift($this->recipients[EmailTemplate::RECIPIENT_TYPE_TO], $add_recipient); } } /** * Move recipients, that were added manually via "$this->sender->Add*" methods. * * @return void * @access protected */ protected function _moveDirectRecipients() { foreach ( $this->getHeaderMapping() as $recipient_type => $header_name ) { $manual_recipients = $this->sender->GetRecipientsByHeader($header_name); if ( !$manual_recipients ) { continue; } foreach ( $manual_recipients as $manual_recipient ) { $this->recipients[$recipient_type][] = array( 'RecipientName' => $manual_recipient['Name'], 'RecipientAddressType' => EmailTemplate::ADDRESS_TYPE_EMAIL, 'RecipientAddress' => $manual_recipient['Email'], ); } $this->sender->SetHeader($header_name, ''); } } /** * Returns mapping between recipient type and header name. * * @return array */ protected function getHeaderMapping() { return array( EmailTemplate::RECIPIENT_TYPE_TO => 'To', EmailTemplate::RECIPIENT_TYPE_CC => 'Cc', EmailTemplate::RECIPIENT_TYPE_BCC => 'Bcc', ); } /** * 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' => EmailTemplate::ADDRESS_TYPE_EMAIL, 'RecipientAddress' => $this->Application->ConfigValue('DefaultEmailSender'), ); array_unshift($this->recipients[EmailTemplate::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 EmailTemplate::ADDRESS_TYPE_EMAIL: $pairs[] = Array ('email' => $address, 'name' => $recipient_name); break; case EmailTemplate::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 EmailTemplate::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); /** @var LanguagesItem $language */ $language = $this->Application->recallObject('lang.current'); $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->emailTemplate->GetDBField('Headers'); $headers = 'Subject: ' . $this->emailTemplate->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 template name, so it will be easier to determine what's actually was received $template_type = $this->emailTemplate->GetDBField('Type') == EmailTemplate::TEMPLATE_TYPE_ADMIN ? 'ADMIN' : 'USER'; $ret['X-Template-Name'] = $this->emailTemplate->GetDBField('TemplateName') . ' - ' . $template_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 = 'L' . $this->params['language_id'] . ':' . ($is_html ? 'html' : 'text'); if ( !isset($design_templates[$design_key]) ) { /** @var LanguagesItem $language */ $language = $this->Application->recallObject('lang.current'); $design_template = $language->GetDBField($is_html ? 'HtmlEmailTemplate' : 'TextEmailTemplate'); if ( !$is_html && !$design_template ) { $design_template = $this->sender->ConvertToText($language->GetDBField('HtmlEmailTemplate'), true); } $design_templates[$design_key] = $design_template; } return $this->_parseText(str_replace('$body', $text, $design_templates[$design_key]), $is_html); } /** * Returns message body * * @param bool $is_html * @return bool|string * @access protected */ protected function _getMessageBody($is_html = false) { $message_body = $this->emailTemplate->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->emailTemplate->GetField('HtmlBody'), true); } if ( !trim($message_body) ) { return false; } if ( isset($this->params['use_custom_design']) && $this->params['use_custom_design'] ) { $message_body = $this->_parseText($message_body, $is_html); } else { $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 * @param bool $is_html * @return string * @access protected */ protected function _parseText($text, $is_html = true) { $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($this->params); $template_name = 'et_' . $this->emailTemplate->GetID() . '_' . crc32($text); $text = $this->Application->Parser->Parse($this->_normalizeLineEndings($text), $template_name); $this->Application->Parser->SetParams($parser_params); // restore parser params /** @var CategoryHelper $category_helper */ $category_helper = $this->Application->recallObject('CategoryHelper'); return $category_helper->replacePageIds($is_html ? $this->_removeTrailingLineEndings($text) : $text); } /** * Substitutes replacement tags in given text * * @param string $text * @return string * @access protected */ protected function _substituteReplacementTags($text) { $default_replacement_tags = Array ( ' ' ' 'emailTemplate->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; } /** * Convert Unix/Windows/Mac line ending into Unix line endings * * @param string $text * @return string * @access protected */ protected function _normalizeLineEndings($text) { return str_replace(Array ("\r\n", "\r"), "\n", $text); } /** * Remove trailing line endings * * @param $text * @return string * @access protected */ protected function _removeTrailingLineEndings($text) { return preg_replace('/(\n|\r)+/', "\\1", $text); } } Index: branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php =================================================================== --- branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php (revision 16596) +++ branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php (revision 16597) @@ -1,351 +1,387 @@ Array ('self' => 'edit'), 'OnGenerateEmailQueue' => Array ('self' => true), + 'OnProcessEmailQueue' => Array ('self' => true), 'OnGetHtmlBody' => Array ('self' => 'edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Prepare recipient list * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { parent::OnNew($event); $recipient_type = $this->Application->GetVar('mailing_recipient_type'); if ( !$recipient_type ) { return; } $recipients = $this->Application->GetVar($recipient_type); if ( $recipients ) { /** @var kDBItem $object */ $object = $event->getObject(); $to = $recipient_type . '_' . implode(';' . $recipient_type . '_', array_keys($recipients)); $object->SetDBField('To', $to); } $this->setRequired($event); } /** * Prepare recipient list * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); $this->setRequired($event); } /** * Don't allow to delete mailings in progress * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { if ( $event->Name == 'OnMassDelete' && $type == 'before' ) { $ids = $event->getEventParam('ids'); if ( $ids ) { $config = $event->getUnitConfig(); $id_field = $config->getIDField(); $sql = 'SELECT ' . $id_field . ' FROM ' . $config->getTableName() . ' WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Status <> ' . MailingList::PARTIALLY_PROCESSED; $allowed_ids = $this->Conn->GetCol($sql); $event->setEventParam('ids', $allowed_ids); } } } /** * Delete all related mails in email queue * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); $this->_deleteQueue($event); /** @var kDBItem $object */ $object = $event->getObject(); // delete mailing attachments after mailing is deleted $attachments = $object->GetField('Attachments', 'file_paths'); if ( $attachments ) { $attachments = explode('|', $attachments); foreach ($attachments as $attachment_file) { if ( file_exists($attachment_file) ) { unlink($attachment_file); } } } } /** * Cancels given mailing and deletes all it's email queue * * @param kEvent $event */ function OnCancelMailing($event) { /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if ($ids) { foreach ($ids as $id) { $object->Load($id); $object->SetDBField('Status', MailingList::CANCELLED); $object->Update(); } } $this->clearSelectedIDs($event); } /** * Checks, that at least one message text field is filled * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( !$this->Application->GetVar('mailing_recipient_type') ) { // user manually typed email addresses -> normalize $recipients = str_replace(',', ';', $object->GetDBField('To')); $recipients = array_map('trim', explode(';', $recipients)); $object->SetDBField('To', implode(';', $recipients)); } // remember user, who created mailing, because of his name // is needed for "From" field, but mailing occurs from cron $user_id = $this->Application->RecallVar('user_id'); $object->SetDBField('PortalUserId', $user_id); $this->setRequired($event); } /** * Checks, that at least one message text field is filled * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->setRequired($event); } /** * Dynamically changes required fields * * @param kEvent $event * @return void * @access protected */ protected function setRequired(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $object->setRequired('MessageHtml', !$object->GetDBField('MessageText')); $object->setRequired('MessageText', !$object->GetDBField('MessageHtml')); } /** * Deletes mailing list email queue, when it becomes cancelled * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); /** @var kDBItem $object */ $object = $event->getObject(); $status = $object->GetDBField('Status'); if ( ($status != $object->GetOriginalField('Status')) && ($status == MailingList::CANCELLED) ) { $this->_deleteQueue($event); } } /** * Deletes email queue records related with given mailing list * * @param kEvent $event */ function _deleteQueue($event) { /** @var kDBItem $object */ $object = $event->getObject(); $sql = 'DELETE FROM ' . $this->Application->getUnitConfig('email-queue')->getTableName() . ' WHERE MailingId = ' . $object->GetID(); $this->Conn->Query($sql); } /** + * Allows to safely get mailing configuration variables + * + * @param string $variable_name Variable name. + * + * @return integer + * @deprecated 5.3.0-B1 + * @see MailingListHelper::getSetting() + */ + function _ensureDefault($variable_name) + { + kUtil::deprecatedMethod(__METHOD__, '5.3.0-B1', 'MailingListHelper::getSetting'); + + /** @var MailingListHelper $mailing_list_helper */ + $mailing_list_helper = $this->Application->recallObject('MailingListHelper'); + + return $mailing_list_helper->getSetting($variable_name); + } + + /** * Generates email queue for active mailing lists * * @param kEvent $event */ function OnGenerateEmailQueue($event) { $config = $event->getUnitConfig(); $id_field = $config->getIDField(); $table_name = $config->getTableName(); $where_clause = Array ( 'Status NOT IN (' . MailingList::CANCELLED . ',' . MailingList::PROCESSED . ')', '(EmailsQueuedTotal < EmailsTotal) OR (EmailsTotal = 0)', '`To` <> ""', ); $sql = 'SELECT * FROM ' . $table_name . ' WHERE (' . implode(') AND (', $where_clause) . ') ORDER BY ' . $id_field . ' ASC'; $mailing_lists = $this->Conn->Query($sql, $id_field); if ( !$mailing_lists ) { return; } /** @var MailingListHelper $mailing_list_helper */ $mailing_list_helper = $this->Application->recallObject('MailingListHelper'); $to_queue = $mailing_list_helper->getSetting('MailingListQueuePerStep'); if ( !is_numeric($to_queue) ) { return; } foreach ($mailing_lists as $mailing_id => $mailing_data) { if ( $mailing_data['EmailsTotal'] == 0 ) { // no work performed on this mailing list -> calculate totals $updated_fields = $mailing_list_helper->generateRecipients($mailing_id, $mailing_data); $updated_fields['Status'] = MailingList::PARTIALLY_PROCESSED; $mailing_data = array_merge($mailing_data, $updated_fields); $this->Conn->doUpdate($updated_fields, $table_name, $id_field . ' = ' . $mailing_id); } $emails = unserialize($mailing_data['ToParsed']); if ( !$emails ) { continue; } // queue allowed count of emails $i = 0; $process_count = count($emails) >= $to_queue ? $to_queue : count($emails); while ($i < $process_count) { $mailing_list_helper->queueEmail($emails[$i], $mailing_id, $mailing_data); $i++; } // remove processed emails from array $to_queue -= $process_count; // decrement available for processing email count array_splice($emails, 0, $process_count); $updated_fields = Array ( 'ToParsed' => serialize($emails), 'EmailsQueuedTotal' => $mailing_data['EmailsQueuedTotal'] + $process_count, ); $this->Conn->doUpdate($updated_fields, $table_name, $id_field . ' = ' . $mailing_id); if ( !$to_queue ) { // emails to be queued per step reached -> leave break; } } } /** + * [SCHEDULED TASK] Process email queue from cron + * + * @param kEvent $event Event. + * + * @return void + * @deprecated 5.3.0-B1 + * @see EmailQueueEventHandler::OnProcess() + */ + function OnProcessEmailQueue($event) + { + kUtil::deprecatedMethod(__METHOD__, '5.3.0-B1', 'EmailQueueEventHandler::OnProcess'); + + $event->CallSubEvent('email-queue:OnProcess'); + } + + /** * Returns HTML of sent e-mail for iframe * * @param kEvent $event * @return void * @access protected */ protected function OnGetHtmlBody(kEvent $event) { $event->status = kEvent::erSTOP; /** @var kDBItem $object */ $object = $event->getObject(); echo '' . $object->GetDBField('MessageHtml') . ''; } } Index: branches/5.3.x/core/units/helpers/mailing_list_helper.php =================================================================== --- branches/5.3.x/core/units/helpers/mailing_list_helper.php (revision 16596) +++ branches/5.3.x/core/units/helpers/mailing_list_helper.php (revision 16597) @@ -1,386 +1,394 @@ Application->recallObject('EmailSender'); if ($this->_mailingId != $mailing_id) { if (is_numeric($this->_mailingId)) { // clear fields after previous mailing processing $esender->Clear(); } // 1. set headers same for all emails list ($mailing_data['FromName'], $mailing_data['FromEmail']) = $this->_getSenderData($mailing_data); $esender->SetFrom($mailing_data['FromEmail'], $mailing_data['FromName']); $esender->SetSubject($mailing_data['Subject']); if ( !$mailing_data['MessageText'] ) { $mailing_data['MessageText'] = $esender->ConvertToText($mailing_data['MessageHtml']); } $esender->SetBody($mailing_data['MessageHtml'], $mailing_data['MessageText']); // 2. add attachment if any $attachments = $mailing_data['Attachments'] ? explode('|', $mailing_data['Attachments']) : Array (); foreach ($attachments as $attachment) { $esender->AddAttachment(FULL_PATH . ITEM_FILES_PATH . $attachment); } $this->_mailingId = $mailing_id; } // 3. set recipient specific fields $esender->SetTo($email, $email); if ( $this->Application->ConfigValue('EnableEmailLog') ) { // 4. write to log $log_fields_hash = Array ( 'From' => $mailing_data['FromName'] . '(' . $mailing_data['FromEmail'] . ')', 'To' => $email, 'Subject' => $mailing_data['Subject'], 'HtmlBody' => $mailing_data['MessageHtml'], 'TextBody' => $mailing_data['MessageText'], 'SentOn' => TIMENOW, 'EventParams' => serialize( Array ('MailingId' => $mailing_id) ), ); $esender->setLogData($log_fields_hash); } $esender->Deliver(null, $mailing_id, false); } /** * Returns mass mail sender name & email * * @param Array $mailing_data * @return Array * @access protected */ protected function _getSenderData(&$mailing_data) { $is_root = true; $email_address = $name = ''; if ( $mailing_data['PortalUserId'] > 0 ) { /** @var UsersItem $sender */ $sender = $this->Application->recallObject('u.-item', null, Array ('skip_autoload' => true)); $sender->Load($mailing_data['PortalUserId']); $email_address = $sender->GetDBField('Email'); $name = trim($sender->GetDBField('FirstName') . ' ' . $sender->GetDBField('LastName')); $is_root = false; } if ( $is_root || !$email_address ) { $email_address = $this->Application->ConfigValue('DefaultEmailSender'); } if ( $is_root || !$name ) { $name = strip_tags($this->Application->ConfigValue('Site_Name')); } return Array ($name, $email_address); } /** * Generates recipients emails based on "To" field value. * * @param int $id Id. * @param Array $fields_hash Fields hash. * * @return array */ function generateRecipients($id, $fields_hash) { // for each group convert ids to names $recipient_emails = Array (); $recipients_grouped = $this->groupRecipientsByType(explode(';', $fields_hash['To'])); foreach ($recipients_grouped as $recipient_type => $group_recipients) { $recipient_emails = array_merge($recipient_emails, $this->_getRecipientEmails($recipient_type, $group_recipients)); } $recipient_emails = array_unique($recipient_emails); return Array ( 'ToParsed' => serialize($recipient_emails), 'EmailsTotal' => count($recipient_emails), ); } /** * Groups recipients by type * * @param Array $recipients * @return Array * @access public */ public function groupRecipientsByType($recipients) { $recipients_grouped = Array (); foreach ($recipients as $recipient) { if ( strpos($recipient, '_') !== false ) { list ($recipient_type, $recipient_id) = explode('_', $recipient); } else { $recipient_type = 'direct'; $recipient_id = $recipient; } if ( !array_key_exists($recipient_type, $recipients_grouped) ) { $recipients_grouped[$recipient_type] = Array (); } $recipients_grouped[$recipient_type][] = $recipient_id; } return $recipients_grouped; } function _getRecipientEmails($recipient_type, $recipient_ids) { if (strpos($recipient_type, '.') !== false) { // remove special list ($recipient_type, ) = explode('.', $recipient_type); } if ($recipient_type != 'u' && $recipient_type != 'g') { // these are already emails return $recipient_ids; } switch ($recipient_type) { case 'u': $sql = 'SELECT Email FROM ' . TABLE_PREFIX . 'Users WHERE (PortalUserId IN (' . implode(',', $recipient_ids) . ')) AND (Email <> "")'; break; case 'g': $sql = 'SELECT u.Email FROM ' . TABLE_PREFIX . 'UserGroupRelations ug LEFT JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = ug.PortalUserId WHERE (ug.GroupId IN (' . implode(',', $recipient_ids) . ')) AND (u.Email <> "")'; break; default: $sql = ''; break; } return $this->Conn->GetCol($sql); } function getRecipientNames($recipient_type, $recipient_ids) { if (strpos($recipient_type, '.') !== false) { // remove special list ($recipient_type, ) = explode('.', $recipient_type); } switch ($recipient_type) { case 'u': $title_field = 'Email'; break; case 'g': $title_field = 'Name'; break; default: $title_field = false; break; } if ($title_field === false || !$recipient_ids) { return $recipient_ids; } $config = $this->Application->getUnitConfig($recipient_type); $id_field = $config->getIDField(); $table_name = $config->getTableName(); $sql = 'SELECT ' . $title_field . ' FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . implode(',', $recipient_ids) . ')'; return $this->Conn->GetCol($sql); } /** * Updates information about sent email count based on given totals by mailings * * @param Array $mailing_totals */ function _updateSentTotals($mailing_totals) { if ( array_key_exists(0, $mailing_totals) ) { // don't update sent email count for mails queued directly (not via mailing lists) unset($mailing_totals[0]); } $config = $this->Application->getUnitConfig('mailing-list'); $table_name = $config->getTableName(); // update sent email count for each processed mailing foreach ( $mailing_totals as $mailing_id => $mailing_total ) { $sql = 'UPDATE ' . $table_name . ' SET EmailsSent = EmailsSent + ' . $mailing_total . ' WHERE ' . $config->getIDField() . ' = ' . $mailing_id; $this->Conn->Query($sql); } // mark mailings, that were processed completely $sql = 'UPDATE ' . $table_name . ' SET Status = ' . MailingList::PROCESSED . ' WHERE (Status = ' . MailingList::PARTIALLY_PROCESSED . ') AND (EmailsSent = EmailsTotal)'; $this->Conn->Query($sql); } /** * Sent given messages from email queue. * + * @param array|null $messages Messages. + * * @return integer */ - function processQueue() + function processQueue(&$messages = null) { /** @var kEmailSendingHelper $esender */ $esender = $this->Application->recallObject('EmailSender'); - $messages = $this->getMessages(); + if ( !isset($messages) ) { + $messages = $this->getMessages(); + } + else { + kUtil::deprecatedArgument(__METHOD__, '5.3.0-B1', 'The "$messages" parameter is deprecated.'); + } + $message_count = count($messages); if ( !$message_count ) { return 0; } $i = 0; $message = Array(); $mailing_totals = Array(); $queue_table = $this->Application->getUnitConfig('email-queue')->getTableName(); while ( $i < $message_count ) { $message[0] = unserialize($messages[$i]['MessageHeaders']); $message[1] =& $messages[$i]['MessageBody']; $esender->setLogData(unserialize($messages[$i]['LogData'])); $delivered = $esender->Deliver($message, true); // immediate send! if ( $delivered ) { // send succeeded, delete from queue $sql = 'DELETE FROM ' . $queue_table . ' WHERE EmailQueueId = ' . $messages[$i]['EmailQueueId']; $this->Conn->Query($sql); $mailing_id = $messages[$i]['MailingId']; if ( !array_key_exists($mailing_id, $mailing_totals) ) { $mailing_totals[$mailing_id] = 0; } $mailing_totals[$mailing_id]++; } else { // send failed, increment retries counter $sql = 'UPDATE ' . $queue_table . ' SET SendRetries = SendRetries + 1, LastSendRetry = ' . time() . ' WHERE EmailQueueId = ' . $messages[$i]['EmailQueueId']; $this->Conn->Query($sql); } $i++; } $this->_updateSentTotals($mailing_totals); return $message_count; } /** * Returns queued messages (or their count), that can be sent * * @param bool $count_only * * @return Array|int * @access public */ public function getMessages($count_only = false) { $deliver_count = $this->getSetting('MailingListSendPerStep'); if ( !is_numeric($deliver_count) ) { return $count_only ? 0 : Array(); } $queue_table = $this->Application->getUnitConfig('email-queue')->getTableName(); if ( $count_only ) { $sql = 'SELECT COUNT(*) FROM ' . $queue_table . ' WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')'; return $this->Conn->GetOne($sql); } // regular e-mails are pressed before mailing generated ones ! $sql = 'SELECT * FROM ' . $queue_table . ' WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ') ORDER BY MailingId ASC LIMIT 0,' . $deliver_count; return $this->Conn->Query($sql); } /** * Allows to safely get mailing configuration variable * * @param string $variable_name * * @return int * @access public */ public function getSetting($variable_name) { $value = $this->Application->ConfigValue($variable_name); if ( $value === false ) { // ensure default value, when configuration variable is missing return 10; } if ( !$value ) { // configuration variable found, but it's value is empty or zero return false; } return $value; } - } \ No newline at end of file + } Index: branches/5.3.x/core/units/email_templates/email_template_eh.php =================================================================== --- branches/5.3.x/core/units/email_templates/email_template_eh.php (revision 16596) +++ branches/5.3.x/core/units/email_templates/email_template_eh.php (revision 16597) @@ -1,741 +1,758 @@ Array ('self' => 'edit'), 'OnSaveSelected' => Array ('self' => 'view'), + 'OnProcessEmailQueue' => Array ('self' => 'add|edit'), 'OnExportEmailTemplates' => Array ('self' => 'view'), 'OnSuggestAddressJSON' => Array ('self' => 'add|edit'), // events only for developers 'OnPreCreate' => Array ('self' => 'debug'), 'OnDelete' => Array ('self' => 'debug'), 'OnDeleteAll' => Array ('self' => 'debug'), 'OnMassDelete' => Array ('self' => 'debug'), 'OnMassApprove' => Array ('self' => 'debug'), 'OnMassDecline' => Array ('self' => 'debug'), 'OnSend' => Array ('self' => 'debug'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Changes permission section to one from REQUEST, not from config * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { $module = $this->Application->GetVar('module'); if ( strlen($module) > 0 ) { // checking permission when listing module email events in separate section $module = explode(':', $module, 2); if ( count($module) == 1 ) { $main_prefix = $this->Application->findModule('Name', $module[0], 'Var'); } else { $exceptions = Array ('Category' => 'c', 'Users' => 'u'); $main_prefix = $exceptions[$module[1]]; } $section = $this->Application->getUnitConfig($main_prefix)->getPermSectionByName('email'); $event->setEventParam('PermSection', $section); } // checking permission when listing all email events when editing language return parent::CheckPermission($event); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); if ( $event->Special == 'module' ) { $module = $this->Application->GetVar('module'); $object->addFilter('module_filter', '%1$s.Module = ' . $this->Conn->qstr($module)); } else { $object->addFilter('module_filter', '%1$s.Module IN (SELECT Name FROM ' . TABLE_PREFIX . 'Modules WHERE Loaded = 1)'); } if ( !$event->Special && !$this->Application->isDebugMode() ) { // no special $object->addFilter('enabled_filter', '%1$s.Enabled <> ' . STATUS_DISABLED); } } /** * Prepares new kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { parent::OnNew($event); $mapping = Array ('conf' => 'VariableValue', 'site-domain' => 'DefaultEmailRecipients'); if ( isset($mapping[$event->Special]) ) { /** @var kDBItem $object */ $object = $event->getObject(); /** @var kDBList $target_object */ $target_object = $this->Application->recallObject($event->Special); $object->SetDBField('Recipients', $target_object->GetDBField($mapping[$event->Special])); } } /** * Set default headers * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('Headers', $this->Application->ConfigValue('Smtp_DefaultHeaders')); $this->setRequired($event); } /** * Sets status Front-End Only to selected email events * * @param kEvent $event */ function OnFrontOnly($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $config = $event->getUnitConfig(); $sql = 'UPDATE ' . $config->getTableName() . ' SET FrontEndOnly = 1 WHERE ' . $config->getIDField() . ' IN (' . implode(',', $this->StoreSelectedIDs($event)) . ')'; $this->Conn->Query($sql); $this->clearSelectedIDs($event); } /** * Sets selected user to email events selected * * @param kEvent $event * @return void * @access protected */ protected function OnSelectUser(kEvent $event) { if ( $event->Special != 'module' ) { parent::OnSelectUser($event); return; } if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $items_info = $this->Application->GetVar('u'); if ( $items_info ) { list ($user_id, ) = each($items_info); $config = $event->getUnitConfig(); $ids = $this->Application->RecallVar($event->getPrefixSpecial() . '_selected_ids'); $sql = 'UPDATE ' . $config->getTableName() . ' SET ' . $this->Application->RecallVar('dst_field') . ' = ' . $user_id . ' WHERE ' . $config->getIDField() . ' IN (' . $ids . ')'; $this->Conn->Query($sql); } $this->finalizePopup($event); } /** * Saves selected ids to session * * @param kEvent $event */ function OnSaveSelected($event) { $this->StoreSelectedIDs($event); } /** + * [AJAX] Process emails from queue. + * + * @param kEvent $event Event. + * + * @return void + * @deprecated 5.3.0-B1 + * @see EmailQueueEventHandler::OnProcessAjax() + */ + function OnProcessEmailQueue($event) + { + kUtil::deprecatedMethod(__METHOD__, '5.3.0-B1', 'EmailQueueEventHandler::OnProcessAjax'); + + $event->CallSubEvent('email-queue:OnProcessAjax'); + } + + /** * Prefills module dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); $config = $event->getUnitConfig(); $fields = $config->getFields(); $fields['Module']['options'] = $this->_getModules(); if ( $this->Application->findModule('Name', 'Custom') ) { $fields['Module']['default'] = 'Custom'; } $config->setFields($fields); $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->replaceMLCalculatedFields($event); if ( $this->Application->GetVar('regional') ) { $config->setPopulateMlFields(true); } } /** * Returns modules, where e-mail event can be added to * * @return Array * @access protected */ protected function _getModules() { $ret = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { if ( $module_name == 'In-Portal' ) { continue; } $ret[$module_name] = $module_name; } return $ret; } /** * Prepare temp tables and populate it * with items selected in the grid * * @param kEvent $event * @return void * @access protected */ protected function OnEdit(kEvent $event) { parent::OnEdit($event); // use language from grid, instead of primary language used by default $event->SetRedirectParam('m_lang', $this->Application->GetVar('m_lang')); } /** * Fixes default recipient type * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( !$this->Application->isDebugMode(false) ) { if ( $object->GetDBField('AllowChangingRecipient') ) { $object->SetDBField('RecipientType', EmailTemplate::RECIPIENT_TYPE_TO); } else { $object->SetDBField('RecipientType', EmailTemplate::RECIPIENT_TYPE_CC); } } // process replacement tags $records = Array (); $replacement_tags = $object->GetDBField('ReplacementTags'); $replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array (); foreach ($replacement_tags as $tag => $replacement) { $records[] = Array ('Tag' => $tag, 'Replacement' => $replacement); } /** @var MInputHelper $minput_helper */ $minput_helper = $this->Application->recallObject('MInputHelper'); $xml = $minput_helper->prepareMInputXML($records, Array ('Tag', 'Replacement')); $object->SetDBField('ReplacementTagsXML', $xml); $this->setRequired($event); $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $translation_fields = $this->getTranslationFields(); $source_language = $ml_helper->getSourceLanguage($object->GetDBField('TranslateFromLanguage')); foreach ($translation_fields as $translation_field) { $object->SetDBField('Source' . $translation_field, $object->GetDBField('l' . $source_language . '_' . $translation_field)); } } /** * Performs custom validation + keep read-only fields * * @param kEvent $event */ function _itemChanged($event) { /** @var kDBItem $object */ $object = $event->getObject(); if ( !$this->Application->isDebugMode(false) ) { // only allow to enable/disable event while in debug mode $to_restore = Array ('Enabled', 'AllowChangingSender', 'AllowChangingRecipient'); if ( !$object->GetOriginalField('AllowChangingSender') ) { $to_restore = array_merge($to_restore, Array ('CustomSender', 'SenderName', 'SenderAddressType', 'SenderAddress')); } if ( !$object->GetOriginalField('AllowChangingRecipient') ) { $to_restore = array_merge($to_restore, Array ('CustomRecipient' /*, 'Recipients'*/)); } // prevent specific fields from editing foreach ($to_restore as $restore_field) { $original_value = $object->GetOriginalField($restore_field); if ( $object->GetDBField($restore_field) != $original_value ) { $object->SetDBField($restore_field, $original_value); } } } // process replacement tags if ( $object->GetDBField('ReplacementTagsXML') ) { /** @var MInputHelper $minput_helper */ $minput_helper = $this->Application->recallObject('MInputHelper'); $replacement_tags = Array (); $records = $minput_helper->parseMInputXML($object->GetDBField('ReplacementTagsXML')); foreach ($records as $record) { $replacement_tags[trim($record['Tag'])] = trim($record['Replacement']); } $object->SetDBField('ReplacementTags', $replacement_tags ? serialize($replacement_tags) : NULL); } if ( $this->translationChanged($object) ) { $object->SetDBField('LastChanged_date', TIMENOW); $object->SetDBField('LastChanged_time', TIMENOW); } $this->setRequired($event); } /** * Dynamically changes required fields * * @param kEvent $event * @return void * @access protected */ protected function setRequired(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $language_prefix = 'l' . $this->Application->GetVar('m_lang') . '_'; $object->setRequired($language_prefix . 'HtmlBody', !$object->GetField('PlainTextBody')); $object->setRequired($language_prefix . 'PlainTextBody', !$object->GetField('HtmlBody')); } /** * Checks, that at least one of phrase's translations was changed * * @param kDBItem $object * @return bool */ function translationChanged($object) { $translation_fields = $this->getTranslationFields(); $changed_fields = array_keys($object->GetChangedFields()); foreach ($changed_fields as $changed_field) { $changed_field = preg_replace('/^l[\d]+_/', '', $changed_field); if ( in_array($changed_field, $translation_fields) ) { return true; } } return false; } /** * Returns fields, that can be translated * * @return Array * @access protected */ protected function getTranslationFields() { return Array ('Subject', 'HtmlBody', 'PlainTextBody'); } /** * Don't allow to enable/disable events in non-debug mode * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $this->_itemChanged($event); } /** * Don't allow to enable/disable events in non-debug mode * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->_itemChanged($event); } /** * Suggest address based on typed address and selected address type * * @param kEvent $event */ function OnSuggestAddressJSON($event) { $event->status = kEvent::erSTOP; $address_type = $this->Application->GetVar('type'); $address = $this->Application->GetVar('term'); $limit = $this->Application->GetVar('limit'); if ( !$limit ) { $limit = 20; } switch ($address_type) { case EmailTemplate::ADDRESS_TYPE_EMAIL: $field = 'Email'; $table_name = TABLE_PREFIX . 'Users'; break; case EmailTemplate::ADDRESS_TYPE_USER: $field = 'Username'; $table_name = TABLE_PREFIX . 'Users'; break; case EmailTemplate::ADDRESS_TYPE_GROUP: $field = 'Name'; $table_name = TABLE_PREFIX . 'UserGroups'; break; default: $field = $table_name = ''; break; } if ( $field ) { $sql = 'SELECT DISTINCT ' . $field . ' FROM ' . $table_name . ' WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($address . '%') . ' ORDER BY ' . $field . ' ASC LIMIT 0,' . $limit; $data = $this->Conn->GetCol($sql); } else { $data = Array (); } echo json_encode($data); } /** * Does custom validation * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { parent::OnBeforeItemValidate($event); /** @var kDBItem $object */ $object = $event->getObject(); // validate email subject and body for parsing errors $this->_validateEmailTemplate($object); // validate sender and recipient addresses if ( $object->GetDBField('CustomSender') ) { $this->_validateAddress($event, 'Sender'); } $this->_validateAddress($event, 'Recipient'); $this->_validateBindEvent($object); } /** * Validates subject and body fields of Email template * * @param kDBItem $object * @return void * @access protected */ protected function _validateEmailTemplate($object) { /** @var kEmailTemplateHelper $email_template_helper */ $email_template_helper = $this->Application->recallObject('kEmailTemplateHelper'); $email_template_helper->parseField($object, 'Subject'); $email_template_helper->parseField($object, 'HtmlBody'); $email_template_helper->parseField($object, 'PlainTextBody'); } /** * Validates address using given field prefix * * @param kEvent $event * @param string $field_prefix * @return void * @access protected */ protected function _validateAddress($event, $field_prefix) { /** @var kDBItem $object */ $object = $event->getObject(); $address_type = $object->GetDBField($field_prefix . 'AddressType'); $object->setRequired($field_prefix . 'Address', $address_type > 0); $address = $object->GetDBField($field_prefix . 'Address'); if ( !$address ) { // don't validate against empty address return; } switch ($address_type) { case EmailTemplate::ADDRESS_TYPE_EMAIL: if ( !preg_match('/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i', $address) ) { $object->SetError($field_prefix . 'Address', 'invalid_email'); } break; case EmailTemplate::ADDRESS_TYPE_USER: $sql = 'SELECT PortalUserId FROM ' . TABLE_PREFIX . 'Users WHERE Username = ' . $this->Conn->qstr($address); if ( !$this->Conn->GetOne($sql) ) { $object->SetError($field_prefix . 'Address', 'invalid_user'); } break; case EmailTemplate::ADDRESS_TYPE_GROUP: $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroups WHERE Name = ' . $this->Conn->qstr($address); if ( !$this->Conn->GetOne($sql) ) { $object->SetError($field_prefix . 'Address', 'invalid_group'); } break; } } /** * Checks that bind event is specified in correct format and exists * * @param kDBItem $object */ protected function _validateBindEvent($object) { $event_string = $object->GetDBField('BindToSystemEvent'); if ( !$event_string ) { return; } try { $this->Application->eventImplemented(new kEvent($event_string)); } catch (Exception $e) { $object->SetError('BindToSystemEvent', 'invalid_event', '+' . $e->getMessage()); } } /** * Stores ids of selected phrases and redirects to export language step 1 * * @param kEvent $event * @return void * @access protected */ protected function OnExportEmailTemplates(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $this->Application->getUnitConfig('phrases')->setAutoLoad(false); $this->StoreSelectedIDs($event); $this->Application->StoreVar('export_language_ids', $this->Application->GetVar('m_lang')); $event->setRedirectParams( Array ( 'phrases.export_event' => 'OnNew', 'pass' => 'all,phrases.export', 'export_mode' => $event->Prefix, ) ); } /** * Deletes all subscribers to e-mail event after it was deleted * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); /** @var kDBItem $object */ $object = $event->getObject(); $sql = 'SELECT SubscriptionId FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions WHERE EmailTemplateId = ' . $object->GetID(); $ids = $this->Conn->GetCol($sql); if ( !$ids ) { return; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event->MasterEvent)); $temp_handler->DeleteItems('system-event-subscription', '', $ids); } /** * Sends selected e-mail event * * @param kEvent $event * @return void * @access protected */ protected function OnSend(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $ids = $this->StoreSelectedIDs($event); foreach ($ids as $id) { $object->Load($id); if ( $object->GetDBField('Type') == EmailTemplate::TEMPLATE_TYPE_ADMIN ) { $this->Application->emailAdmin($object->GetDBField('TemplateName')); } else { $this->Application->emailUser($object->GetDBField('TemplateName')); } } $this->clearSelectedIDs($event); } /** * Updates translation state for all saved phrases * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeCopyToLive(kEvent $event) { parent::OnBeforeCopyToLive($event); $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->updateTranslationState($event); } }