<?php
/**
 * @package Habari
 *
 */

/**
 * SuperGlobals class
 *
 */
class SuperGlobal extends ArrayIterator
{
	protected $values = array();
	protected $raw_values = array();

	public function __construct($array)
	{
		if (!is_array($array) && !$array instanceof SuperGlobal) {
			throw new Exception('Parameter must be array or SuperGlobal');
		}
		parent::__construct($array);
	}

	/**
	 * Convert $_GET, $_POST and $_SERVER into SuperGlobal instances, also kill $_REQUEST
	 *
	 * @return
	 */
	public static function process_gps()
	{
		/* We should only revert the magic quotes once per page hit */
		static $revert = true;

		if (!$revert) {
			// our work has already been done
			return;
		}

		if ( get_magic_quotes_gpc() ) {
			$_GET = Utils::stripslashes($_GET);
			$_POST = Utils::stripslashes($_POST);
		}

		$_GET = new SuperGlobal($_GET);
		$_POST = new SuperGlobal($_POST);
		$_SERVER = new SuperGlobal($_SERVER);
		unset($_REQUEST);

		$revert = false;
	}

	/**
	 * Convert $_COOKIE into SuperGlobal instance
	 *
	 */
	public static function process_c()
	{
		/* We should only revert the magic quotes once per page hit */
		static $revert = true;

		if (!$revert) {
			// our work has already been done
			return;
		}

		if ( get_magic_quotes_gpc() ) {
			$_COOKIE = Utils::stripslashes($_COOKIE);
		}

		$_COOKIE = new SuperGlobal($_COOKIE);

		$revert = false;
	}

	/**
	 * Return the raw, unfiltered value of the requested index
	 *
	 * @param mixed $index The index of the value
	 * @return mixed The unfiltered value
	 */
	public function raw($index)
	{
		if(isset($this->raw_values[$index])) {
			return $this->raw_values[$index];
		}
		$cp = $this->get_array_copy_raw();
		if(isset($cp[$index])) {
			$this->raw_values[$index] = $cp[$index];
			return $this->raw_values[$index];
		}
	}
	
	/**
	 * Return a copy of the filtered array. Implments ArrayIterator::getArrayCopy()
	 */
	public function getArrayCopy()
	{
		return array_map( array( $this, 'base_filter'), parent::getArrayCopy() );
	}
	
	/**
	 * Return a copy of the unfiltered array.
	 */
	public function get_array_copy_raw()
	{
		return parent::getArrayCopy();
	}
	
	/**
	 * Return the current array element, filtered. Implements ArrayIterator::current()
	 */
	public function current()
	{
		return $this->offsetGet(parent::key());
	}

	/**
	 * Return the value of an array offset.  Allows the values to be filtered
	 *
	 * @param mixed $index The index of the array
	 * @return mixed The filtered value at the array index
	 */
	public function offsetGet($index)
	{
		if(isset($this->values[$index])) {
			return $this->values[$index];
		}
		$cp = $this->get_array_copy_raw();
		if(isset($cp[$index])) {
			$this->values[$index] = $this->base_filter($cp[$index]);
			return $this->values[$index];
		}
	}
	
	/**
	 * Set the value of the array, clear caches for that index
	 *
	 * @param mixed $index The array index
	 * @param mixed $value Tha value to store
	 */
	public function offsetSet($index, $value)
	{
		unset($this->values[$index]);
		unset($this->raw_values[$index]);
		parent::offsetSet($index, $value);
	}

	/**
	 * Recursively filter array values and strings using InputFilter::filter()
	 *
	 * @param mixed $value A value to filter
	 * @return mixes The filtered value
	 */
	protected function base_filter($value)
	{
		if(is_array($value)) {
			return array_map(array($this, 'base_filter'), $value);
		}
		elseif(is_string($value)) {
			return InputFilter::filter($value);
		}
		else {
			return $value;
		}
	}

	/**
	 * Merges the contents of one or more arrays or ArrayObjects with this SuperGlobal
	 *
	 * @param mixed One or more array-like structures to merge into this array.
	 * @return SuperGlobal The merged array
	 */
	public function merge()
	{
		$args = func_get_args();
		$cp = $this->get_array_copy_raw();
		foreach($args as $ary) {
			if(is_array($ary)) {
				foreach($ary as $key => $value) {
					if(is_numeric($key)) {
						$cp[] = $value;
					}
					else {
						$cp[$key] = $value;
					}
				}
			}
			elseif($ary instanceof SuperGlobal) {
				// loop to get raw data.
				while( $ary->valid() ) {
					if( is_numeric($ary->key()) ) {
						$cp[] = $ary->raw($ary->key());
					}
					else {
						$cp[$ary->key()] = $ary->raw($ary->key());
					}
					$ary->next();
				}
			}
			elseif($ary instanceof ArrayObject || $ary instanceof ArrayIterator) {
				$arycp = $ary->getArrayCopy();  // Don't trigger offsetGet for ArrayObject
				foreach($arycp as $key => $value) {
					if(is_numeric($key)) {
						$cp[] = $value;
					}
					else {
						$cp[$key] = $value;
					}
				}
			}
			else {
				$cp[] = $ary;
			}
		}
		return new SuperGlobal($cp);
	}

	/**
	 * Filters this SuperGlobal based on an array or arrays of keys
	 *
	 * @param mixed An array of key values that should be returned, or a string of a key value to be returned
	 * @return SuperGlobal The values from this array that match the supplied keys
	 */
	public function filter_keys()
	{
		$keys = array();
		$args = func_get_args();
		foreach($args as $ary) {
			if(!is_array($ary)) {
				$ary = array($ary);
			}
			$keys = array_merge($keys, array_values($ary));
		}
		$cp = $this->get_array_copy_raw();
		$cp = array_intersect_key($cp, array_flip($keys));
		return new SuperGlobal($cp);
	}
	
	/**
	 * Apply a map function to this array, like array_map()
	 * @param callback $fn the name of the function to map through
	 * @return SuperGlobal the result of the mapping
	 **/
	public function map($fn)
	{
		return new SuperGlobal(array_map($fn, $this->get_array_copy_raw()));
	}
	
	/**
	 * Use a regular expression replacement to change the keys in an array
	 * @param string $replacement (optional) The regex replacement value
	 * @param string $search_regex (optional) The regex search value
	 * @return SuperGlobal The re-keyed array
	 **/
	public function rekey($replacement = '{\$$0}', $search_regex = '%^.*$%')
	{
		$output = array();
		foreach($this->get_array_copy_raw() as $k => $v) {
			$output[preg_replace($search_regex, $replacement, $k)] = $v;
		}
		return new SuperGlobal($output);
	}
}

?>
