<?php
/**
* @version	$Id: email_events_event_handler.php 15268 2012-04-04 13:55:40Z 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 standard permission mapping
		 *
		 * @return void
		 * @access protected
		 * @see kEventHandler::$permMapping
		 */
		protected function mapPermissions()
		{
			parent::mapPermissions();

			$permissions = Array (
				'OnFrontOnly' => Array ('self' => 'edit'),
				'OnSaveSelected' => Array ('self' => 'view'),
				'OnProcessEmailQueue' => Array ('self' => 'add|edit'),
				'OnExportEmailEvents' => Array ('self' => 'view'),

				'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);
		}

		/**
		 * 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 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
		 * @return void
		 * @access protected
		 * @see kDBEventHandler::OnListBuild()
		 */
		protected function SetCustomQuery(kEvent $event)
		{
			parent::SetCustomQuery($event);

			$object = $event->getObject();
			/* @var $object kDBList */

			if ( $event->Special == 'module' ) {
				$module = $this->Application->GetVar('module');
				$object->addFilter('module_filter', '%1$s.Module = ' . $this->Conn->qstr($module));
			}

			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]) ) {
				$object = $event->getObject();
				/* @var $object kDBItem */

				$target_object = $this->Application->recallObject($event->Special);
				/* @var $target_object kDBList */

				$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);

			$object = $event->getObject();
			/* @var $object kDBItem */

			$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;
			}

			$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
		 * @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 ) {
				$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);
		}

		/**
		 * 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 = kEvent::erSTOP;
				echo ($emails_sent / $total_emails) * 100;
			}
		}

		/**
		 * Prefills module dropdown
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterConfigRead(kEvent $event)
		{
			parent::OnAfterConfigRead($event);

			$options = Array ();

			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
				if ( $module_name == 'In-Portal' ) {
					continue;
				}

				$options[$module_name] = $module_name;
			}

			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
			$fields['Module']['options'] = $options;
			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);

			if ( $this->Application->GetVar('regional') ) {
				$this->Application->setUnitOption($event->Prefix, 'PopulateMlFields', true);
			}
		}

		/**
		 * 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);

			$object = $event->getObject();
			/* @var $object kDBItem */

			if ( !$this->Application->isDebugMode(false) ) {
				if ( $object->GetDBField('AllowChangingRecipient') ) {
					$object->SetDBField('RecipientType', EmailEvent::RECIPIENT_TYPE_TO);
				}
				else {
					$object->SetDBField('RecipientType', EmailEvent::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);

			$this->setRequired($event);
		}

		/**
		 * Performs custom validation + keep read-only fields
		 *
		 * @param kEvent $event
		 */
		function _itemChanged($event)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			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') ) {
				$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);
			}

			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)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			$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)
		{
			$changed_fields = array_keys($object->GetChangedFields());
			$translation_fields = Array ('Subject', 'HtmlBody', 'PlainTextBody');

			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;
		}

		/**
		 * 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 OnSuggestAddress($event)
		{
			$event->status = kEvent::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 EmailEvent::ADDRESS_TYPE_EMAIL:
					$field = 'Email';
					$table_name = TABLE_PREFIX . 'Users';
					break;

				case EmailEvent::ADDRESS_TYPE_USER:
					$field = 'Username';
					$table_name = TABLE_PREFIX . 'Users';
					break;

				case EmailEvent::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 ();
			}

			$this->Application->XMLHeader();

			echo '<suggestions>';

			foreach ($data as $item) {
				echo '<item>' . htmlspecialchars($item) . '</item>';
			}

			echo '</suggestions>';
		}

		/**
		 * Does custom validation
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemValidate(kEvent $event)
		{
			parent::OnBeforeItemValidate($event);

			$object = $event->getObject();
			/* @var $object kDBItem */

			// 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)
		{
			$email_message_helper = $this->Application->recallObject('kEmailMessageHelper');
			/* @var $email_message_helper kEmailMessageHelper */

			$email_message_helper->parseField($object, 'Subject');
			$email_message_helper->parseField($object, 'HtmlBody');
			$email_message_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)
		{
			$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 EmailEvent::ADDRESS_TYPE_EMAIL:
					if ( !preg_match('/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i', $address) ) {
						$object->SetError($field_prefix . 'Address', 'invalid_email');
					}
					break;

				case EmailEvent::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 EmailEvent::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 OnExportEmailEvents(kEvent $event)
		{
			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
				$event->status = kEvent::erFAIL;
				return;
			}

			$this->Application->setUnitOption('phrases', 'AutoLoad', 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);

			$object = $event->getObject();
			/* @var $object kDBItem */

			$sql = 'SELECT SubscriptionId
					FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
					WHERE EmailEventId = ' . $object->GetID();
			$ids = $this->Conn->GetCol($sql);

			if ( !$ids ) {
				return;
			}

			$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
			/* @var $temp_handler kTempTablesHandler */

			$temp_handler->DeleteItems('system-event-subscription', '', $ids);
		}
	}