<?php
/**
 * Move entire multisite into a subdirectory within its assigned domain
 */
class WPCOM_VIP_Multisite_Subdirectory_Rewrites {
	/**
	 * Singleton
	 */
	private static $__instance = null;

	public static function instance( $rewrite_root, $permalink_stub ) {
		// Instantiate the singleton
		if ( ! is_a( self::$__instance, __CLASS__ ) ) {
			self::$__instance = new self( $rewrite_root, $permalink_stub );
		}

		// Throw an error if subsequent instantiations use different arguments
		if ( $rewrite_root !== self::$__instance->rewrite_root || $permalink_stub !== self::$__instance->permalink_stub ) {
			_doing_it_wrong( 'WPCOM_VIP_Multisite_Subdirectory_Rewrites::instance', __CLASS__ . ' can only be instantiated with one set of arguments.', 1 );
			return false;
		}

		// Return this class's instance
		return self::$__instance;
	}

	/**
	 * Class properties
	 */
	private $rewrite_root        = null;
	private $permalink_stub      = null;
	private $permalink_structure = null;

	/**
	 * Set up new permalink structure and the hooks that implement it
	 *
	 * @param string $rewrite_root String to prefix all URLs with
	 * @param string $permalink_stub WordPress permalink structure, with leading slash
	 */
	private function __construct( $rewrite_root, $permalink_stub ) {
		// Abort if requisite VIP functions are missing
		if ( ! function_exists( 'wpcom_vip_load_permastruct' ) ) {
			_doing_it_wrong( 'WPCOM_VIP_Multisite_Subdirectory_Rewrites::instance', __CLASS__ . ' only works with the WordPress.com VIP environment. Be sure to include its init call before this function is used.', 1 );
			return;
		}

		// Set properties from the instantiation
		$this->rewrite_root = $rewrite_root;

		if ( 0 !== strpos( $permalink_stub, '/' ) ) {
			$permalink_stub = '/' . $permalink_stub;
		}
		$this->permalink_stub = $permalink_stub;

		$this->permalink_structure = $this->rewrite_root . $this->permalink_stub;

		// Bail if we don't have what's required
		if ( in_array( null, array( $this->rewrite_root, $this->permalink_stub, $this->permalink_structure ) ) ) {
			$this->rewrite_root = $this->permalink_stub = $this->permalink_structure = null;

			_doing_it_wrong( 'WPCOM_VIP_Multisite_Subdirectory_Rewrites::instance', __CLASS__ . ' requires two valid arguments.', 1 );
			return;
		}

		// Setting a static slug in the permalink structure takes care of most of this for us
		wpcom_vip_load_permastruct( '/' . $this->permalink_structure );

		// Force certain rules to automatically respect the prefix
		// Called several times because `WP_Rewrite` can overwrite this when other rules are added
		add_filter( 'after_setup_theme', array( $this, 'set_rewrite_root' ) );
		add_filter( 'wp_loaded',         array( $this, 'set_rewrite_root' ) );

		// Filter rules
		add_filter( 'rewrite_rules_array', array( $this, 'filter_rewrite_rules' ) );

		// Intercept some requests and update them to reflect the new structure
		add_action( 'parse_request', array( $this, 'parse_request' ) );

		// Override URL of static front-page, when applicable
		add_filter( 'page_link', array( $this, 'filter_page_link' ), 10, 2 );
		add_filter( 'redirect_canonical', array( $this, 'filter_canonical_redirect' ), 10, 2 );
	}

	/**
	 * Enforces custom URL prefix
	 *
	 * Uses Core functionality normally applied to sites whose permalink structures include `index.php`
	 */
	public function set_rewrite_root() {
		global $wp_rewrite;
		$wp_rewrite->root = $this->rewrite_root . '/';
	}

	/**
	 * Prepend all rules with the specified root
	 */
	public function filter_rewrite_rules( $rules ) {
		if ( ! is_array( $rules ) ) {
			return $rules;
		}

		$prefixed_rules = array();

		foreach( $rules as $key => $value ) {
			if ( 0 === strpos( $key, $this->rewrite_root ) ) {
				$prefixed_rules[ $key ] = $value;
			} else {
				$new_key                    = $this->rewrite_root . '/' . $key;
				$prefixed_rules[ $new_key ] = $value;
			}
		}

		return $prefixed_rules;
	}

	/**
	 * Special handling for the homepage, which is normally detected by a lack of request params
	 */
	public function parse_request( $request ) {
		// Display main archive at new location
		if ( $request->request === $this->rewrite_root ) {
			// Lack of query args is how WP detects the homepage
			$request->query_vars = array();
		}

		// Redirect requests to root
		if ( '' === $request->request ) {
			wp_safe_redirect( user_trailingslashit( home_url( $this->rewrite_root ) ), 301 );
			exit;
		}
	}

	/**
	 * Force static homepage to use new structure
	 *
	 * Core disobeys all other filtering and forces this to `home_url('/')` otherwise
	 */
	public function filter_page_link( $link, $_page_id ) {
		if ( 'page' === get_option( 'show_on_front' ) && (int) $_page_id === (int) get_option( 'page_on_front' ) ) {
			$link = user_trailingslashit( home_url( $this->rewrite_root ) );
		}

		return $link;
	}

	/**
	 * Correct canonical redirect for frontpage requests
	 */
	public function filter_canonical_redirect( $redirect_url, $requested_url ) {
		// Stop WP from sending front-page requests back to the root
		if ( is_front_page() && false !== stripos( $requested_url, $this->rewrite_root ) && false === stripos( $redirect_url, $this->rewrite_root ) ) {
			$parsed_redirect = parse_url( $redirect_url );
			$parsed_request  = parse_url( $requested_url );

			// Only deal with requests for the frontpage and any pagination thereunder
			if ( preg_match( "#^/{$this->rewrite_root}/?(page/([\d]+)/?)?#i", $parsed_request['path'] ) ) {
				$match   = '#^' . preg_quote( $parsed_redirect['scheme'] . '://' . $parsed_redirect[ 'host' ] . $parsed_redirect['path'] ) . '#i';
				$replace = user_trailingslashit( $parsed_redirect['scheme'] . '://' . $parsed_redirect['host'] . "/{$this->rewrite_root}" . $parsed_redirect['path'] );

				$redirect_url = preg_replace( $match, $replace, $redirect_url );
			}
		}

		return $redirect_url;
	}
}