<?php

/**
 * Nonce class
 *
 * @package WP Link Status
 * @subpackage Core
 */
class WPLNST_Core_Nonce {



	// Public methods
	// ----------------------------------------------------------------------------------------



	/**
	 * Create a new, user-independent, 24H valid nonce
	 */
	public static function create_nonce($action = '') {
		return substr(self::get_hash(self::nonce_tick().'|'.$action.'|'.__FILE__), -12, 10);
	}



	/*
	 * Check valid nonce
	 */
	public static function verify_nonce($nonce, $action = '') {

		// First check
		$nonce = (string) $nonce;
		if (empty($nonce)) {
			return false;
		}

		// Time based "tick"
		$tick = self::nonce_tick();

		// Nonce generated 0-12 hours ago
		$expected = substr(self::get_hash($tick.'|'.$action.'|'.__FILE__), -12, 10);
		if (self::hash_equals($expected, $nonce)) {
			return 1;
		}

		// Nonce generated 12-24 hours ago
		$expected = substr(self::get_hash(($tick - 1).'|'.$action.'|'.__FILE__), -12, 10);
		return self::hash_equals($expected, $nonce)? 2 : false;
	}



	/**
	 * Get system salt and hash data
	 */
	public static function get_hash($data) {
		$salt = self::get_salt();
		return self::hash_hmac('md5', $data, $salt);
	}



	/**
	 * Generates random number
	 **/
	public static function get_rand( $min = 0, $max = 0 ) {

		global $wplnst_rnd_value;

		// Reset $rnd_value after 14 uses
		// 32(md5) + 40(sha1) + 40(sha1) / 8 = 14 random numbers from $rnd_value
		if ( strlen($wplnst_rnd_value) < 8 ) {
			$wplnst_rnd_value = md5(uniqid(microtime().mt_rand(), true));
			$wplnst_rnd_value .= sha1($wplnst_rnd_value);
			$wplnst_rnd_value .= sha1($wplnst_rnd_value);
		}

		// Take the first 8 digits for our value
		$value = substr($wplnst_rnd_value, 0, 8);

		// Strip the first eight, leaving the remainder for the next call to wp_rand().
		$wplnst_rnd_value = substr($wplnst_rnd_value, 8);

		$value = abs(hexdec($value));

		// Some misconfigured 32bit environments (Entropy PHP, for example) truncate integers larger than PHP_INT_MAX to PHP_INT_MAX rather than overflowing them to floats.
		$max_random_number = 3000000000 === 2147483647 ? (float) "4294967295" : 4294967295; // 4294967295 = 0xffffffff

		// Reduce the value to be within the min - max range
		if ($max != 0) {
			$value = $min + ($max - $min + 1) * $value / ($max_random_number + 1);
		}

		return abs(intval($value));
	}



	/**
	 * Password generation
	 */
	public static function generate_password($length = 12, $special_chars = true, $extra_special_chars = false) {

		// Alphabet
		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

		// Add special
		if ($special_chars) {
			$chars .= '!@#$%^&*()';
		}

		// Extra chars
		if ($extra_special_chars) {
			$chars .= '-_ []{}<>~`+=,.;:/?|';
		}

		// Generate password
		$password = '';
		for ($i = 0; $i < $length; $i++) {
			$password .= substr($chars, self::get_rand(0, strlen($chars) - 1), 1);
		}

		// Done
		return $password;
	}



	/**
	 * Check and creates new salt file
	 */
	public static function check_salt_file() {
		return self::salt_file_ready()? true : self::create_salt_file();
	}



	/**
	 * Check salt file and constant
	 */
	public static function salt_file_ready() {

		// Check if file exists
		$path = self::salt_file_path();
		if (!@file_exists($path) || !@is_readable($path)) {
			return false;
		}

		// Require
		@require_once($path);
		return defined('WPLNST_SALT');
	}



	/**
	 * Attempt to create a salt file
	 */
	public static function create_salt_file() {

		// Check directory permissions
		$path = self::salt_file_path();
		if (!@is_writable(dirname($path))) {
			return false;
		}

		// Attempt to write the file
		$result = @file_put_contents($path, "<?php /* Generated by WP Link Status: PLEASE DO NOT REMOVE OR MODIFY THIS FILE */"."\n"."define('WPLNST_SALT', '".self::generate_password(64, false, false)."');");

		// Set permissions
		@chmod($path, 0644);

		// Done
		return (false !== $result && @file_exists($path) && @is_readable($path));
	}



	/**
	 * Remove custom salt file
	 */
	public static function remove_salt_file() {
		$path = self::salt_file_path();
		if (@file_exists($path)) {
			@unlink($path);
		}
	}



	// Internal functions
	// ----------------------------------------------------------------------------------------



	/**
	 * Creates a "tick", time based
	 * This is a clone of WP nonce_tick function without constants and filters
	 */
	private static function nonce_tick() {
		return ceil(time()/(86400/2));
	}



	/**
	 * Unique salt to hash strings
	 */
	private static function get_salt() {

		// Process cache
		static $cached_salt;
		if (isset($cached_salt)) {
			return $cached_salt;
		}

		// Salt from file
		$salt = self::get_salt_stored();
		if (empty($salt)) {
			$salt = php_uname().'|'.phpversion().'|'.__FILE__;
		}

		// Store hash
		$cached_salt = md5($salt);
		return $cached_salt;
	}



	/**
	 * Return salt from config file
	 */
	private static function get_salt_stored() {

		// Require file
		if (!defined('WPLNST_SALT')) {
			self::require_salt_file();
		}

		// Check constant
		return defined('WPLNST_SALT')? WPLNST_SALT : false;
	}



	/**
	 * Check if file exists and require config file
	 */
	private static function require_salt_file() {

		// Check if file exists
		$path = self::salt_file_path();
		if (!@file_exists($path) || !@is_readable($path)) {
			return false;
		}

		// Require
		@require_once($path);
	}



	/**
	 * Salt path and filename
	 * Yest, it is very ugly
	 */
	private static function salt_file_path() {
		return dirname(dirname(dirname(dirname(dirname(__FILE__))))).'/wp-link-status-salt.php';
	}



	/**
	 * Wrapper for hash_hmac function
	 */
	private static function hash_hmac($algo, $data, $key, $raw_output = false) {

		// Check existing function
		if (function_exists('hash_hmac')) {
			return hash_hmac($algo, $data, $key, $raw_output);

		// From WP
		} else {

			$packs = array('md5' => 'H32', 'sha1' => 'H40');

			if (!isset($packs[$algo])) {
				return false;
			}

			$pack = $packs[$algo];

			if (strlen($key) > 64) {
				$key = pack($pack, $algo($key));
			}

			$key = str_pad($key, 64, chr(0));

			$ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64));
			$opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64));

			$hmac = $algo($opad.pack($pack, $algo($ipad.$data)));

			if ($raw_output) {
				return pack($pack, $hmac);
			}

			return $hmac;
		}
	}



	/**
	 * Wrapper for hash_equals function
	 */
	private static function hash_equals($a, $b) {

		// Check existing function
		if (function_exists('hash_equals')) {
			return hash_equals($a, $b);

		// From WP
		} else {

			$a_length = strlen($a);
			if ($a_length !== strlen($b)) {
				return false;
			}

			$result = 0;

			// Do not attempt to "optimize" this.
			for ($i = 0; $i < $a_length; $i++) {
				$result |= ord($a[$i]) ^ ord($b[$i]);
			}

			return $result === 0;
		}
	}



}