<?php
/**
* @version	$Id: priority_helper.php 16513 2017-01-20 14:10:53Z 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 kPriorityHelper extends kHelper {

		/**
		 * Prepares options for priority dropdown
		 *
		 * @param kEvent $event
		 * @param bool $is_new for newly created items add new priority to the end
		 * @param string $constrain constrain for priority selection (if any)
		 * @param string $joins left joins, used by constrain (if any)
		 *
		 */
		function preparePriorities($event, $is_new = false, $constrain = '', $joins = '')
		{
			/** @var kDBItem $object */
			$object = $event->getObject();

			$field_options = $object->GetFieldOptions('Priority');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

			$sql = 'SELECT COUNT(*)
					FROM ' . $table_name . ' item_table
					' . $joins;

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

			if ( !$object->isField('OldPriority') ) {
				// make sure, then OldPriority field is defined
				$virtual_fields = $object->getVirtualFields();
				$virtual_fields['OldPriority'] = Array ('type' => 'int', 'default' => 0);
				$object->setVirtualFields($virtual_fields);
			}

			$items_count = $this->Conn->GetOne($sql);
			$current_priority = $object instanceof kDBList ? 0 : $object->GetDBField('Priority');

			if ( $is_new || $current_priority == -($items_count + 1) || $this->isTempTableOnly($object) ) {
				$items_count++;
			}

			if ( $is_new ) {
				// add new item to the end of list
				$object->SetDBField('Priority', -$items_count);
				$object->SetDBField('OldPriority', -$items_count);
			}
			else {
				// storing priority right after load for comparing when updating
				$object->SetDBField('OldPriority', $current_priority);
			}

			for ($i = 1; $i <= $items_count; $i++) {
				$field_options['options'][-$i] = $i;
			}

			$object->SetFieldOptions('Priority', $field_options);
		}

		/**
		 * Determines if an item only exists in temp table.
		 *
		 * @param kDBBase $object Object.
		 *
		 * @return boolean
		 */
		protected function isTempTableOnly(kDBBase $object)
		{
			if ( !$object->IsTempTable() || ($object instanceof kDBList) ) {
				return false;
			}

			return $object->GetID() <= 0;
		}

		/**
		 * Updates priorities for changed items
		 *
		 * @param kEvent $event
		 * @param Array $changes = Array (ID => Array ('constrain' => ..., 'new' => ..., 'old' => ...), ...)
		 * @param Array $new_ids = Array (temp_id => live_id)
		 * @param string $constrain
		 * @param string $joins
		 * @return Array
		 */
		function updatePriorities($event, $changes, $new_ids, $constrain = '', $joins = '')
		{
			// TODO: no need pass external $constrain, since the one from $pair is used

			if ( !$changes ) {
				// no changes to process
				return Array ();
			}

			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

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

			$ids = Array ();
			$not_processed = array_keys($changes);

			foreach ($changes as $id => $pair) {
				array_push($ids, $id);
				$constrain = isset($pair['constrain']) ? $this->normalizeConstrain($pair['constrain']) . ' AND ' : '';

				if ( $pair['old'] == 'new' ) {
					// replace 0 with newly created item id (from $new_ids mapping)
					$not_processed[array_search($id, $not_processed)] = $new_ids[$id];
					$id = $new_ids[$id];

					$sql = 'SELECT MIN(item_table.Priority)
							FROM ' . $table_name . ' item_table
							' . $joins . '
							WHERE ' . $constrain . ' item_table.' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
					$min_priority = (int)$this->Conn->GetOne($sql) - 1;

					if ( $pair['new'] < $min_priority ) {
						$pair['new'] = $min_priority;
					}

					$pair['old'] = $min_priority;
				}

				if ( $pair['new'] < $pair['old'] ) {
					$set = '	SET item_table.Priority = item_table.Priority + 1';
					$where = '	WHERE ' . $constrain . '
								item_table.Priority >= ' . $pair['new'] . '
								AND
								item_table.Priority < ' . $pair['old'] . '
								AND
								' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
				}
				elseif ( $pair['new'] > $pair['old'] ) {
					$set = '	SET item_table.Priority = item_table.Priority - 1';
					$where = '	WHERE ' . $constrain . '
								item_table.Priority > ' . $pair['old'] . '
								AND
								item_table.Priority <= ' . $pair['new'] . '
								AND
								' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
				}
				else {
					$set = '	SET item_table.Priority = ' . $pair['new'];
					$where = '	WHERE ' . $id_field . ' = ' . $id;
				}

				$sql = 'SELECT item_table.' . $id_field . '
						FROM ' . $table_name . ' item_table
						' . $joins . '
						' . $where;
				$ids = array_merge($ids, $this->Conn->GetCol($sql));

				$q = 'UPDATE ' . $table_name . ' item_table
						' . $joins . '
						' . $set . $where;
				$this->Conn->Query($q);

				unset($not_processed[array_search($id, $not_processed)]);
			}

			return $ids;
		}

		/**
		 * Recalculates priorities
		 *
		 * @param kEvent $event
		 * @param string $constrain
		 * @param string $joins
		 * @return Array
		 */
		function recalculatePriorities($event, $constrain = '', $joins = '')
		{
			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

			if ( $constrain ) {
				$constrain = $this->normalizeConstrain($constrain);
			}

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

			$sql = 'SELECT ' . $id_field . '
					FROM ' . $table_name . ' item_table ' .
					$joins . ' ' .
					($constrain ? ' WHERE ' . $constrain : '') . '
					ORDER BY item_table.Priority DESC';
			$items = $this->Conn->GetCol($sql);

			foreach ($items as $item_number => $item_id) {
				$sql = 'UPDATE ' . $table_name . '
						SET Priority = ' . -($item_number + 1) . '
						WHERE ' . $id_field . ' = ' . $item_id;
				$this->Conn->Query($sql);
			}

			return $items;
		}

		/**
		 * Adds current table name into constrain if doesn't have it already (to prevent ambiguous columns during joins)
		 *
		 * @param string $constrain
		 * @return string
		 */
		function normalizeConstrain($constrain)
		{
			if ( strpos($constrain, '.') === false ) {
				return 'item_table.' . $constrain;
			}

			return $constrain;
		}

		/**
		 * Performs fake kDBItem::Update call, so any OnBefore/OnAfter events would be notified of priority change
		 *
		 * @param string $prefix
		 * @param Array $ids
		 */
		function massUpdateChanged($prefix, $ids)
		{
			$ids = array_unique($ids);

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

			$sql = 	$dummy->GetSelectSQL() . '
					WHERE ' . $dummy->TableName . '.' . $dummy->IDField . ' IN (' . implode(',', $ids) . ')';
			$records = $this->Conn->Query($sql);

			foreach ($records as $record) {
				$dummy->LoadFromHash($record);
				$dummy->Update();
			}
		}
	}
