<?php
/**
* @version	$Id: col_picker_helper.php 16566 2017-07-16 17:49: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 kColumnPickerHelper extends kHelper {

	const DEFAULT_COLUMN_WIDTH = 100;

	/**
	 * Currently processed grid name.
	 *
	 * @var string
	 */
	protected $gridName = '';

	/**
	 * Data of currently processed grid.
	 *
	 * @var ColumnSet
	 */
	protected $pickerData;

	/**
	 * System wide setting indicating column freezer usage.
	 *
	 * @var boolean
	 */
	protected $useFreezer = true;

	/**
	 * Columns renamed by formatter in last used grid.
	 *
	 * @var array
	 */
	var $formatterRenamed = array();

	/**
	 * Create picker instance for a specific grid.
	 *
	 * @param string $prefix    Unit config prefix.
	 * @param string $grid_name Grid name.
	 */
	public function __construct($prefix, $grid_name)
	{
		parent::__construct();

		$splitted = $this->Application->processPrefix($prefix);
		$this->Init($splitted['prefix'], $splitted['special']);

		$this->useFreezer = $this->Application->ConfigValue('UseColumnFreezer');

		$this->gridName = $grid_name;
		$this->pickerData = $this->loadColumns();
	}

	/**
	 * Loads picker data.
	 *
	 * @return ColumnSet
	 */
	protected function loadColumns()
	{
		$default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false;
		$value = $this->Application->RecallPersistentVar($this->getVarName('get'), $default_value);

		if ( !$value ) {
			$columns = $this->rebuildColumns();
		}
		else {
			$default_columns = $this->getDefaultColumns();
			$columns = new ColumnSet(unserialize($value));

			if ( !$columns->same($default_columns) ) {
				$columns = $this->rebuildColumns($columns);
			}
		}

		return $columns;
	}

	/**
	 * Merges default column set with given one.
	 *
	 * @param ColumnSet $current_columns Currently used column set.
	 *
	 * @return ColumnSet
	 */
	protected function rebuildColumns(ColumnSet $current_columns = null)
	{
		if ( isset($current_columns) ) {
			$columns = $current_columns->merge($this->getDefaultColumns(), self::DEFAULT_COLUMN_WIDTH);
		}
		else {
			$columns = $this->getDefaultColumns();
		}

		$this->storeCols($columns);

		return $columns;
	}

	/**
	 * Returns column set built purely from grid definition in unit config.
	 *
	 * @return ColumnSet
	 */
	protected function getDefaultColumns()
	{
		$grid_columns = $this->getColumnsFromUnitConfig();

		// we NEED to recall dummy here to apply field changes imposed by formatters,
		// such as replacing multilingual field titles etc.
		$this->Application->recallObject($this->getPrefixSpecial(), null, array('skip_autoload' => 1));

		$counter = 0;
		$fields = $titles = $widths = $hidden = array();

		foreach ( $grid_columns as $name => $options ) {
			if ( array_key_exists('formatter_renamed', $options) && $options['formatter_renamed'] ) {
				// remove language prefix from field, because formatter renamed column
				$this->formatterRenamed[] = $name;
				$name = preg_replace('/^l[\d]+_/', '', $name);
			}

			$fields[$counter] = $name;
			$titles[$name] = isset($options['title']) ? $options['title'] : 'column:la_fld_' . $name;
			$widths[$name] = isset($options['width']) ? $options['width'] : self::DEFAULT_COLUMN_WIDTH;

			if ( isset($options['hidden']) && $options['hidden'] ) {
				$hidden[$counter] = $name;
			}

			$counter++;
		}

		$cols = array(
			'order' => $fields,
			'titles' => $titles,
			'hidden_fields' => $hidden,
			'widths' => $widths,
		);

		return new ColumnSet($cols);
	}

	/**
	 * Returns columns as-is from unit config.
	 *
	 * @return array
	 */
	protected function getColumnsFromUnitConfig()
	{
		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
		$grid = $grids[$this->gridName];

		if ( $this->useFreezer ) {
			$freezer_column = array('__FREEZER__' => array('title' => '__FREEZER__'));

			return array_merge_recursive($freezer_column, $grid['Fields']);
		}

		return $grid['Fields'];
	}

	/**
	* Gets variable name in persistent session to store column positions in
	*
	* @param string $mode
	* @return string
	*/
	protected function getVarName($mode = 'get')
	{
		$view_name = $this->Application->RecallVar($this->Prefix . '_current_view');

		$ret = $this->Prefix . '[' . $this->gridName . ']columns_.' . $view_name;

		if ( $mode == 'get' ) {
			// fallback to old storage system, that remember only 1 grid configuration per-unit
			if ( $this->Application->RecallPersistentVar($ret) === false ) {
				$ret = $this->Prefix . '_columns_.' . $view_name;
			}
		}

		return $ret;
	}

	/**
	 * Returns picker data.
	 *
	 * @return ColumnSet
	 */
	public function getData()
	{
		return $this->pickerData;
	}

	protected function storeCols(ColumnSet $cols)
	{
		$this->Application->StorePersistentVar($this->getVarName('set'), serialize($cols->toArray()));
	}

	/**
	 * Reorders given grid configuration based on picker data and removes hidden columns.
	 *
	 * @param array $grid_columns
	 *
	 * @return array
	 */
	public function apply(array $grid_columns)
	{
		uksort($grid_columns, array($this, 'compareColumns'));

		return $this->removeHidden($grid_columns);
	}

	/**
	 * Removes columns, that are hidden in picker configuration.
	 *
	 * @param array $grid_columns Grid columns.
	 *
	 * @return array
	 */
	protected function removeHidden(array $grid_columns)
	{
		$to_remove = array();

		foreach ( $grid_columns as $name => $options ) {
			if ( array_key_exists('formatter_renamed', $options) && $options['formatter_renamed'] ) {
				// remove language prefix from field, because formatter renamed column
				$name_renamed = preg_replace('/^l[\d]+_/', '', $name);
			}
			else {
				$name_renamed = $name;
			}

			if ( $this->pickerData->isHidden($name_renamed) ) {
				$to_remove[] = $name;
			}
		}

		foreach ( $to_remove as $name ) {
			unset($grid_columns[$name]);
		}

		return $grid_columns;
	}

	/**
	 * Helper function for reordering grid columns.
	 *
	 * @param string $first_column
	 * @param string $second_column
	 *
	 * @return integer
	 */
	protected function compareColumns($first_column, $second_column)
	{
		$first_column_order = $this->pickerData->getOrder($this->fixColumnName($first_column));
		$second_column_order = $this->pickerData->getOrder($this->fixColumnName($second_column));

		if ( $first_column_order == $second_column_order ) {
			return 0;
		}

		return ($first_column_order < $second_column_order) ? -1 : 1;
	}

	/**
	 * Saves changes to column widths.
	 *
	 * @param string $picked Visible columns.
	 * @param string $hidden Hidden columns.
	 *
	 * @return void
	 */
	public function saveColumns($picked, $hidden)
	{
		$order = $picked ? explode('|', $picked) : array();
		$hidden = $hidden ? explode('|', $hidden) : array();
		$order = array_merge($order, $hidden);

		$this->pickerData->allFields = $order;
		$this->pickerData->hiddenFields = $hidden;

		$this->storeCols($this->pickerData);
	}

	/**
	 * Saves changes to column widths.
	 *
	 * @param array|string $widths Column width info.
	 *
	 * @return void
	 */
	public function saveWidths($widths)
	{
		if ( !is_array($widths) ) {
			$widths = explode(':', $widths);
		}

		$i = 0;
		array_shift($widths); // removing first col (checkbox col) width

		foreach ( $this->pickerData->allFields as $field ) {
			if ( $field == '__FREEZER__' || $this->pickerData->isHidden($field) ) {
				continue;
			}

			$this->pickerData->widths[$field] = isset($widths[$i]) ? $widths[$i] : self::DEFAULT_COLUMN_WIDTH;
			$i++;
		}

		$this->storeCols($this->pickerData);
	}

	/**
	 * Returns width of a given column.
	 *
	 * @param string $column_name Column name.
	 *
	 * @return string
	 */
	public function getWidth($column_name)
	{
		return $this->pickerData->getWidth($this->fixColumnName($column_name));
	}

	/**
	 * Removes language prefix from formatter renamed column.
	 *
	 * @param string $name Column name.
	 *
	 * @return string
	 */
	protected function fixColumnName($name)
	{
		if ( in_array($name, $this->formatterRenamed) ) {
			// Remove language prefix from field, because formatter renamed column.
			$name = preg_replace('/^l[\d]+_/', '', $name);
		}

		return $name;
	}

}


