<?php

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
/**
* Optimisationio_CacheEnablerDisk
*
* @since 1.0.0
*/

class Optimisationio_CacheEnablerDisk {


	/**
	* cached filename settings
	*
	* @since  	1.0.7
	* @change 	1.0.7
	*
	* @var    string
	*/

	const FILE_HTML = 'index.html';
	const FILE_GZIP = 'index.html.gz';
	const FILE_WEBP_HTML = 'index-webp.html';
	const FILE_WEBP_GZIP = 'index-webp.html.gz';


	/**
	* permalink check
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @return  boolean  true if installed
	*/

	public static function is_permalink() {
		return get_option('permalink_structure');
	}


	/**
	* store asset
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @param   string   $data	content of the asset
	*/

	public static function store_asset($data) {

		// check if empty
		if ( empty($data) ) {
			wp_die('Asset is empty.');
		}

		// save asset
		self::_create_files(
			$data
		);

	}


	/**
	* check asset
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @return  boolean  true if asset exists
	*/

	public static function check_asset() {
		return is_readable(
			self::_file_html()
		);
	}


	/**
	* check expiry
	*
	* @since   1.0.1
	* @change  1.0.1
	*
	* @return  boolean  true if asset expired
	*/

	public static function check_expiry() {

		// cache enabler options
		$options = Optimisationio_CacheEnabler::$options;

		// check if expires is active
		if ($options['cache_expires'] == 0) {
			return false;
		}

		$now = time();
		$expires_seconds = 3600*$options['cache_expires'];

		// check if asset has expired
		if ( ( filemtime(self::_file_html()) + $expires_seconds ) <= $now ) {
			return true;
		}

		return false;

	}


	/**
	* delete asset
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @param   string   $url   url of cached asset
	*/

	public static function delete_asset($url) {

		// check if url empty
		if ( empty($url) ) {
			wp_die('URL is empty.');
		}

		// delete
		self::_clear_dir(
			self::_file_path($url)
		);
	}


	/**
	* clear cache
	*
	* @since   1.0.0
	* @change  1.0.0
	*/

	public static function clear_cache() {
		self::_clear_dir(
			Optimisationio_CACHE_DIR
		);
	}


	/**
	* clear home cache
	*
	* @since   1.0.7
	* @change  1.0.9
	*/

	public static function clear_home() {
		$path = sprintf(
			'%s%s%s%s',
			Optimisationio_CACHE_DIR,
			DIRECTORY_SEPARATOR,
			preg_replace('#^https?://#', '', get_option('siteurl')),
			DIRECTORY_SEPARATOR
		);

		wp_delete_file($path.self::FILE_HTML);
		wp_delete_file($path.self::FILE_GZIP);
		wp_delete_file($path.self::FILE_WEBP_HTML);
		wp_delete_file($path.self::FILE_WEBP_GZIP);
	}


	/**
	* get asset
	*
	* @since   1.0.0
	* @change  1.0.9
	*/

