<?php
/**
* @version	$Id: priority_eh.php 15262 2012-04-03 11:12:46Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2011 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 PriorityEventHandler extends kDBEventHandler {

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

		$permissions = Array (
			'OnRecalculatePriorities' => Array ('self' => true),
		);

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

	/**
	 * Define alternative event processing method names
	 *
	 * @return void
	 * @see kEventHandler::$eventMethods
	 * @access protected
	 */
	protected function mapEvents()
	{
		parent::mapEvents();

		$events_map = Array (
			'OnMassMoveUp' => 'OnChangePriority',
			'OnMassMoveDown' => 'OnChangePriority',
		);

		$this->eventMethods = array_merge($this->eventMethods, $events_map);
	}

	/**
	 * Occurs, when config was parsed, allows to change config data dynamically
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnAfterConfigRead(kEvent $event)
	{
		parent::OnAfterConfigRead($event);

		$hooks = Array(
			Array(
				'Mode' => hAFTER,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnAfterItemLoad', 'OnPreCreate', 'OnListBuild'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnPreparePriorities',
				'Conditional' => false,
			),
			Array(
				'Mode' => hBEFORE,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnPreSaveCreated'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnPreparePriorities',
				'Conditional' => false,
			),
			Array(
				'Mode' => hAFTER,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnPreSave', 'OnPreSaveCreated', 'OnSave', 'OnUpdate'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnSavePriorityChanges',
				'Conditional' => false,
			),
			Array(
				'Mode' => hAFTER,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnSave'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnSaveItems',
				'Conditional' => false,
			),
			Array(
				'Mode' => hBEFORE,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnBeforeItemCreate'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnItemCreate',
				'Conditional' => false,
			),
			Array(
				'Mode' => hBEFORE,
				'Conditional' => false,
				'HookToPrefix' => '',
				'HookToSpecial' => '*',
				'HookToEvent' => Array('OnAfterItemDelete'),
				'DoPrefix' => 'priority',
				'DoSpecial' => '*',
				'DoEvent' => 'OnItemDelete',
				'Conditional' => false,
			)
		);

		$prefixes = $this->Application->getUnitOption($event->Prefix, 'ProcessPrefixes', Array ());
		/* @var $prefixes Array */

		foreach ($prefixes as $prefix) {
			foreach ($hooks as $hook) {
				if ( !is_array($hook['HookToEvent']) ) {
					$hook['HookToEvent'] = Array($hook['HookToEvent']);
				}

				foreach ($hook['HookToEvent'] as $hook_event) {
					$this->Application->registerHook(
						$prefix . '.' . $hook['HookToSpecial'] . ':' . $hook_event,
						$event->Prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent'],
						$hook['Mode'],
						$hook['Conditional']
					);
				}
			}
		}
	}

	/**
	 * Should be hooked to OnAfterItemLoad, OnPreSaveCreated (why latter?)
	 *
	 * @param kEvent $event
	 */
	function OnPreparePriorities($event)
	{
		if ( !$this->Application->isAdminUser ) {
			return ;
		}

		$priority_helper = $this->Application->recallObject('PriorityHelper');
		/* @var $priority_helper kPriorityHelper */

		list ($constrain, $joins) = $this->getConstrainInfo($event);
		$is_new = $event->MasterEvent->Name == 'OnPreCreate' || $event->MasterEvent->Name == 'OnPreSaveCreated';
		$priority_helper->preparePriorities($event->MasterEvent, $is_new, $constrain, $joins);
	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */
	function OnSavePriorityChanges($event)
	{
		if ($event->MasterEvent->status != kEvent::erSUCCESS) {
			// don't update priorities, when OnSave validation failed
			return ;
		}

		$object = $event->MasterEvent->getObject();

		$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
		$changes = $tmp ? unserialize($tmp) : array();

		if (!isset($changes[$object->GetID()])) {
			$changes[$object->GetId()]['old'] = $object->GetID() == 0 ? 'new' : $object->GetDBField('OldPriority');
		}

		if ($changes[$object->GetId()]['old'] == $object->GetDBField('Priority')) return ;
		$changes[$object->GetId()]['new'] = $object->GetDBField('Priority');

		list ($constrain, $joins) = $this->getConstrainInfo($event);

		if ($constrain) {
			$changes[$object->GetId()]['constrain'] = $constrain;
		}

		$this->Application->StoreVar('priority_changes'.$this->Application->GetVar('m_wid'), serialize($changes));
	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */
	function OnItemDelete($event)
	{
		// just store the prefix in which the items were deleted
		$del = $this->Application->RecallVar('priority_deleted' . $this->Application->GetVar('m_wid'));
		$del = $del ? unserialize($del) : array();

		list ($constrain, $joins) = $this->getConstrainInfo($event);
		$cache_key = crc32($event->MasterEvent->Prefix . ':' . $constrain . ':' . $joins);

		if ( !isset($del[$cache_key]) ) {
			$del[$cache_key] = Array (
				'prefix' => $event->MasterEvent->Prefix,
				'constrain' => $constrain,
				'joins' => $joins,
			);

			$this->Application->StoreVar('priority_deleted' . $this->Application->GetVar('m_wid'), serialize($del));
		}
	}

	/**
	 * Called before script shut-down and recalculate all deleted prefixes, to avoid recalculation on each deleted item
	 *
	 * @param kEvent $event
	 */
	function OnBeforeShutDown($event)
	{
		$del = $this->Application->RecallVar('priority_deleted'.$this->Application->GetVar('m_wid'));
		$del = $del ? unserialize($del) : array();

		$priority_helper = $this->Application->recallObject('PriorityHelper');
		/* @var $priority_helper kPriorityHelper */

		foreach ($del as $del_info) {
			$dummy_event = new kEvent( array('prefix'=>$del_info['prefix'], 'name'=>'Dummy' ) );
			$ids = $priority_helper->recalculatePriorities($dummy_event, $del_info['constrain'], $del_info['joins']);

			if ($ids) {
				$priority_helper->massUpdateChanged($del_info['prefix'], $ids);
			}
		}

		$this->Application->RemoveVar('priority_deleted'.$this->Application->GetVar('m_wid'));
	}

	/**
	 * Enter description here...
	 *
	 * @param kEvent $event
	 */
	function OnSaveItems($event)
	{
		$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
		$changes = $tmp ? unserialize($tmp) : array();

		$priority_helper = $this->Application->recallObject('PriorityHelper');
		/* @var $priority_helper kPriorityHelper */

		list ($constrain, $joins) = $this->getConstrainInfo($event);
		$ids = $priority_helper->updatePriorities($event->MasterEvent, $changes, Array (0 => $event->MasterEvent->getEventParam('ids')), $constrain, $joins);

		if ($ids) {
			$priority_helper->massUpdateChanged($event->MasterEvent->Prefix, $ids);
		}
	}

	function OnItemCreate($event)
	{
		$obj = $event->MasterEvent->getObject();
		if ($obj->GetDBField('Priority') == 0) {
			$priority_helper = $this->Application->recallObject('PriorityHelper');
			/* @var $priority_helper kPriorityHelper */

			list ($constrain, $joins) = $this->getConstrainInfo($event);
			$priority_helper->preparePriorities($event->MasterEvent, true, $constrain, $joins);
		}
	}

	/**
	 * Processes OnMassMoveUp, OnMassMoveDown events
	 *
	 * @param kEvent $event
	 */
	function OnChangePriority($event)
	{
		$prefix = $this->Application->GetVar('priority_prefix');
		$dummy_event = new kEvent( array('prefix'=>$prefix, 'name'=>'Dummy' ) );

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

		if ($ids) {
			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($prefix, 'TableName');

			if ( $this->Application->IsTempMode($prefix) ) {
				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix);
			}

			$sql = 'SELECT Priority, '.$id_field.'
					FROM '.$table_name.'
					WHERE '.$id_field.' IN ('.implode(',', $ids).') ORDER BY Priority DESC';
			$priorities = $this->Conn->GetCol($sql, $id_field);

			$priority_helper = $this->Application->recallObject('PriorityHelper');
			/* @var $priority_helper kPriorityHelper */

			list ($constrain, $joins) = $this->getConstrainInfo($event);

			$sql = 'SELECT IFNULL(MIN(item_table.Priority), -1)
					FROM '.$table_name . ' item_table
					' . $joins;

			if ( $constrain ) {
				$sql .= ' WHERE ' . $priority_helper->normalizeConstrain($constrain);
			}

			$min_priority = $this->Conn->GetOne($sql);

			foreach ($ids as $id) {
				$new_priority = $priorities[$id] + ($event->Name == 'OnMassMoveUp' ? +1 : -1);
				if ($new_priority > -1 || $new_priority < $min_priority) {
					continue;
				}

				$changes = Array (
					$id	=>	Array ('old' => $priorities[$id], 'new' => $new_priority),
				);

				if ($constrain) {
					$changes[$id]['constrain'] = $constrain;
				}

				$sql = 'UPDATE '.$table_name.'
						SET Priority = '.$new_priority.'
						WHERE '.$id_field.' = '.$id;
				$this->Conn->Query($sql);

				$ids = $priority_helper->updatePriorities($dummy_event, $changes, Array ($id => $id), $constrain, $joins);

				if ($ids) {
					$priority_helper->massUpdateChanged($prefix, $ids);
				}
			}
		}

		$this->clearSelectedIDs($dummy_event);
	}

	/**
	 * Completely recalculates priorities in current category
	 *
	 * @param kEvent $event
	 */
	function OnRecalculatePriorities($event)
	{
		$priority_helper = $this->Application->recallObject('PriorityHelper');
		/* @var $priority_helper kPriorityHelper */

		$prefix = $this->Application->GetVar('priority_prefix');
		$dummy_event = new kEvent($prefix . ':Dummy');

		list ($constrain, $joins) = $this->getConstrainInfo($event);
		$ids = $priority_helper->recalculatePriorities($dummy_event, $constrain, $joins);

		if ($ids) {
			$priority_helper->massUpdateChanged($prefix, $ids);
		}
	}

	/**
	 * Returns constrain for current priority calculations
	 *
	 * @param kEvent $event
	 * @return Array
	 */
	function getConstrainInfo($event)
	{
		$constrain_event = new kEvent($event->MasterEvent->getPrefixSpecial() . ':OnGetConstrainInfo');
		$constrain_event->setEventParam('actual_event', $event->Name);
		$constrain_event->setEventParam('original_special', $event->MasterEvent->Special);
		$constrain_event->setEventParam('original_event', $event->MasterEvent->Name);
		$this->Application->HandleEvent($constrain_event);

		return $constrain_event->getEventParam('constrain_info');
	}
}
