<?php
/** 
 * @package     VikWP - Libraries
 * @subpackage  adapter.session
 * @author      E4J s.r.l.
 * @copyright   Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 * @link        https://vikwp.com
 */

// No direct access
defined('ABSPATH') or die('No script kiddies please!');

/**
 * Class adapter for managing HTTP sessions using the Joomla standard interface.
 *
 * @since 10.0
 */
class JSession
{
	/**
	 * The session adapter instance.
	 *
	 * @var JSession
	 */
	private static $instance = null;

	/**
	 * Session data pool.
	 *
	 * @var array
	 */
	private $data;

	/**
	 * Class constructor.
	 */
	public function __construct()
	{
		$this->data = &$_SESSION;
	}

	/**
	 * Returns the global Session object, only creating it if it doesn't already exist.
	 *
	 * @return 	self 	The session object.
	 */
	public static function getInstance()
	{
		if (static::$instance === null)
		{
			static::$instance = new JSession();
		}

		return static::$instance;
	}

	/**
	 * Gets data from the session store.
	 *
	 * @param 	string 	$name 		Name of a variable.
	 * @param 	mixed 	$default 	Default value of a variable if not set.
	 * @param 	string 	$namespace 	Namespace to use.
	 *
	 * @return 	mixed 	Value of a variable.
	 */
	public function get($name, $default = null, $namespace = 'default')
	{
		// add prefix and namespace to avoid collisions
		$key = '__' . $namespace . '.' . $name;

		// check if the key is contained in the SESSION
		if (isset($this->data[$key]))
		{
			return $this->data[$key];
		}

		return $default;
	}

	/**
	 * Sets data into the session store.
	 *
	 * @param 	string 	$name 		Name of a variable.
	 * @param 	mixed 	$value 		Value of a variable.
	 * @param 	string 	$namespace 	Namespace to use.
	 *
	 * @return 	mixed 	Old value of a variable.
	 *
	 * @uses 	get()
	 */
	public function set($name, $value = null, $namespace = 'default')
	{
		$prev = $this->get($name, null, $namespace);

		// add prefix and namespace to avoid collisions
		$key = '__' . $namespace . '.' . $name;

		// push the value in the session
		$this->data[$key] = $value;

		return $prev;
	}

	/**
	 * Checks whether data exists in the session store.
	 *
	 * @param 	string 	 $name 		 Name of variable.
	 * @param 	string 	 $namespace  Namespace to use.
	 *
	 * @return  boolean  True if the variable exists.
	 *
	 * @uses 	get()
	 */
	public function has($name, $namespace = 'default')
	{
		return !is_null($this->get($name, null, $namespace));
	}

	/**
	 * Unsets data from the session store.
	 *
	 * @param 	string 	$name 		Name of variable.
	 * @param 	string 	$namespace 	Namespace to use.
	 *
	 * @return 	mixed 	The value from session or NULL if not set.
	 *
	 * @uses 	set()
	 */
	public function clear($name, $namespace = 'default')
	{
		return $this->set($name, null, $namespace);
	}

	/**
	 * Returns our internal nonce identifier.
	 * 
	 * @param   boolean  $forceNew  If true, the action will be randomly changed,
	 *                              instructing WP to use a different token.
	 *
	 * @return 	string   The action name that will be used by WordPress to create
	 *                   a matching token hash.
	 *
	 * @since 	10.1.33
	 */
	public static function getFormTokenAction($forceNew = false)
	{
		// create initial identifier
		static $id = 1;

		if ($forceNew)
		{
			// Refresh identifier when requested.
			// Use a random ID to prevent predictability.
			$id = uniqid();
		}

		// merge method name with our unique identifier
		$action = __METHOD__ . '.' . $id;

		/**
		 * Plugins can use this hook to change at runtime the action to
		 * use while creating/validating a WordPress nonce.
		 *
		 * @param 	string   $action  The action to filter.
		 *
		 * @since 	10.1.33
		 */
		return apply_filters('vik_csrf_token_action', $action);
	}

	/**
	 * Returns the name that will be used while generating the token input
	 * and during its validation.
	 *
	 * @return 	string  The input name.
	 */
	public static function getFormTokenName()
	{
		/**
		 * Plugins can use this hook to change at runtime the name to
		 * use while creating/validating a WordPress nonce.
		 *
		 * @param 	string   $name  The name to filter.
		 *
		 * @since 	10.1.33
		 */
		return apply_filters('vik_csrf_token_name', 'vikwp_nonce');
	}

	/**
	 * Method to determine a hash for anti-spoofing variable names.
	 *
	 * @param   boolean  $forceNew  If true, force a new token to be created.
	 *
	 * @return  string   Hashed var name.
	 *
	 * @uses 	getToken()
	 */
	public static function getFormToken($forceNew = false)
	{
		// create nonce by using our internal action
		return wp_create_nonce(static::getFormTokenAction($forceNew));
	}

	/**
	 * Checks for a form token in the request.
	 * Use with JHtml::fetch('form.token') or Session::getFormToken().
	 *
	 * @param   string   $method  The request method in which to look for the token key.
	 *
	 * @return  boolean  True if found and valid, false otherwise.
	 */
	public static function checkToken($method = 'post')
	{
		/**
		 * Plugins can use this hook to change the default behavior used to validate
		 * the CSRF tokens.
		 *
		 * @param 	boolean|null   $valid  True whether the token is valid, false in case
		 *                                 it is invalid, null to let the system uses its
		 *                                 own validation.
		 *
		 * @since 	10.1.33
		 */
		$validated = apply_filters('vik_csrf_token_check', null);
		
		if (!is_null($validated))
		{
			// A plugin validated the token itself.
			// Even if we cannot trust that validation, we have to return the status
			// fetched by the attached plugins.
			return (bool) $validated;
		}

		$action = static::getFormTokenAction();
		$app    = JFactory::getApplication();

		// check from header first, since AJAX request might specify the
		// X-CSRF-Token directive within the server headers
		$nonce = $app->input->server->get('HTTP_X_CSRF_TOKEN', '', 'alnum');
		
		if ($nonce && wp_verify_nonce($nonce, $action))
		{
			return true;
		}

		// get the name of the data set in request
		$name = static::getFormTokenName();

		// then fallback to HTTP query
		$nonce = $app->input->$method->get($name, '', 'alnum');

		if ($nonce && wp_verify_nonce($nonce, $action))
		{
			return true;
		}

		return false;
	}
}