	public static function get_asset() {

		// Serving a pre-generated cache file: readfile() streaming has no
		// WP_Filesystem equivalent and this is a front-end hot path. The request
		// headers below are only used for cache content negotiation (compared via
		// strtotime()/strpos()), not stored or echoed.
		// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_readfile, WordPress.Security.ValidatedSanitizedInput

		// set cache handler header
		header('x-cache-handler: php');

		// get if-modified request headers
		if ( function_exists( 'apache_request_headers' ) ) {
			$headers = apache_request_headers();
			$http_if_modified_since = ( isset( $headers[ 'If-Modified-Since' ] ) ) ? $headers[ 'If-Modified-Since' ] : '';
			$http_accept = ( isset( $headers[ 'Accept' ] ) ) ? $headers[ 'Accept' ] : '';
			$http_accept_encoding = ( isset( $headers[ 'Accept-Encoding' ] ) ) ? $headers[ 'Accept-Encoding' ] : '';
		} else {
			$http_if_modified_since = ( isset( $_SERVER[ 'HTTP_IF_MODIFIED_SINCE' ] ) ) ? $_SERVER[ 'HTTP_IF_MODIFIED_SINCE' ] : '';
			$http_accept = ( isset( $_SERVER[ 'HTTP_ACCEPT' ] ) ) ? $_SERVER[ 'HTTP_ACCEPT' ] : '';
			$http_accept_encoding = ( isset( $_SERVER[ 'HTTP_ACCEPT_ENCODING' ] ) ) ? $_SERVER[ 'HTTP_ACCEPT_ENCODING' ] : '';
		}

		// check modified since with cached file and return 304 if no difference
		if ( $http_if_modified_since && ( strtotime( $http_if_modified_since ) == filemtime( self::_file_html() ) ) ) {
			header( $_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304 );
			exit;
		}

		// check webp and deliver gzip webp file if support
		if ( $http_accept && ( strpos($http_accept, 'webp') !== false ) ) {
			if ( is_readable( self::_file_webp_gzip() ) ) {
				header('Content-Encoding: gzip');
				readfile( self::_file_webp_gzip() );
				exit;
			} elseif ( is_readable( self::_file_webp_html() ) ) {
				readfile( self::_file_webp_html() );
				exit;
			}
		}

		// check encoding and deliver gzip file if support
		if ( $http_accept_encoding && ( strpos($http_accept_encoding, 'gzip') !== false ) && is_readable( self::_file_gzip() )  ) {
			header('Content-Encoding: gzip');
			readfile( self::_file_gzip() );
			exit;
		}

		// deliver cached file (default)
		readfile( self::_file_html() );
		// phpcs:enable
		exit;
	}


	/**
	* create signature
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @return  string  signature
	*/

	public static function _cache_signatur() {
		return sprintf(
			"\n\n<!-- %s @ %s",
			'Cache Enabler by KeyCDN',
			date_i18n(
				'd.m.Y H:i:s',
				current_time('timestamp')
			)
		);
	}


	/**
	* create files
	*
	* @since   1.0.0
	* @change  1.1.1
	*
	* @param   string  $data  html content
	*/

	public static function _create_files($data) {

		// create folder
		if ( ! wp_mkdir_p( self::_file_path() ) ) {
			wp_die('Unable to create directory.');
		}

		// get base signature
		$cache_signature = self::_cache_signatur();

		// cache enabler options
		$options = Optimisationio_CacheEnabler::$options;

		// create files
		self::_create_file( self::_file_html(), $data.$cache_signature." (html) -->" );

		// create pre-compressed file
		if ($options['cache_compress']) {
			self::_create_file( self::_file_gzip(), gzencode($data.$cache_signature." (html gzip) -->", 9) );
		}

		// create webp supported files
		if ($options['cache_webp']) {
			// magic regex rule
			$regex_rule = '#(?<=(?:(ref|src|set)=[\"\']))(?:http[s]?[^\"\']+)(\.png|\.jp[e]?g)(?:[^\"\']+)?(?=[\"\')])#';

			// call the webp converter callback
			$converted_data = preg_replace_callback($regex_rule, array(__CLASS__, '_convert_webp'), $data);

			self::_create_file( self::_file_webp_html(), $converted_data.$cache_signature." (webp) -->" );

			// create pre-compressed file
			if ($options['cache_compress']) {
				self::_create_file( self::_file_webp_gzip(), gzencode($converted_data.$cache_signature." (webp gzip) -->", 9) );
			}
		}

	}


	/**
	* create file
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @param   string  $file  file path
	* @param   string  $data  content of the html
	*/

