Index: branches/5.1.x/core/units/email_events/email_events_event_handler.php =================================================================== diff -u -N -r13151 -r13635 --- branches/5.1.x/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 13151) +++ branches/5.1.x/core/units/email_events/email_events_event_handler.php (.../email_events_event_handler.php) (revision 13635) @@ -1,6 +1,6 @@ Array('self' => 'edit'), - 'OnSaveSelected' => Array('self' => 'view'), - 'OnProcessEmailQueue' => Array('self' => 'add|edit'), + $permissions = Array ( + 'OnFrontOnly' => Array ('self' => 'edit'), + 'OnSaveSelected' => Array ('self' => 'view'), + 'OnProcessEmailQueue' => Array ('self' => 'add|edit'), + + 'OnSuggestAddress' => 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'), ); + $this->permMapping = array_merge($this->permMapping, $permissions); } @@ -201,76 +212,259 @@ } /** - * Returns sender & recipients ids plus event_id (as parameter by reference) + * Processes email sender * * @param kEvent $event - * - * @return mixed + * @param Array $direct_params */ - function GetMessageRecipients(&$event) + function _processSender(&$event, $direct_params = Array ()) { + $this->Application->removeObject('u.email-from'); + $object =& $this->_getEmailEvent($event); /* @var $object kDBItem */ - // initial values - $to_user_id = $event->getEventParam('EmailEventToUserId'); - if ( !is_numeric($to_user_id) ) { - $to_user_id = -1; // when not specified, then send to "root" - } - $from_user_id = $object->GetDBField('FromUserId'); + $email = $name = ''; - if ($object->GetDBField('Type') == EVENT_TYPE_ADMIN) { - // For type "Admin" recipient is a user from field FromUserId which means From/To user in Email events list - if ($to_user_id == -1) { - $to_user_id = $from_user_id; + // set defaults from event + if ($object->GetDBField('CustomSender')) { + $address = $object->GetDBField('SenderAddress'); + $address_type = $object->GetDBField('SenderAddressType'); + + switch ($address_type) { + case ADDRESS_TYPE_EMAIL: + $email = $address; + break; + + case ADDRESS_TYPE_USER: + $sql = 'SELECT FirstName, LastName, Email, PortalUserId + FROM ' . TABLE_PREFIX . 'PortalUser + WHERE Login = ' . $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; } - $from_user_id = -1; + + if ($object->GetDBField('SenderName')) { + $name = $object->GetDBField('SenderName'); + } } - return Array ($from_user_id, $to_user_id); + // 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('Smtp_AdminMailFrom'); + } + + 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); } /** - * Returns user name, email by id, or ones, that specified in $direct_params + * Processes email recipients * - * @param int $user_id - * @param string $user_type type of user = {to,from} + * @param kEvent $event * @param Array $direct_params - * @return Array */ - function GetRecipientInfo($user_id, $user_type, $direct_params = null) + function _processRecipients(&$event, $direct_params = Array ()) { - // load user, because it can be addressed from email template tags - $user =& $this->Application->recallObject('u.email-'.$user_type, null, Array('skip_autoload' => true)); - /* @var $user UsersItem */ + $this->Application->removeObject('u.email-to'); - $email = $name = ''; - $result = $user_id > 0 ? $user->Load( (int)$user_id ) : $user->Clear(); - if ($user->IsLoaded()) { - $email = $user->GetDBField('Email'); - $name = trim($user->GetDBField('FirstName').' '.$user->GetDBField('LastName')); - } + $object =& $this->_getEmailEvent($event); + /* @var $object kDBItem */ - if (is_array($direct_params)) { - if (isset($direct_params[$user_type.'_email'])) { - $email = $direct_params[$user_type.'_email']; + $to_email = $to_name = ''; + $all_recipients = Array (); + $recipients_xml = $object->GetDBField('Recipients'); + + if ($recipients_xml) { + $minput_helper =& $this->Application->recallObject('MInputHelper'); + /* @var $minput_helper MInputHelper */ + + // group recipients by type + $records = $minput_helper->parseMInputXML($recipients_xml); + + foreach ($records as $record) { + $recipient_type = $record['RecipientType']; + + if (!array_key_exists($recipient_type, $all_recipients)) { + $all_recipients[$recipient_type] = Array (); + } + + $all_recipients[$recipient_type][] = $record; } + } - if (isset($direct_params[$user_type.'_name'])) { - $name = $direct_params[$user_type.'_name']; + if (!array_key_exists(RECIPIENT_TYPE_TO, $all_recipients)) { + $all_recipients[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[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) { + $sql = 'SELECT FirstName, LastName, Email + FROM ' . TABLE_PREFIX . 'PortalUser + WHERE PortalUserId = ' . $to_user_id; + $user_info = $this->Conn->GetRow($sql); + + if ($user_info) { + $add_recipient = Array ( + 'RecipientAddressType' => ADDRESS_TYPE_EMAIL, + 'RecipientAddress' => $user_info['Email'], + 'RecipientName' => trim($user_info['FirstName'] . ' ' . $user_info['LastName']), + ); + + array_unshift($all_recipients[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); } } - if (!$email) { - // if email is empty, then use admins email - $email = $this->Application->ConfigValue('Smtp_AdminMailFrom'); + // 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'] = ADDRESS_TYPE_EMAIL; + $add_recipient['RecipientAddress'] = $direct_params['to_email']; } - if (!$name) { - $name = $user_type == 'from' ? strip_tags($this->Application->ConfigValue('Site_Name')) : $email; + if (array_key_exists('to_name', $direct_params)) { + $add_recipient['RecipientName'] = $direct_params['to_name']; } - return Array ($email, $name); + if ($add_recipient) { + array_unshift($all_recipients[RECIPIENT_TYPE_TO], $add_recipient); + } + + if (($object->GetDBField('Type') == EVENT_TYPE_ADMIN) && !$all_recipients[RECIPIENT_TYPE_TO]) { + // admin email event without direct recipient -> send to admin + $all_recipients[RECIPIENT_TYPE_TO][] = Array ( + 'RecipientName' => $this->Application->ConfigValue('Smtp_AdminMailFrom'), + 'RecipientAddressType' => ADDRESS_TYPE_EMAIL, + 'RecipientAddress' => $this->Application->ConfigValue('Smtp_AdminMailFrom'), + ); + } + + $esender =& $this->Application->recallObject('EmailSender'); + /* @var $esender kEmailSendingHelper */ + + $header_mapping = Array ( + RECIPIENT_TYPE_TO => 'To', + RECIPIENT_TYPE_CC => 'Cc', + RECIPIENT_TYPE_BCC => 'Bcc', + ); + + $default_email = $this->Application->ConfigValue('Smtp_AdminMailFrom'); + + foreach ($all_recipients as $recipient_type => $recipients) { + // add recipients to email + $pairs = Array (); + + foreach ($recipients as $recipient) { + $address = $recipient['RecipientAddress']; + $address_type = $recipient['RecipientAddressType']; + $repipient_name = $recipient['RecipientName']; + + switch ($address_type) { + case ADDRESS_TYPE_EMAIL: + $pairs[] = Array ('email' => $address, 'name' => $repipient_name); + break; + + case ADDRESS_TYPE_USER: + $sql = 'SELECT FirstName, LastName, Email + FROM ' . TABLE_PREFIX . 'PortalUser + WHERE Login = ' . $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 : $repipient_name, + ); + } + break; + + case ADDRESS_TYPE_GROUP: + $sql = 'SELECT u.FirstName, u.LastName, u.Email + FROM ' . TABLE_PREFIX . 'PortalGroup g + JOIN ' . TABLE_PREFIX . 'UserGroup ug ON ug.GroupId = g.GroupId + JOIN ' . TABLE_PREFIX . 'PortalUser u ON u.PortalUserId = ug.PortalUserId + WHERE g.Name = ' . $this->Conn->qstr($address); + $users = $this->Conn->Query($sql); + + foreach ($users as $user) { + $name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']); + + $pairs[] = Array ( + 'email' => $user_info['Email'], + 'name' => $name ? $name : $repipient_name, + ); + } + break; + } + } + + if (!$pairs) { + continue; + } + + if ($recipient_type == 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); } /** @@ -279,7 +473,7 @@ * @param kEvent $event * @param int $language_id */ - function GetMessageBody(&$event, $language_id = null) + function _getMessageBody(&$event, $language_id = null) { if (!isset($language_id)) { $language_id = $this->Application->GetVar('m_lang'); @@ -399,7 +593,7 @@ * @param Array $direct_params * @return Array */ - function ParseMessageBody($message, $direct_params = null) + function ParseMessageBody($message, $direct_params = Array ()) { $message_language = $this->_getSendLanguage($direct_params); $this->_changeLanguage($message_language); @@ -463,15 +657,12 @@ $send_params = $event->getEventParam('DirectSendParams'); // 1. get information about message sender and recipient - $recipients = $this->GetMessageRecipients($event); + list ($from_email, $from_name) = $this->_processSender($event, $send_params); + list ($to_email, $to_name) = $this->_processRecipients($event, $send_params); - list ($from_id, $to_id) = $recipients; - list ($from_email, $from_name) = $this->GetRecipientInfo($from_id, 'from', $send_params); - list ($to_email, $to_name) = $this->GetRecipientInfo($to_id, 'to', $send_params); - // 2. prepare message to be sent $message_language = $this->_getSendLanguage($send_params); - $message_template = $this->GetMessageBody($event, $message_language); + $message_template = $this->_getMessageBody($event, $message_language); if (!trim($message_template)) { trigger_error('Message template is empty', E_USER_WARNING); return false; @@ -487,15 +678,12 @@ $esender =& $this->Application->recallObject('EmailSender'); /* @var $esender kEmailSendingHelper */ - $esender->SetFrom($from_email, $from_name); - $esender->AddTo($to_email, $to_name); - $message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message'; $esender->SetSubject($message_subject); if ($this->Application->isDebugMode()) { // set special header with event name, so it will be easier to determite what's actually was received - $message_headers['X-Event-Name'] = $email_event_name . ' - ' . ($event->getEventParam('EmailEventType') == EVENT_TYPE_ADMIN ? 'ADMIN' : 'USER'); + $message_headers['X-Event-Name'] = $email_event_name . ' - ' . ($object->GetDBField('Type') == EVENT_TYPE_ADMIN ? 'ADMIN' : 'USER'); } foreach ($message_headers as $header_name => $header_value) { @@ -524,14 +712,11 @@ $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'EmailLog'); } - - $this->Application->removeObject('u.email-from'); - $this->Application->removeObject('u.email-to'); } function _getSendLanguage($send_params) { - if ($send_params && array_key_exists('language_id', $send_params)) { + if (array_key_exists('language_id', $send_params)) { return $send_params['language_id']; } @@ -634,10 +819,10 @@ { parent::OnAfterConfigRead($event); - $options = Array ('Core:Users' => 'Core - Users', 'Core:Category' => 'Core - Categories'); + $options = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { - if (($module_name == 'In-Portal') || ($module_name == 'Core')) { + if ($module_name == 'In-Portal') { continue; } @@ -666,4 +851,221 @@ // 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 + */ + function OnAfterItemLoad(&$event) + { + parent::OnAfterItemLoad($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if (!$this->Application->isDebugMode(false)) { + if ($object->GetDBField('AllowChangingRecipient')) { + $object->SetDBField('RecipientType', RECIPIENT_TYPE_TO); + } + else { + $object->SetDBField('RecipientType', 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); + } + + $minput_helper =& $this->Application->recallObject('MInputHelper'); + /* @var $minput_helper MInputHelper */ + + $xml = $minput_helper->prepareMInputXML($records, Array ('Tag', 'Replacement')); + $object->SetDBField('ReplacementTagsXML', $xml); + } + + /** + * Performs custom validation + keep read-only fields + * + * @param kEvent $event + */ + function _itemChanged(&$event) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if ($object->GetDBField('CustomSender')) { + $this->_validateAddress($event, 'Sender'); + } + + $this->_validateAddress($event, 'Recipient'); + + 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 + $minput_helper =& $this->Application->recallObject('MInputHelper'); + /* @var $minput_helper 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); + } + + /** + * Validates address using given field prefix + * + * @param kEvent $event + * @param string $field_prefix + */ + function _validateAddress(&$event, $field_prefix) + { + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $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 ADDRESS_TYPE_EMAIL: + if (!preg_match('/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i', $address)) { + $object->SetError($field_prefix . 'Address', 'invalid_email'); + } + break; + + case ADDRESS_TYPE_USER: + $sql = 'SELECT PortalUserId + FROM ' . TABLE_PREFIX . 'PortalUser + WHERE Login = ' . $this->Conn->qstr($address); + if (!$this->Conn->GetOne($sql)) { + $object->SetError($field_prefix . 'Address', 'invalid_user'); + } + break; + + case ADDRESS_TYPE_GROUP: + $sql = 'SELECT GroupId + FROM ' . TABLE_PREFIX . 'PortalGroup + WHERE Name = ' . $this->Conn->qstr($address); + if (!$this->Conn->GetOne($sql)) { + $object->SetError($field_prefix . 'Address', 'invalid_group'); + } + break; + } + } + + /** + * Don't allow to enable/disable events in non-debug mode + * + * @param kEvent $event + */ + function OnBeforeItemCreate(&$event) + { + parent::OnBeforeItemCreate($event); + + $this->_itemChanged($event); + } + + /** + * Don't allow to enable/disable events in non-debug mode + * + * @param kEvent $event + */ + function OnBeforeItemUpdate(&$event) + { + parent::OnBeforeItemUpdate($event); + + $this->_itemChanged($event); + } + + /** + * Suggest address based on typed address and selected address type + * + * @param kEvent $event + */ + function OnSuggestAddress(&$event) + { + $event->status = erSTOP; + + $address_type = $this->Application->GetVar('type'); + $address = $this->Application->GetVar('value'); + $limit = $this->Application->GetVar('limit'); + + if (!$limit) { + $limit = 20; + } + + switch ($address_type) { + case ADDRESS_TYPE_EMAIL: + $field = 'Email'; + $table_name = TABLE_PREFIX . 'PortalUser'; + break; + + case ADDRESS_TYPE_USER: + $field = 'Login'; + $table_name = TABLE_PREFIX . 'PortalUser'; + break; + + case ADDRESS_TYPE_GROUP: + $field = 'Name'; + $table_name = TABLE_PREFIX . 'PortalGroup'; + break; + } + + if (isset($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 (); + } + + $this->Application->XMLHeader(); + + echo ''; + + foreach ($data as $item) { + echo '' . htmlspecialchars($item) . ''; + } + + echo ''; + } } \ No newline at end of file