<?php
/**
* @version	$Id: mailing_list_eh.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 MailingListEventHandler extends kDBEventHandler {

		/**
		 * Allows to override standart permission mapping
		 *
		 */
		function mapPermissions()
		{
			parent::mapPermissions();

			$permissions = Array (
				'OnCancelMailing' => Array ('self' => 'edit'),
				'OnGenerateEmailQueue' => Array ('self' => true),
				'OnProcessEmailQueue' => Array ('self' => true),
			);

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

		/**
		 * Prepare recipient list
		 *
		 * @param kEvent $event
		 */
		function OnNew(&$event)
		{
			parent::OnNew($event);

			$recipient_type = $this->Application->GetVar('mailing_recipient_type');
			if (!$recipient_type) {
				return ;
			}

			$recipients = $this->Application->GetVar($recipient_type);
			if ($recipients) {
				$object =& $event->getObject();
				/* @var $object kDBItem */

				$to = $recipient_type . '_' . implode(';' . $recipient_type . '_', array_keys($recipients));

				$object->SetDBField('To', $to);
			}
		}

		/**
		 * Don't allow to delete mailings in progress
		 *
		 * @param kEvent $event
		 */
		function customProcessing(&$event, $type)
		{
			if ($event->Name == 'OnMassDelete' && $type == 'before') {
				$ids = $event->getEventParam('ids');
				if ($ids) {
					$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
					$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

					$sql = 'SELECT ' . $id_field . '
							FROM ' . $table_name . '
							WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Status <> ' . MAILING_LIST_PARTIALLY_PROCESSED;
					$allowed_ids = $this->Conn->GetCol($sql);

					$event->setEventParam('ids', $allowed_ids);
				}
			}
		}

		/**
		 * Delete all realted mails in email queue
		 *
		 * @param kEvent $event
		 */
		function OnAfterItemDelete(&$event)
		{
			parent::OnAfterItemDelete($event);

			$this->_deleteQueue($event);

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

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

			$ids = $this->StoreSelectedIDs($event);

			if ($ids) {
				foreach ($ids as $id) {
					$object->Load($id);
					$object->SetDBField('Status', MAILING_LIST_CANCELLED);
					$object->Update();
				}
			}

			$this->clearSelectedIDs($event);
		}

		/**
		 * Checks, that at least one message text field is filled
		 *
		 * @param kEvent $event
		 */
		function OnBeforeItemCreate(&$event)
		{
			parent::OnBeforeItemCreate($event);

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

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

			if (!$object->GetDBField('MessageText')) {
				$object->setRequired('MessageHtml', true);
			}

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

		/**
		 * Deletes mailing list email queue, when it becomes cancelled
		 *
		 * @param kEvent $event
		 */
		function OnAfterItemUpdate(&$event)
		{
			parent::OnAfterItemUpdate($event);

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

			$status = $object->GetDBField('Status');
			if (($status != $object->GetOriginalField('Status')) && ($status == MAILING_LIST_CANCELLED)) {
				$this->_deleteQueue($event);
			}
		}

		/**
		 * Deletes email queue records related with given mailing list
		 *
		 * @param kEvent $event
		 */
		function _deleteQueue(&$event)
		{
			$object =& $event->getObject();
			/* @var $object kDBItem */

			$sql = 'DELETE FROM ' . $this->Application->getUnitOption('email-queue', 'TableName') . '
					WHERE MailingId = ' . $object->GetID();
			$this->Conn->Query($sql);
		}

		/**
		 * Allows to safely get mailing configuration variables
		 *
		 * @param string $variable_name
		 * @return int
		 */
		function _ensureDefault($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;
		}

		/**
		 * Generates email queue for active mailing lists
		 *
		 * @param kEvent $event
		 */
		function OnGenerateEmailQueue(&$event)
		{
			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

			$where_clause = Array (
				'Status NOT IN (' . MAILING_LIST_CANCELLED . ',' . MAILING_LIST_PROCESSED . ')',
				'(EmailsQueued < 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 ;
			}

			// queue 10 emails per step summary from all mailing lists (FIFO logic)
			$to_queue = $this->_ensureDefault('MailingListQueuePerStep');
			if ($to_queue === false) {
				return ;
			}

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

			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'] = MAILING_LIST_PARTIALLY_PROCESSED;
					$mailing_data = array_merge_recursive2($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),
					'EmailsQueued' => $mailing_data['EmailsQueued'] + $process_count,
				);
				$mailing_data = array_merge_recursive2($mailing_data, $updated_fields);
				$this->Conn->doUpdate($updated_fields, $table_name, $id_field . ' = ' . $mailing_id);

				if (!$to_queue) {
					// emails to be queued per step reached -> leave
					break;
				}
			}
		}

		/**
		 * Process email queue from cron
		 *
		 * @param kEvent $event
		 */
		function OnProcessEmailQueue(&$event)
		{
			$deliver_count = $this->_ensureDefault('MailingListSendPerStep');
			if ($deliver_count === false) {
				return ;
			}

			$queue_table = $this->Application->getUnitOption('email-queue', 'TableName');

			// get queue part to send
			$sql = 'SELECT *
					FROM ' . $queue_table . '
					WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')
					LIMIT 0,' . $deliver_count;
			$messages = $this->Conn->Query($sql);

			if (!$messages) {
				// no more messages left in queue
				return ;
			}

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

			$mailing_list_helper->processQueue($messages);
		}
	}