<?php
/**
* @version	$Id: email_events_event_handler.php 12734 2009-10-20 19:28:11Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class EmailEventsEventsHandler extends kDBEventHandler
	{

		/**
		 * Allows to override standart permission mapping
		 *
		 */
		function mapPermissions()
		{
			parent::mapPermissions();
			$permissions = Array(
				'OnFrontOnly'		=>	Array('self' => 'edit'),
				'OnSaveSelected'	=>	Array('self' => 'view'),

				'OnProcessEmailQueue'	=>	Array('self' => 'add|edit'),
			);
			$this->permMapping = array_merge($this->permMapping, $permissions);
		}

		/**
		 * Changes permission section to one from REQUEST, not from config
		 *
		 * @param kEvent $event
		 */
		function CheckPermission(&$event)
		{
			$module = $this->Application->GetVar('module');

			if (strlen($module) > 0) {
				// checking permission when lising 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->getUnitOption($main_prefix.'.email', 'PermSection');

				$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
		 * @access protected
		 * @see OnListBuild
		 */
		function SetCustomQuery(&$event)
		{
			if ($event->Special == 'module') {
				$object =& $event->getObject();
				$module = $this->Application->GetVar('module');
				$object->addFilter('module_filter', '%1$s.Module = '.$this->Conn->qstr($module));
			}
		}

		/**
		 * Sets status Front-End Only to selected email events
		 *
		 * @param kEvent $event
		 */
		function OnFrontOnly(&$event)
		{
			$ids = implode(',', $this->StoreSelectedIDs($event));

			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
			$sql = 'UPDATE '.$table_name.'
					SET FrontEndOnly = 1
					WHERE EventId IN ('.$ids.')';
			$this->Conn->Query($sql);

			$this->clearSelectedIDs($event);
		}

		/**
		 * Sets selected user to email events selected
		 *
		 * @param kEvent $event
		 */
		function OnSelectUser(&$event)
		{
			if ($event->Special != 'module') {
				parent::OnSelectUser($event);
				return ;
			}

			$items_info = $this->Application->GetVar('u');
			if ($items_info) {
				$user_id = array_shift( array_keys($items_info) );

				$selected_ids = $this->getSelectedIDs($event, true);

				$ids = $this->Application->RecallVar($event->getPrefixSpecial().'_selected_ids');
				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
				$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
				$sql = 'UPDATE '.$table_name.'
						SET '.$this->Application->RecallVar('dst_field').' = '.$user_id.'
						WHERE '.$id_field.' IN ('.$ids.')';
				$this->Conn->Query($sql);
			}

			$this->finalizePopup($event);
		}

		/**
		 * Saves selected ids to session
		 *
		 * @param kEvent $event
		 */
		function OnSaveSelected(&$event)
		{
			$this->StoreSelectedIDs($event);
		}

		/**
		 * Returns sender & recipients ids plus event_id (as parameter by reference)
		 *
		 * @param kEvent $event
		 * @param int $event_id id of email event used
		 *
		 * @return mixed
		 */
		function GetMessageRecipients(&$event, &$event_id)
		{
			$email_event =& $event->getObject( Array('skip_autoload' => true) );
			/* @var $email_event kDBItem */

			// get event parameters by name & type
			$load_keys = Array (
				'Event'	=>	$event->getEventParam('EmailEventName'),
				'Type'	=>	$event->getEventParam('EmailEventType'),
			);
			$email_event->Load($load_keys);

			if (!$email_event->isLoaded()) {
				// event record not found
				return false;
			}

			if ($email_event->GetDBField('Enabled') == STATUS_DISABLED) {
				return false;
			}

			if ($email_event->GetDBField('FrontEndOnly') && $this->Application->isAdmin) {
				return false;
			}

			// 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 = $email_event->GetDBField('FromUserId');

			if ($email_event->GetDBField('Type') == EVENT_TYPE_ADMIN) {
				// For type "Admin" recipient is a user from field FromUserId which means From/To user in Email events list
				if ($to_user_id == -1) {
					$to_user_id = $from_user_id;
				}
				$from_user_id = -1;
			}

			$event_id = $email_event->GetDBField('EventId');

			return Array ($from_user_id, $to_user_id);
		}

		/**
		 * Returns user name, email by id, or ones, that specified in $direct_params
		 *
		 * @param int $user_id
		 * @param string $user_type type of user = {to,from}
		 * @param Array $direct_params
		 * @return Array
		 */
		function GetRecipientInfo($user_id, $user_type, $direct_params = null)
		{
			// load user, because it can be addressed from email template tags
			$user =& $this->Application->recallObject('u.email-'.$user_type, null, Array('skip_autoload' => true));
			/* @var $user UsersItem */

			$email = $name = '';
			$result = $user_id > 0 ? $user->Load( (int)$user_id ) : $user->Clear();
			if ($user->IsLoaded()) {
				 $email = $user->GetDBField('Email');
				 $name = trim($user->GetDBField('FirstName').' '.$user->GetDBField('LastName'));
			}

			if (is_array($direct_params)) {
				if (isset($direct_params[$user_type.'_email'])) {
					$email = $direct_params[$user_type.'_email'];
				}

				if (isset($direct_params[$user_type.'_name'])) {
					$name = $direct_params[$user_type.'_name'];
				}
			}

			if (!$email) {
				// if email is empty, then use admins email
				$email = $this->Application->ConfigValue('Smtp_AdminMailFrom');
			}

			if (!$name) {
				$name = $user_type == 'from' ? strip_tags($this->Application->ConfigValue('Site_Name')) : $email;
			}

			return Array ($email, $name);
		}

		/**
		 * Returns email event message by ID (headers & body in one piece)
		 *
		 * @param int $event_id
		 * @param string $message_type contains message type = {text,html}
		 * @param int $language_id
		 */
		function GetMessageBody($event_id, &$message_type, $language_id = null)
		{
			if (!isset($language_id)) {
				$language_id = $this->Application->GetVar('m_lang');
			}

			$message =& $this->Application->recallObject('emailmessages', null, Array('skip_autoload' => true));
			/* @var $message kDBItem */

			$message->Load( Array('EventId' => $event_id, 'LanguageId' => $language_id) );
			if (!$message->isLoaded()) {
				// event translation on required language not found
				return false;
			}

			$message_type = $message->GetDBField('MessageType');

			// 1. get message body
			$message_body = $message->GetDBField('Template');

			// 2. add footer
			$sql = 'SELECT em.Template
           			FROM '.$message->TableName.' em
           			LEFT JOIN '.TABLE_PREFIX.'Events e ON e.EventId = em.EventId
           			WHERE em.LanguageId = '.$language_id.' AND e.Event = "COMMON.FOOTER"';

			list (, $footer) = explode("\n\n", $this->Conn->GetOne($sql));
			if ($message_type == 'text') {
				$esender =& $this->Application->recallObject('EmailSender');
				/* @var $esender kEmailSendingHelper */

				$footer = $esender->ConvertToText($footer);
			}

			if ($footer) {
				$message_body .= "\r\n".$footer;
			}

			// 3. replace tags if needed
			$default_replacement_tags = Array (
				'<inp:touser _Field="password"' => '<inp2:u_Field name="Password_plain"',
				'<inp:touser _Field="UserName"' => '<inp2:u_Field name="Login"',
				'<inp:touser _Field' => '<inp2:u_Field name',
			);

			$replacement_tags = $message->GetDBField('ReplacementTags');
			$replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array ();
			$replacement_tags = array_merge_recursive2($default_replacement_tags, $replacement_tags);

			foreach ($replacement_tags as $replace_from => $replace_to) {
				$message_body = str_replace($replace_from, $replace_to, $message_body);
			}

			return $message_body;
		}

		/**
		 * Parse message template and return headers (as array) and message body part
		 *
		 * @param string $message
		 * @param Array $direct_params
		 * @return Array
		 */
		function ParseMessageBody($message, $direct_params = null)
		{
			$message_language = $this->_getSendLanguage($direct_params);
			$this->_changeLanguage($message_language);

			$direct_params['message_text'] = isset($direct_params['message']) ? $direct_params['message'] : ''; // parameter alias

			// 1. parse template
			$this->Application->InitParser();
			$parser_params = $this->Application->Parser->Params; // backup parser params

			$this->Application->Parser->SetParams( array_merge_recursive2($parser_params, $direct_params) );

			$message = implode('&|&', explode("\n\n", $message, 2)); // preserves double \n in case when tag is located in subject field
			$message = $this->Application->Parser->Parse($message, 'email_template', 0);

			$this->Application->Parser->SetParams($parser_params); // restore parser params

			// 2. replace line endings, that are send with data submitted via request
			$message = str_replace("\r\n", "\n", $message); // possible case
			$message = str_replace("\r", "\n", $message); // impossible case, but just in case replace this too

			// 3. separate headers from body
			$message_headers = Array ();
			list($headers, $message_body) = explode('&|&', $message, 2);

			$headers = explode("\n", $headers);
			foreach ($headers as $header) {
				$header = explode(':', $header, 2);
				$message_headers[ trim($header[0]) ] = trim($header[1]);
			}

			$this->_changeLanguage();
			return Array ($message_headers, $message_body);
		}

		/**
		 * Raised when email message shoul be sent
		 *
		 * @param kEvent $event
		 */
		function OnEmailEvent(&$event)
		{
			$email_event_name = $event->getEventParam('EmailEventName');
			if (strpos($email_event_name, '_') !== false) {
				trigger_error('<span class="debug_error">Invalid email event name</span> <b>'.$email_event_name.'</b>. Use only <b>UPPERCASE characters</b> and <b>dots</b> as email event names', E_USER_ERROR);
			}

			// additional parameters from kApplication->EmailEvent
			$send_params = $event->getEventParam('DirectSendParams');

			// 1. get information about message sender and recipient
			$recipients = $this->GetMessageRecipients($event, $event_id);
			if ($recipients === false) {
				// if not valid recipients found, then don't send event
				return false;
			}

			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_id, $message_type, $message_language);
			if (!trim($message_template)) {
				trigger_error('Message template is empty', E_USER_WARNING);
				return false;
			}

			list ($message_headers, $message_body) = $this->ParseMessageBody($message_template, $send_params);
			if (!trim($message_body)) {
				trigger_error('Message template is empty after parsing', E_USER_WARNING);
				return false;
			}

			// 3. set headers & send message
			$esender =& $this->Application->recallObject('EmailSender');
			/* @var $esender kEmailSendingHelper */

			$esender->SetFrom($from_email, $from_name);
			$esender->AddTo($to_email, $to_name);

			$message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message';
			$esender->SetSubject($message_subject);

			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') == 1 ? 'ADMIN' : 'USER');
			}

			foreach ($message_headers as $header_name => $header_value) {
				$esender->SetEncodedHeader($header_name, $header_value);
			}

			$esender->CreateTextHtmlPart($message_body, $message_type == 'html');

			$event->status = $esender->Deliver() ? erSUCCESS : erFAIL;

			if ($event->status == erSUCCESS){
				// all keys, that are not used in email sending are written to log record
				$send_keys = Array ('from_email', 'from_name', 'to_email', 'to_name', 'message');
				foreach ($send_keys as $send_key) {
					unset($send_params[$send_key]);
				}

				$fields_hash = Array (
					'fromuser'		=>	$from_name.' ('.$from_email.')',
					'addressto'		=>	$to_name.' ('.$to_email.')',
					'subject'		=>	$message_subject,
					'timestamp'		=>	adodb_mktime(),
					'event'			=>	$email_event_name,
					'EventParams'	=>	serialize($send_params),
				);

				$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'EmailLog');
			}

			$this->Application->removeObject('u.email-from');
			$this->Application->removeObject('u.email-to');
		}

		function _getSendLanguage($send_params)
		{
			if ($send_params && array_key_exists('language_id', $send_params)) {
				return $send_params['language_id'];
			}

			return $this->Application->GetVar('m_lang');
		}

		function _changeLanguage($language_id = null)
		{
			static $prev_language_id = null;

			if (!isset($language_id)) {
				// restore language
				$language_id = $prev_language_id;
			}

			$this->Application->SetVar('m_lang', $language_id);
			$language =& $this->Application->recallObject('lang.current');
			/* @var $lang_object kDBItem */

			$language->Load($language_id);

			$this->Application->Phrases->LanguageId = $language_id;
			$this->Application->Phrases->Phrases = Array();

			$prev_language_id = $language_id; // for restoring it later
		}

		/**
		 * Process emails from queue
		 *
		 * @param kEvent $event
		 * @todo Move to MailingList
		 */
		function OnProcessEmailQueue(&$event)
		{
			$deliver_count = $event->getEventParam('deliver_count');
			if ($deliver_count === false) {
				$deliver_count = $this->Application->ConfigValue('MailingListSendPerStep');
				if ($deliver_count === false) {
					$deliver_count = 10; // 10 emails per script run (if not specified directly)
				}
			}

			$processing_type = $this->Application->GetVar('type');
			if ($processing_type = 'return_progress') {
				$email_queue_progress = $this->Application->RecallVar('email_queue_progress');
				if ($email_queue_progress === false) {
					$emails_sent = 0;
					$sql = 'SELECT COUNT(*)
							FROM ' . TABLE_PREFIX . 'EmailQueue
							WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')';
					$total_emails = $this->Conn->GetOne($sql);
					$this->Application->StoreVar('email_queue_progress', $emails_sent.':'.$total_emails);
				}
				else {
					list ($emails_sent, $total_emails) = explode(':', $email_queue_progress);
				}
			}

			$sql = 'SELECT *
					FROM '.TABLE_PREFIX.'EmailQueue
					WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')
					LIMIT 0,' . $deliver_count;
			$messages = $this->Conn->Query($sql);

			$message_count = count($messages);
			if (!$message_count) {
				// no messages left to send in queue
				if ($processing_type = 'return_progress') {
					$this->Application->RemoveVar('email_queue_progress');
					$this->Application->Redirect($this->Application->GetVar('finish_template'));
				}
				return ;
			}

			$mailing_list_helper =& $this->Application->recallObject('MailingListHelper');
			/* @var $mailing_list_helper MailingListHelper */

			$mailing_list_helper->processQueue($messages);

			if ($processing_type = 'return_progress') {
				$emails_sent += $message_count;
				if ($emails_sent >= $total_emails) {
					$this->Application->RemoveVar('email_queue_progress');
					$this->Application->Redirect($this->Application->GetVar('finish_template'));
				}

				$this->Application->StoreVar('email_queue_progress', $emails_sent.':'.$total_emails);
				$event->status = erSTOP;
				echo ($emails_sent / $total_emails) * 100;
			}
		}
	}