class ColumnSet extends kBase
{

	/**
	 * List of all fields (key - order, value - field name).
	 *
	 * @var array
	 */
	public $allFields;

	/**
	 * List of hidden fields (key - order, value - field name).
	 *
	 * @var array
	 */
	public $hiddenFields;

	/**
	 * List of field titles (key - field name, value - label).
	 *
	 * @var array
	 */
	public $titles;

	/**
	 * List of field widths (key - field name, value - width).
	 *
	 * @var array
	 */
	public $widths;

	/**
	 * Creates column set.
	 *
	 * @param array $data Data.
	 */
	public function __construct(array $data)
	{
		$this->allFields = $data['order'];
		$this->hiddenFields = $data['hidden_fields'];

		$this->titles = $data['titles'];
		$this->widths = $data['widths'];
	}

	/**
	 * Returns array representation of an object.
	 *
	 * @return array
	 */
	public function toArray()
	{
		$ret = array(
			'order' => $this->allFields,
			'hidden_fields' => $this->hiddenFields,

			'titles' => $this->titles,
			'widths' => $this->widths,
		);

		return $ret;
	}

	/**
	 * Returns title for a column.
	 *
	 * @param string $column_name Column name.
	 *
	 * @return string
	 */
	public function getTitle($column_name)
	{
		return $this->titles[$column_name];
	}

