<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2008 - 2017 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flSession Class
 *
 * Base class to provide session management and storage.
 *
 * @package fusionLib
 */
abstract class flSession extends SessionHandler {

	/**
	 * The name of the flash data namespace.
	 */
	CONST FLASHDATANS = '__flSession::flashdata';

	/**
	 * The response object to use.
	 *
	 * @var flResponse
	 */
	protected $response;

	/**
	 * The time to expire the session data at.
	 *
	 * @var int
	 */
	protected $expireat;

	/**
	 * The name of the cookie.
	 *
	 * @var string
	 */
	protected $cookieName;

	/**
	 * The domain the cookie is available on.
	 *
	 * @var string
	 */
	protected $cookieDomain;

	/**
	 * The path the session cookie is valid on.
	 *
	 * @var string
	 */
	protected $cookiePath;

	/**
	 * The session ID.
	 *
	 * @var string
	 */
	protected $sessionID;

	/**
	 * Flags if to expire cookie when browser closes or session expires.
	 *
	 * @var bool
	 */
	protected $expireCookieOnExit;

	/**
	 * Flags if using secure cookies.
	 *
	 * @var bool
	 */
	protected $secureCookies;

	/**
	 * The session data.
	 *
	 * @var array
	 */
	protected $data = null;

	/**
	 * The flash data from the last load.
	 *
	 * @var flSessionData
	 */
	protected $lastFlash;

	/**
	 * Construct and initialize the session object.
	 *
	 * @param flResponse $response The response object to use.
	 * @param int $life The time in seconds a session should live for, must be greater than 0 or null to use the value of session.gc_maxlifetime.
	 * @param bool $expireCookieOnExit true to expire cookie at the end of the session or false to keep the cookie for the lifetime of the session data.
	 * @param string $cookieName The name of the cookie holding session data.
	 * @param string $cookieDomain The optional domain the cookie is available on, e.g. for all subdomains '.example.com'
	 * @param string $cookiePath The options path the cookie is valid on
	 * @param bool $cookieSecure true to use secure session cookies; else false.
	 */
	function __construct($response, $life = null, $expireCookieOnExit = true, $cookieName = '__sessionid', $cookieDomain = '', $cookiePath = '/', $cookieSecure = false) {
		$this->response = $response;
		$this->expireat = time() + ($life === null ? get_cfg_var('session.gc_maxlifetime') : $life);
		$this->cookieName = $cookieName;
		$this->cookieDomain = $cookieDomain;
		$this->cookiePath = $cookiePath;
		$this->expireCookieOnExit = $expireCookieOnExit;
		$this->secureCookies = $cookieSecure;

		session_set_save_handler($this);
		session_name($cookieName);
		session_set_cookie_params($life, $cookiePath, $cookieDomain, $cookieSecure, true);
		session_start();
		$this->sessionID = session_id();
		$this->data = &$_SESSION;

		// Get the flash data
		$this->lastFlash = $this->getNamespace(self::FLASHDATANS);
		$this->data[self::FLASHDATANS] = new flSessionData();
	}

	/**
	 * Store the session data and end it.
	 */
	public function writeClose() {
		if($this->sessionID !== null) {
			session_write_close();
		}
	}

	/**
	 * Regenerate the session ID.
	 */
	function regenerate() {
		session_regenerate_id();
		$this->sessionID = session_id();
	}

	/**
	 * Get the current session ID.
	 *
	 * @return string The session ID
	 */
	function getSessionID() {
		return $this->sessionID;
	}

	/**
	 * Destroy the session.
	 */
	function destorySession() {
		if($this->sessionID !== null) {
			session_destroy();
			$this->sessionID = null;
		}
	}

	/**
	 *	Get a namespace data object.
	 *
	 *	@param string $ns The namespace to get.
	 *	@return flSessionData The data object for the namespace.
	 */
	function getNamespace($ns) {
		if(!isset($this->data[$ns]))
			$this->data[$ns] = new flSessionData();
		return $this->data[$ns];
	}

	/**
	 * Test if the session has a namespace.
	 *
	 * @param string $ns The namespace to test for.
	 * @return bool true if the namespace is present; else false.
	 */
	function hasNamespace($ns) {
		return isset($this->data[$ns]);
	}

	/**
	 * Destroy a namespace.
	 *
	 * @param string $ns The namespace to destroy.
	 */
	function destroyNamespace($ns) {
		unset($this->data[$ns]);
	}

	/**
	 * Set flash data.
	 *
	 * @param string $n The name of the data.
	 * @param string $v The value of the data.
	 */
	function flashSet($n, $v) {
		$this->lastFlash->$n = ($this->data[self::FLASHDATANS]->$n = $v);
	}

	/**
	 * Get the flash data.
	 *
	 * @param string $n The name of the data to get.
	 * @param mixed $default The default value to return.
	 * @return mixed The value of the data item.
	 */
	function flashGet($n, $default = null) {
		return isset($this->lastFlash->$n) ? $this->lastFlash->$n : $default;
	}

	/**
	 * Make a flash data item available for the next request.
	 *
	 * @param string $n The name of the flash data item to keep.
	 */
	function flashKeep($n) {
		if(isset($this->lastFlash->$n))
			$this->data[self::FLASHDATANS]->$n = $this->lastFlash->$n;
	}

	/**
	 * Delete a flash data item.
	 *
	 * @param string $n The name of the flash data item to delete.
	 */
	function flashDelete($n) {
		unset($this->data[self::FLASHDATANS]->$n);
		unset($this->lastFlash->$n);
	}

	/**
	 * Make all flash data available for the next request.
	 *
	 * Note: This must be called before setting any flash data.
	 */
	function flashKeepAll() {
		$this->data[self::FLASHDATANS] = $this->lastFlash;
	}
}

/**
 * flSessionData Class
 *
 * Class to hold session data for a namespace data.
 *
 * @package fusionLib
 */
class flSessionData implements Countable {

	/**
	 * Capture requests for data that isn't present.
	 *
	 * @param string $name The name of the variable to get.
	 * @return mixed The value.
	 * @throws flSessionException
	 */
	function __get($name) {
		throw new flSessionException("Session data variable '$name' not defined.");
	}

	/**
	 * Get the value from the session data or the default if not set.
	 *
	 * @param string $name The name of the variable to get.
	 * @param mixed $default The default value.
	 * @return mixed The value or if not set the default.
	 */
	function get($name, $default = null) {
		return isset($this->$name) ? $this->$name : $default;
	}

	/**
	 * Return the count of the variables/values held.
	 *
	 * @return int The number of variables/values held.
	 */
	function count() {
		return count(get_object_vars($this));
	}
}