	public static function _create_file($file, $data) {

		// Writing cache files on the front-end hot path; WP_Filesystem needs
		// credentials and is not available here.
		// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fclose, WordPress.WP.AlternativeFunctions.file_system_operations_chmod

		// open file handler
		if ( ! $handle = @fopen($file, 'wb') ) {
			wp_die('Can not write to file.');
		}

		// write
		@fwrite($handle, $data);
		fclose($handle);
		clearstatcache();

		// set permissions
		$stat = @stat( dirname($file) );
		$perms = $stat['mode'] & 0007777;
		$perms = $perms & 0000666;
		@chmod($file, $perms);
		clearstatcache();
		// phpcs:enable
	}


	/**
	* clear directory
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @param   string  $dir  directory
	*/

	public static function _clear_dir($dir) {

		// remove slashes
		$dir = untrailingslashit($dir);

		// check if dir
		if ( ! is_dir($dir) ) {
			return;
		}

		// get dir data
		$objects = array_diff(
			scandir($dir),
			array('..', '.')
		);

		if ( empty($objects) ) {
			return;
		}

		foreach ( $objects as $object ) {
			// full path
			$object = $dir. DIRECTORY_SEPARATOR .$object;

			// check if directory
			if ( is_dir($object) ) {
				self::_clear_dir($object);
			} else {
				wp_delete_file($object);
			}
		}

		// delete
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- removing an emptied cache directory; no WP_Filesystem on this path.
		@rmdir($dir);

		// clears file status cache
		clearstatcache();
	}


	/**
	* get cache size
	*
	* @since   1.0.0
	* @change  1.0.0
	*
	* @param   string  $dir   folder path
	* @return  mixed   $size  size in bytes
	*/

	public static function cache_size($dir = '.') {

		// check if not dir
		if ( ! is_dir($dir) ) {
			return;
		}

		// get dir data
		$objects = array_diff(
			scandir($dir),
			array('..', '.')
		);

		if ( empty($objects) ) {
			return;
		}

		$size = 0;

		foreach ( $objects as $object ) {
			// full path
			$object = $dir. DIRECTORY_SEPARATOR .$object;

			// check if dir
			if ( is_dir($object) ) {
				$size += self::cache_size($object);
			} else {
				$size += filesize($object);
			}
		}

		return $size;
	}


	/**
	* cache path
	*
	* @since   1.0.0
	* @change  1.1.0
	*
	* @param   string  $path  uri or permlink
	* @return  string  $diff  path to cached asset
	*/

	public static function _file_path($path = NULL) {

		// Sanitise the host (strip anything that is not a valid host character).
		$host = isset($_SERVER['HTTP_HOST']) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) : '';
		$host = preg_replace('/[^a-z0-9\-\.:]/', '', $host);

		$request_uri = ( $path ? $path : ( isset($_SERVER['REQUEST_URI']) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '' ) );
		$uri_path    = (string) wp_parse_url( $request_uri, PHP_URL_PATH );

		// Reject path traversal attempts.
		if ( strpos( $uri_path, '..' ) !== false ) {
			wp_die('Path is not valid.');
		}

		$path = sprintf(
			'%s%s%s%s',
			Optimisationio_CACHE_DIR,
			DIRECTORY_SEPARATOR,
			$host,
			$uri_path
		);

		if ( is_file($path) > 0 ) {
			wp_die('Path is not valid.');
		}