	/**
	 * Returns width of a column.
	 *
	 * @param string $column_name Column name.
	 * @param mixed $default Default value.
	 *
	 * @return string
	 */
	public function getWidth($column_name, $default = false)
	{
		return isset($this->widths[$column_name]) ? $this->widths[$column_name] : $default;
	}

	/**
	 * Returns order for a column.
	 *
	 * @param string $column_name Column name.
	 *
	 * @return integer|boolean
	 */
	public function getOrder($column_name)
	{
		return array_search($column_name, $this->allFields);
	}

	/**
	 * Determines if a column is hidden.
	 *
	 * @param string $column_name Column name.
	 *
	 * @return boolean
	 */
	public function isHidden($column_name)
	{
		return array_search($column_name, $this->hiddenFields) !== false;
	}

	/**
	 * Returns checksum for current column set.
	 *
	 * @return integer
	 */
	public function getChecksum()
	{
		$sorted_fields = $this->allFields;
		$sorted_titles = $this->titles;
		asort($sorted_fields);
		asort($sorted_titles);

		return crc32(implode(',', $sorted_fields) . implode(',', $sorted_titles));
	}

	/**
	 * Compares 2 column sets.
	 *
	 * @param ColumnSet $columns Column set.
	 *
	 * @return boolean
	 */
	public function same(ColumnSet $columns)
	{
		return $this->getChecksum() == $columns->getChecksum();
	}

	/**
	 * Merges current column set with given one.
	 *
	 * @param ColumnSet $default_columns       Column set to merge with.
	 * @param integer   $default_width Default column width.
	 *
	 * @return self
	 */
	public function merge(ColumnSet $default_columns, $default_width)
	{
		// keep user column order (common columns between user's and default grid)
		$common = array_intersect($this->allFields, $default_columns->allFields);

		// get new columns (found in default grid, but not found in user's grid)
		$added = array_diff($default_columns->allFields, $this->allFields);

		// in case if freezer was added, then make it first column
		if ( in_array('__FREEZER__', $added) ) {
			array_unshift($common, '__FREEZER__');
			unset($added[array_search('__FREEZER__', $added)]);
		}

		// keep added column position
		$this->allFields = $common;

		foreach ( $added as $added_column ) {
			$this->insertAfter($added_column, $default_columns->getPrecedingColumn($added_column));
		}

		$this->titles = $default_columns->titles;
		$this->hiddenFields = array_intersect($this->allFields, $this->hiddenFields);

		// update width & hidden status for added columns
		foreach ( $added as $added_column ) {
			$this->widths[$added_column] = $default_columns->getWidth($added_column, $default_width);

			if ( $default_columns->isHidden($added_column) ) {
				$this->hiddenFields[$default_columns->getOrder($added_column)] = $added_column;
			}
		}

		return $this;
	}

	/**
	 * Inserts one column after another.
	 *
	 * @param string $new_column   Name of column to insert.
	 * @param string $after_column Name of column to insert after.
	 *
	 * @return self
	 */
	public function insertAfter($new_column, $after_column)
	{
		$addition = array($after_column, $new_column);
		array_splice($this->allFields, $this->getOrder($after_column), 1, $addition);

		return $this;
	}

	/**
	 * Returns preceding column.
	 *
	 * @param string $name Column name.
	 *
	 * @return string
	 */
	public function getPrecedingColumn($name)
	{
		$prev_column = reset($this->allFields);

		foreach ( $this->allFields as $column ) {
			if ( $column == $name ) {
				return $prev_column;
			}

			$prev_column = $column;
		}

		return '';
	}

}
