<?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; } }