		return trailingslashit($path);
	}


	/**
	* get file path
	*
	* @since   1.0.0
	* @change  1.0.7
	*
	* @return  string  path to the html file
	*/

	public static function _file_html() {
		return self::_file_path(). self::FILE_HTML;
	}


	/**
	* get gzip file path
	*
	* @since   1.0.1
	* @change  1.0.7
	*
	* @return  string  path to the gzipped html file
	*/

	public static function _file_gzip() {
		return self::_file_path(). self::FILE_GZIP;
	}


	/**
	* get webp file path
	*
	* @since   1.0.7
	* @change  1.0.7
	*
	* @return  string  path to the webp html file
	*/

	public static function _file_webp_html() {
		return self::_file_path(). self::FILE_WEBP_HTML;
	}


	/**
	* get gzip webp file path
	*
	* @since   1.0.1
	* @change  1.0.7
	*
	* @return  string  path to the webp gzipped html file
	*/

	public static function _file_webp_gzip() {
		return self::_file_path(). self::FILE_WEBP_GZIP;
	}


	/**
	* convert to webp
	*
	* @since   1.0.1
	* @change  1.1.1
	*
	* @return  string  converted HTML file
	*/

	public static function _convert_webp($asset) {

		if ($asset[1] == 'src') {
			return self::_convert_webp_src($asset[0]);
		} elseif ($asset[1] == 'ref') {
			return self::_convert_webp_src($asset[0]);
		} elseif ($asset[1] == 'set') {
			return self::_convert_webp_srcset($asset[0]);
		}

		return $asset[0];

	}


	/**
	* convert src to webp source
	*
	* @since   1.0.1
	* @change  1.1.0
	*
	* @return  string  converted src webp source
	*/

	public static function _convert_webp_src($src) {
		$upload_dir = wp_upload_dir();
		$src_url = wp_parse_url($upload_dir['baseurl']);
		$upload_path = $src_url['path'];

		if ( strpos($src, $upload_path) !== false ) {

			$src_webp = str_replace('.jpg', '.webp', $src);
			$src_webp = str_replace('.jpeg', '.webp', $src_webp);
			$src_webp = str_replace('.png', '.webp', $src_webp);

			$parts = explode($upload_path, $src_webp);
			$relative_path = $parts[1];

			// check if relative path is not empty and file exists
			if ( !empty($relative_path) && file_exists($upload_dir['basedir'].$relative_path) ) {
				return $src_webp;
			} else {
				// try appended webp extension
				$src_webp_appended = $src.'.webp';
				$parts_appended = explode($upload_path, $src_webp_appended);
				$relative_path_appended = $parts_appended[1];

				// check if relative path is not empty and file exists
				if ( !empty($relative_path_appended) && file_exists($upload_dir['basedir'].$relative_path_appended) ) {
					return $src_webp_appended;
				}
			}

		}

		return $src;
	}


	/**
	* convert srcset to webp source
	*
	* @since   1.0.8
	* @change  1.1.0
	*
	* @return  string  converted srcset webp source
	*/

	public static function _convert_webp_srcset($srcset) {

		$sizes = explode(', ', $srcset);
		$upload_dir = wp_upload_dir();
		$src_url = wp_parse_url($upload_dir['baseurl']);
		$upload_path = $src_url['path'];

		for ($i=0; $i<count($sizes); $i++) {

			if ( strpos($sizes[$i], $upload_path) !== false ) {

				$src_webp = str_replace('.jpg', '.webp', $sizes[$i]);
				$src_webp = str_replace('.jpeg', '.webp', $src_webp);
				$src_webp = str_replace('.png', '.webp', $src_webp);

				$size_parts = explode(' ', $src_webp);
				$parts = explode($upload_path, $size_parts[0]);
				$relative_path = $parts[1];

				// check if relative path is not empty and file exists
				if ( !empty($relative_path) && file_exists($upload_dir['basedir'].$relative_path) ) {
					$sizes[$i] = $src_webp;
				} else {
					// try appended webp extension
					$size_parts_appended = explode(' ', $sizes[$i]);
					$src_webp_appended = $size_parts_appended[0].'.webp';
					$parts_appended = explode($upload_path, $src_webp_appended);
					$relative_path_appended = $parts_appended[1];
					$src_webp_appended = $src_webp_appended.' '.$size_parts_appended[1];

					// check if relative path is not empty and file exists
					if ( !empty($relative_path_appended) && file_exists($upload_dir['basedir'].$relative_path_appended) ) {
						$sizes[$i] = $src_webp_appended;
					}
				}

			}

		}

		$srcset = implode(', ', $sizes);

		return $srcset;
	}

}
