<?php
/**
* @version	$Id: submission_log_eh.php 15145 2012-03-04 09:04:08Z 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 SubmissionLogEventHandler extends kDBEventHandler {

		/**
		 * Allows to override standard permission mapping
		 *
		 * @return void
		 * @access protected
		 * @see kEventHandler::$permMapping
		 */
		protected function mapPermissions()
		{
			parent::mapPermissions();

			$permissions = Array (
				'OnResendReply' => Array ('subitem' => 'add|edit'),
				'OnSaveDraft' => Array ('subitem' => 'add|edit'),
				'OnUseDraft' => Array ('subitem' => 'add|edit'),
				'OnDeleteDraft' => Array ('subitem' => 'add|edit'),

				'OnProcessBounceMail' => Array ('subitem' => true),
			);

			$this->permMapping = array_merge($this->permMapping, $permissions);
		}

		/**
		 * Checks user permission to execute given $event
		 *
		 * @param kEvent $event
		 * @return bool
		 * @access public
		 */
		public function CheckPermission(kEvent $event)
		{
			$section = $event->getSection();
			$form_id = $this->Application->GetVar('form_id');

			if ( $form_id ) {
				// copy form_id to env to be passed info upload links
				$this->Application->SetVar($event->getPrefixSpecial() . '_form_id', $form_id);
			}
			else {
				$form_id = $this->Application->GetVar($event->getPrefixSpecial() . '_form_id');
			}

			$event->setEventParam('PermSection', $section . ':' . $form_id);

			return parent::CheckPermission($event);
		}

		/**
		 * Prepares new kDBItem object
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnNew(kEvent $event)
		{
			parent::OnNew($event);

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

			$form_submission = $this->Application->recallObject('formsubs');
			/* @var $form_submission kDBItem */

			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
			/* @var $form_submission_helper FormSubmissionHelper */

			$form =& $form_submission_helper->getForm($form_submission);
			/* @var $form kDBItem */

			$from_email = $form->GetDBField('ReplyFromEmail');
			$to_email = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);

			if ( $this->Application->GetVar('client_mode') ) {
				// debug code for sending email from client
				$object->SetDBField('FromEmail', $to_email);
				$object->SetDBField('ToEmail', $from_email);

			}
			else {
				$object->SetDBField('FromEmail', $from_email);
				$object->SetDBField('ToEmail', $to_email);
			}

			$object->SetDBField('Cc', $form->GetDBField('ReplyCc'));
			$object->SetDBField('Bcc', $form->GetDBField('ReplyBcc'));

			$ids = $this->StoreSelectedIDs($event);
			if ( $ids ) {
				$org_message = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
				/* @var $org_message kDBItem */

				$org_message->Load(array_shift($ids));
				// client could reply from different email, so compare to admin email!
				if ( $org_message->GetDBField('ToEmail') == $from_email ) {
					// can reply only to client email, not own :)

					// transform subject
					$message_subject = $org_message->GetDBField('Subject');

					if ( $message_subject ) {
						$object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re'));
					}

					// add signature
					$message_body = $form->GetDBField('ReplyMessageSignature');

					if ( $org_message->GetDBField('Message') ) {
						// add replied marks
						$message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $org_message->GetDBField('Message'));
					}

					$object->SetDBField('ToEmail', $org_message->GetDBField('FromEmail')); // user client's email from reply
					$object->SetDBField('Message', $message_body);
					$object->SetDBField('ReplyTo', $org_message->GetID());
				}
			}
			else {
				$sql = 'SELECT COUNT(*)
						FROM ' . $object->TableName . '
						WHERE FormSubmissionId = ' . $form_submission->GetID();
				$replies_found = $this->Conn->GetOne($sql);

				if ( !$replies_found ) {
					// 1st message from admin -> quote subject & text from feedback
					$message_subject = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT, true);

					if ( $message_subject ) {
						$object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re'));
					}

					// add signature
					$message_body = $form->GetDBField('ReplyMessageSignature');

					// add replied marks
					$original_message_body = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_BODY);

					if ( $original_message_body ) {
						$message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $original_message_body);
					}

					$object->SetDBField('Message', $message_body);
				}
			}

			$this->clearSelectedIDs($event);
		}

		/**
		 * Parses $search string in subject and reformats it
		 * Used for replying and forwarding
		 *
		 * @param string $subject
		 * @param string $search
		 * @return string
		 */
		function _transformSubject($subject, $search = 'Re')
		{
			$regex = '/'.$search.'(\[([\d]+)\]){0,1}:/i';
			preg_match_all($regex, $subject, $regs);

			if ($regs[2]) {
				$reply_count = 0; // reply count without numbers (equals to "re[1]")
				$max_reply_number = 0; // maximal reply number
				sort($regs[2], SORT_NUMERIC); // sort ascending (non-numeric replies first)
				foreach ($regs[2] as $match) {
					if (!$match) {
						// found "re:"
						$reply_count++;
					}
					elseif ($match > $max_reply) {
						// found "re:[number]"
						$max_reply_number = $match;
					}
				}

				return $search.'['.($reply_count + $max_reply_number + 1).']: '.trim(preg_replace($regex, '', $subject));
			}

			return $search.': '.$subject;
		}

		/**
		 * Resends reply, that was not sent last time
		 *
		 * @param kEvent $event
		 */
		function OnResendReply($event)
		{
			$ids = $this->StoreSelectedIDs($event);

			if (!$ids) {
				return ;
			}

			$object = $event->getObject( Array('skip_autoload' => true) );
			/* @var $object kDBItem */

			$sql = 'SELECT f.ReplyFromEmail, sl.' . $object->IDField . '
					FROM ' . $object->TableName . ' sl
					JOIN ' . $this->Application->getUnitOption('formsubs', 'TableName') . ' fs ON fs.FormSubmissionId = sl.FormSubmissionId
					JOIN ' . $this->Application->getUnitOption('form', 'TableName') . ' f ON f.FormId = fs.FormId
					WHERE sl.' . $object->IDField . ' IN (' . implode(',', $ids) . ')';
			$reply_emails = $this->Conn->GetCol($sql, $object->IDField);

			foreach ($ids as $id) {
				$object->Load($id);

				// allow to send messages, that were successfully sended before :(
				if (($object->GetDBField('ToEmail') != $reply_emails[$id]) && ($object->GetDBField('SentStatus') != SUBMISSION_LOG_SENT)) {
					$object->SetOriginalField('SentStatus', 0); // reset sent status to update sent date automatically

					$this->_sendEmail($object); // resend email here
				}
			}

			$this->clearSelectedIDs($event);

			if (!$this->Application->GetVar('from_list')) {
				$event->SetRedirectParam('opener', 'u');
			}
		}

		/**
		 * Updates last operation dates for log record
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemCreate(kEvent $event)
		{
			parent::OnBeforeItemCreate($event);

			$this->_validateRecipients($event);
			$this->_updateStatusDates($event);
		}

		/**
		 * Updates last operation dates for log record
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemUpdate(kEvent $event)
		{
			parent::OnBeforeItemUpdate($event);

			$this->_validateRecipients($event);
			$this->_updateStatusDates($event);
		}

		/**
		 * Validates email recipients
		 *
		 * @param kEvent $event
		 */
		function _validateRecipients($event)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			$esender = $this->Application->recallObject('EmailSender');
			/* @var $esender kEmailSendingHelper */

			$cc = $object->GetDBField('Cc');

			if ($cc && ($esender->GetRecipients($cc) === false)) {
				$object->SetError('Cc', 'invalid_format');
			}

			$bcc = $object->GetDBField('Bcc');

			if ($bcc && ($esender->GetRecipients($bcc) === false)) {
				$object->SetError('Bcc', 'invalid_format');
			}
		}

		/**
		 * Generates verification code and sets it inside sent message
		 *
		 * @param kDBItem $object
		 * @return string
		 */
		function _generateVerificationCode(&$object)
		{
			$code = Array (
				$object->GetDBField('FromEmail'),
				$object->GetDBField('ToEmail'),
				$object->GetID(),
				microtime(true)
			);

			$object->SetDBField('VerifyCode', md5( implode('-', $code) ));
		}

		/**
		 * Sends email based on fields from given submission-log record
		 *
		 * @param kDBItem $object
		 */
		function _sendEmail(&$object)
		{
			if ($this->Application->GetVar('client_mode')) {
				return ;
			}

			if (!$object->GetDBField('VerifyCode')) {
				$this->_generateVerificationCode($object);
			}

			$form_submission =& $this->_getFormSubmission($object);

			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
			/* @var $form_submission_helper FormSubmissionHelper */

			$form =& $form_submission_helper->getForm($form_submission);

			$send_params = Array (
				'from_name' => $form->GetDBField('ReplyFromName'),
				'from_email' => $object->GetDBField('FromEmail'),

				'to_email' => $object->GetDBField('ToEmail'),

				'subject' => $object->GetDBField('Subject'),
				'message' => $object->GetDBField('Message'),
			);

			$to_name = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_NAME);

			if ($to_name) {
				$send_params['to_name'] = $to_name;
			}

			$esender = $this->Application->recallObject('EmailSender');
			/* @var $esender kEmailSendingHelper */

			$esender->SetReturnPath( $form->GetDBField('BounceEmail') );

			if ($object->GetDBField('Cc')) {
				$recipients = $esender->GetRecipients( $object->GetDBField('Cc') );

				foreach ($recipients as $recipient_info) {
					$esender->AddCc($recipient_info['Email'], $recipient_info['Name']);
				}
			}

			if ($object->GetDBField('Bcc')) {
				$recipients = $esender->GetRecipients( $object->GetDBField('Bcc') );

				foreach ($recipients as $recipient_info) {
					$esender->AddBcc($recipient_info['Email'], $recipient_info['Name']);
				}
			}

			if ($object->GetDBField('Attachment')) {
				$attachments = explode('|', $object->GetField('Attachment', 'file_paths'));

				foreach ($attachments as $attachment) {
					$esender->AddAttachment($attachment);
				}
			}

			$this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.TO.USER', null, $send_params);

			// mark as sent after sending is finished
			$object->SetDBField('SentStatus', SUBMISSION_LOG_SENT);

			// reset bounce status before (re-)sending
			$object->SetDBField('BounceInfo', NULL);
			$object->SetDBField('BounceDate_date', NULL);
			$object->SetDBField('BounceDate_time', NULL);

			if ($object->GetDBField('DraftId')) {
				$temp_handler = $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler');
				/* @var $temp_handler kTempTablesHandler */

				$temp_handler->DeleteItems('draft', '', Array ($object->GetDBField('DraftId')));
				$object->SetDBField('DraftId', 0);
			}

			$object->Update();
		}

		/**
		 * Sends new email after log record was created
		 * Updates last update time for submission
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemCreate(kEvent $event)
		{
			parent::OnAfterItemCreate($event);

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

			$this->_sendEmail($object); // send email

			$this->_updateSubmission($event);

			$reply_to = $object->GetDBField('ReplyTo');
			if ( !$reply_to ) {
				$reply_to = $this->_getLastMessageId($event, !$this->Application->GetVar('client_mode'));
			}

			if ( $reply_to ) {
				// this is reply to other message -> mark it as replied
				$org_message = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
				/* @var $org_message kDBItem */

				$org_message->Load($reply_to);
				$org_message->SetDBField('ReplyStatus', SUBMISSION_LOG_REPLIED);
				$org_message->Update();
			}

			if ( $this->Application->GetVar('client_mode') ) {
				// new reply from client received -> send notification about it
				$this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.FROM.USER');
			}
		}

		/**
		 * Returns last message id (client OR admin)
		 *
		 * @param kEvent $event
		 * @param bool $from_client
		 * @return int
		 */
		function _getLastMessageId($event, $from_client = false)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			$form_submission =& $this->_getFormSubmission($object);

			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
			/* @var $form_submission_helper FormSubmissionHelper */

			$form =& $form_submission_helper->getForm($form_submission);
			$reply_email = $form->GetDBField('ReplyFromEmail');

			$sql = 'SELECT MAX(' . $object->IDField . ')
					FROM ' . $object->TableName . '
					WHERE (FormSubmissionId = ' . $form_submission->GetID() . ') AND (ToEmail' . ($from_client ? ' = ' : ' <> ') . $this->Conn->qstr($reply_email) . ')';
			return $this->Conn->GetOne($sql);
		}

		/**
		 * Updates last update time for submission
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemUpdate(kEvent $event)
		{
			parent::OnAfterItemUpdate($event);

			$this->_updateSubmission($event);

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

			// send out email event to admin for bouncing
			$sent_status = $object->GetDBField('SentStatus');

			if ( $object->GetOriginalField('SentStatus') != $sent_status && $sent_status == SUBMISSION_LOG_BOUNCE ) {
				$this->Application->EmailEventAdmin('FORM.SUBMISSION.REPLY.FROM.USER.BOUNCED');
			}
		}

		/**
		 * Sets last sent/reply dates based on field changes in log record
		 *
		 * @param kEvent $event
		 */
		function _updateStatusDates($event)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			$now = adodb_mktime();

			$sent_status = $object->GetDBField('SentStatus');
			if (($event->Special != 'merge') && ($sent_status == SUBMISSION_LOG_SENT) && ($sent_status != $object->GetOriginalField('SentStatus'))) {
				// sent status was set
				$object->SetDBField('SentOn_date', $now);
				$object->SetDBField('SentOn_time', $now);
			}

			$reply_status = $object->GetDBField('ReplyStatus');
			if (($reply_status == SUBMISSION_LOG_REPLIED) && ($reply_status != $object->GetOriginalField('ReplyStatus'))) {
				// sent status was set
				$object->SetDBField('RepliedOn_date', $now);
				$object->SetDBField('RepliedOn_time', $now);
			}
		}

		/**
		 * Returns form submission by given event of submission log
		 *
		 * @param kDBItem $object
		 * @return kDBItem
		 */
		function &_getFormSubmission(&$object)
		{
			$submission_id = $object->GetDBField('FormSubmissionId');

			$form_submission = $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true));
			/* @var $form_submission kDBItem */

			if ($form_submission->isLoaded() && ($form_submission->GetID() == $submission_id)) {
				// already loaded AND has needed id
				return $form_submission;
			}

			$form_submission->Load($submission_id);

			return $form_submission;
		}

		/**
		 * Sets last updated field for form submission
		 *
		 * @param kEvent $event
		 */
		function _updateSubmission($event)
		{
			$object = $event->getObject();
			/* @var $object kDBItem */

			$form_submission =& $this->_getFormSubmission($object);

			// 1. set last updated
			$last_updated = max ($object->GetDBField('SentOn'), $object->GetDBField('RepliedOn'));

			if ($form_submission->GetDBField('LastUpdatedOn') < $last_updated) {
				// don't set smaller last update, that currenly set
				$form_submission->SetDBField('LastUpdatedOn_date', $last_updated);
				$form_submission->SetDBField('LastUpdatedOn_time', $last_updated);
			}

			// 2. update submission status
			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
			/* @var $form_submission_helper FormSubmissionHelper */

			$form =& $form_submission_helper->getForm($form_submission);
			$client_responce = $form->GetDBField('ReplyFromEmail') == $object->GetDBField('ToEmail');
			$replied = $object->GetDBField('ReplyStatus') == SUBMISSION_LOG_REPLIED;

			if (!$client_responce && !$replied) {
				 // admin sends new email to client
				 $form_submission->SetDBField('LogStatus', SUBMISSION_REPLIED);
			}
			elseif ($client_responce) {
				// client email becomes replied OR receiving new unreplied email from client
				$form_submission->SetDBField('LogStatus', $replied ?  SUBMISSION_REPLIED : SUBMISSION_NEW_EMAIL);
			}

			if ($object->GetDBField('SentStatus') == SUBMISSION_LOG_BOUNCE) {
				// propagate bounce status from reply
				$form_submission->SetDBField('LogStatus', SUBMISSION_BOUNCE);
			}

			$form_submission->Update();
		}

		/**
		 * Saves current unsent message as draft
		 *
		 * @param kEvent $event
		 */
		function OnSaveDraft($event)
		{
			$object = $event->getObject( Array('skip_autoload' => true) );
			/* @var $object kDBItem */

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

			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
			if ($items_info) {
				foreach ($items_info as $id => $field_values) {
					$object->setID($id);
	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));

	 				$load_keys = Array (
	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
	 					'CreatedById' => $this->Application->RecallVar('user_id'),
	 				);

	 				// get existing draft for given submission and user
	 				$draft->Load($load_keys);

	 				$draft->SetDBField('Message', $object->GetDBField('Message'));

	 				if ($draft->isLoaded()) {
	 					$draft->Update();
	 				}
	 				else {
	 					$draft->SetDBFieldsFromHash($load_keys);
	 					$draft->Create();
	 				}
				}
			}

			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
			$event->SetRedirectParam('opener', 'u');
		}

		/**
		 * Uses found draft instead of submission reply body
		 *
		 * @param kEvent $event
		 */
		function OnUseDraft($event)
		{
			$object = $event->getObject( Array('skip_autoload' => true) );
			/* @var $object kDBItem */

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

			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
			if ($items_info) {
				foreach ($items_info as $id => $field_values) {
					$object->setID($id);
	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));

	 				$load_keys = Array (
	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
	 					'CreatedById' => $this->Application->RecallVar('user_id'),
	 				);

	 				// get existing draft for given submission and user
	 				$draft->Load($load_keys);
					if ($draft->isLoaded()) {
						$object->SetDBField('Message', $draft->GetDBField('Message'));
						$object->SetDBField('DraftId', $draft->GetID());
					}
				}
			}

			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
			$event->redirect = false;
		}

		/**
		 * Deletes draft, that matches given user and form submission
		 *
		 * @param kEvent $event
		 */
		function OnDeleteDraft($event)
		{
			$object = $event->getObject( Array('skip_autoload' => true) );
			/* @var $object kDBItem */

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

			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
			if ($items_info) {
				foreach ($items_info as $id => $field_values) {
					$object->setID($id);
	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
					$object->SetDBField('DraftId', 0);

	 				$load_keys = Array (
	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
	 					'CreatedById' => $this->Application->RecallVar('user_id'),
	 				);

	 				// get existing draft for given submission and user
	 				$draft->Load($load_keys);
					if ($draft->isLoaded()) {
						$temp_handler = $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler');
						/* @var $temp_handler kTempTablesHandler */

						$temp_handler->DeleteItems('draft', '', Array ($draft->GetID()));
					}
				}
			}

			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
			$event->redirect = false;
		}
	}