<?php
namespace AIOWPS\Firewall;

class Utility {

	/**
	 * Returns the directory of where the WordPress files are installed
	 * This differs from get_root_dir() when WordPress is setup in a subdirectory
	 *
	 * @return string
	 */
	public static function get_wordpress_dir() {

		if (Context::wordpress_safe()) {
			return wp_normalize_path(ABSPATH);
		}

		global $aiowps_firewall_data;

		return isset($aiowps_firewall_data['ABSPATH']) ? $aiowps_firewall_data['ABSPATH'] : '';
	}

	/**
	 * Returns the root directory of the site
	 * This may be different from where the WordPress files are installed if WordPress is setup in a subdirectory
	 *
	 * @return string|null
	 */
	public static function get_root_dir() {
		
		if (Context::wordpress_safe()) {
			return \AIOWPSecurity_Utility_File::get_home_path();
		}

		// We're in the firewall context here, so get the root directory from the bootstrap file path
		$includes = get_included_files();

		foreach ($includes as $file) {
			if (preg_match('/aios-bootstrap\.php$/', $file)) {
				return self::normalize_path(dirname($file).'/');
			}
		}

		return null;
	}

	/**
	 * Normalizes the file path
	 *
	 * @see https://developer.wordpress.org/reference/functions/wp_normalize_path/
	 * @param string $path
	 * @return string
	 */
	public static function normalize_path($path) {
		// Standardize all paths to use '/'.
		$path = str_replace('\\', '/', $path);
	
		// Replace multiple slashes down to a singular, allowing for network shares having two slashes.
		$path = preg_replace('|(?<=.)/+|', '/', $path);
	
		// Windows paths should uppercase the drive letter.
		if (':' === substr($path, 1, 1)) {
			$path = ucfirst($path);
		}
	
		return $path;
	}

	/**
	 * Returns the path to wp-config.php
	 *
	 * @param string $root - Where to look for wp-config.php file
	 * @return string
	 */
	public static function get_wpconfig_path($root = '') {

		if (empty($root)) $root = self::get_wordpress_dir();
		
		$wp_config_file = $root . 'wp-config.php';
		if (file_exists($wp_config_file)) {
			return $wp_config_file;
		} elseif (file_exists(dirname($root) . '/wp-config.php')) {
			return dirname($root) . '/wp-config.php';
		}
		return $wp_config_file;
	}

	
	/**
	 * Recursive directory creation based on full path
	 *
	 * @see https://developer.wordpress.org/reference/functions/wp_mkdir_p/
	 * @param string $target
	 * @return bool
	 */
	public static function wp_mkdir_p($target) {
		$wrapper = null;
	
		// Strip the protocol.
		if (self::wp_is_stream($target)) {
			list($wrapper, $target) = explode('://', $target, 2);
		}
	
		// From php.net/mkdir user contributed notes.
		$target = str_replace('//', '/', $target);
	
		// Put the wrapper back on the target.
		if (null !== $wrapper) {
			$target = $wrapper . '://' . $target;
		}
	
		/*
		 * Safe mode fails with a trailing slash under certain PHP versions.
		 * Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
		 */
		$target = rtrim($target, '/');
		if (empty($target)) {
			$target = '/';
		}
	
		if (file_exists($target)) {
			return @is_dir($target);
		}
	
		// Do not allow path traversals.
		if (false !== strpos($target, '../') || false !== strpos($target, '..' . DIRECTORY_SEPARATOR)) {
			return false;
		}
	
		// We need to find the permissions of the parent folder that exists and inherit that.
		$target_parent = dirname($target);
		while ('.' !== $target_parent && ! is_dir($target_parent) && dirname($target_parent) !== $target_parent) {
			$target_parent = dirname($target_parent);
		}
	
		// Get the permission bits
		$stat = @stat($target_parent);
		if ($stat) {
			$dir_perms = $stat['mode'] & 0007777;
		} else {
			$dir_perms = 0777;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- PCP error. WP not loaded. WP API not available.
		if (@mkdir($target, $dir_perms, true)) {
	
			/*
			 * If a umask is set that modifies $dir_perms, we'll have to re-set
			 * the $dir_perms correctly with chmod()
			 */
			if (($dir_perms & ~umask()) != $dir_perms) {
				$folder_parts = explode('/', substr($target, strlen($target_parent) + 1));
				for ($i = 1, $c = count($folder_parts); $i <= $c; $i++) {
					// phpcs:ignore WordPress.WP.AlternativeFunctions -- PCP error. WP not loaded. WP API not available.
					chmod($target_parent . '/' . implode('/', array_slice($folder_parts, 0, $i)), $dir_perms);
				}
			}
	
			return true;
		}
	
		return false;
	}
	

	/**
	 * Tests if a given path is a stream URL
	 *
	 * @see https://developer.wordpress.org/reference/functions/wp_is_stream/
	 * @param string $path
	 * @return bool
	 */
	public static function wp_is_stream($path) {
		$scheme_separator = strpos($path, '://');
	
		if (false === $scheme_separator) {
			// $path isn't a stream.
			return false;
		}
	
		$stream = substr($path, 0, $scheme_separator);
	
		return in_array($stream, stream_get_wrappers(), true);
	}


	/**
	 * Attempts to give us access to the $wpdb object from the firewall.
	 * This should only be used when you're sure WordPress will not be loading after the firewall.
	 *
	 * @return bool
	 */
	public static function attempt_to_access_wpdb() {
		
		// wpdb is already accessible
		if (isset($GLOBALS['wpdb'])) return true;

		$wp_path = self::get_wordpress_dir() . 'wp-load.php';
			
		clearstatcache();
		if (!file_exists($wp_path)) return false;

		define('SHORTINIT', true);
	
		$included = (bool) include $wp_path;
	
		global $wpdb;

		// If $wpdb is inaccessible by this point, it means loading wp-settings didn't complete.
		// So we have to manually include the wp-config (which includes wp-settings) for it to complete.
		if (empty($wpdb) && $included) include self::get_wpconfig_path();
		
		global $wpdb;

		return !empty($wpdb);
		
	}
}
