Last active
October 21, 2016 13:34
-
-
Save ddebernardy/10529951 to your computer and use it in GitHub Desktop.
SubdirInstall component for WordPress
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Mesoconcepts\WordPress\Plugin\BugFixes\Component; | |
use Mesoconcepts\WordPress\Bridge\Component\WordPressInterface; | |
use Mesoconcepts\WordPress\Bridge\DependencyInjection\EagerLoadedInterface; | |
use Mesoconcepts\WordPress\Bridge\DependencyInjection\EventSubscriberInterface; | |
/** | |
* Subdir install fixes -- part of MIT-licensed `mesoconcepts/mc-bug-fixes` plugin | |
* | |
* @author Denis de Bernardy | |
*/ | |
class SubdirInstall implements EagerLoadedInterface, EventSubscriberInterface | |
{ | |
/** | |
* WP folder reference | |
* | |
* @var string | |
*/ | |
protected static $wp_dir; | |
/** | |
* Whether to override the WP url or not | |
* | |
* @var boolean | |
*/ | |
protected $override_site_url = true; | |
/** | |
* The number of active buffers | |
* | |
* @var int | |
*/ | |
protected $buffers = 0; | |
/** | |
* @var array | |
*/ | |
protected $cookie_prefs = array(); | |
/** | |
* @var WordPressInterface | |
*/ | |
protected $wp; | |
/** | |
* Constructor | |
* | |
* @param WordPressInterface $wp | |
*/ | |
public function __construct(WordPressInterface $wp) | |
{ | |
$this->wp = $wp; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function boot() | |
{ | |
$this->setCookieHash(); | |
$this->getWPDir(); | |
$this->maybeRegisterDefaultThemeDir(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getSubscribedEvents() | |
{ | |
return array( | |
'option_siteurl' => array('overrideSiteUrl', ~PHP_INT_MAX), | |
'mod_rewrite_rules' => array('fixModRewriteRules', PHP_INT_MAX), | |
'flush_rewrite_rules_hard' => array('fixHtaccessFileLocation', PHP_INT_MAX), | |
'load-options-general.php' => 'obStartRestoreSiteUrlOption', | |
'admin_footer-options-general.php' => 'obFlushRestoreSiteUrlOption', | |
'secure_logged_in_cookie' => array('catchCookiePrefs', PHP_INT_MAX, 3), | |
'set_auth_cookie' => array('setAuthCookie', PHP_INT_MAX, 5), | |
'set_logged_in_cookie' => array('setLoggedInCookie', PHP_INT_MAX, 5), | |
'clear_auth_cookie' => 'clearAuthCookie', | |
); | |
} | |
/** | |
* Conditionally registers the default wp-cotent/themes folder | |
* | |
* @see https://core.trac.wordpress.org/ticket/24143 | |
*/ | |
protected function maybeRegisterDefaultThemeDir() | |
{ | |
# Bail if a default theme is defined - assume it's in WP_CONTENT_DIR | |
if ($this->wp->getDefaultTheme()) return; | |
# Bail if no custom content folder is defined | |
if ($this->wp->getContentDir().'/themes' != $this->wp->getWPDir().'/wp-content/themes') { | |
$this->wp->registerThemeDir($this->wp->getWPDir().'/wp-content/themes'); | |
} | |
} | |
/** | |
* Sets the WP COOKIEHASH constant | |
* | |
* This is needed to prevent wild redirects and auth cookie invalidation | |
* when overriding the site_url. | |
*/ | |
protected function setCookieHash() | |
{ | |
$site_url = $this->wp->getNetworkOption('siteurl'); | |
$cookie_hash = $site_url ? md5($site_url) : ''; | |
$this->wp->setCookieHash($cookie_hash); | |
} | |
/** | |
* Gets the raw WP dir | |
* | |
* @return string $wp_dir | false on failure | null when unknown | |
*/ | |
protected function getWPDir() | |
{ | |
# Do this only once | |
if (isset(static::$wp_dir)) { | |
return static::$wp_dir; | |
} | |
# Fetch the raw WP urls | |
$home_url = $this->wp->getOption('home'); | |
$site_url = $this->wp->getOption('siteurl'); | |
# Bail if we're not installed yet | |
if (!$home_url || !$site_url) { | |
return null; | |
} | |
# Fetch the relative url paths from the site root | |
$home_dir = basename(parse_url($home_url, PHP_URL_PATH)); | |
$site_dir = basename(parse_url($site_url, PHP_URL_PATH)); | |
# Normalize the two in case WP *and* the site's url are in subfolders | |
if ($home_dir !== '' && strpos($site_dir, $home_dir.'/') === 0) { | |
$site_dir = substr($site_dir, strlen($home_dir.'/')); | |
$home_dir = ''; | |
} | |
# If site_dir is a subdir of home_dir, we can manage | |
if ($home_dir === '' && $site_dir !== '') { | |
static::$wp_dir = $site_dir; | |
} | |
# Anything else, we don't want to even try | |
else { | |
static::$wp_dir = false; | |
} | |
return static::$wp_dir; | |
} | |
/** | |
* Catches whether to send secure auth and logged in cookies or not | |
* | |
* @param boolean $secure_logged_in | |
* @param int $user_id | |
* @param boolean $secure | |
* @return $secure_logged_in | |
*/ | |
public function catchCookiePrefs($secure_logged_in, $user_id, $secure) | |
{ | |
if (!static::$wp_dir) return; | |
$this->cookie_prefs[$user_id] = array( | |
'secure_auth' => $secure, | |
'secure_logged_in' => $secure_logged_in, | |
); | |
return $secure_logged_in; | |
} | |
/** | |
* Sets an additional logged in cookie when WP is in a subfolder | |
* | |
* @param string $auth_cookie | |
* @param boolean $expire | |
* @param int $expiration | |
* @param int $user_id | |
* @param string $scheme | |
*/ | |
public function setAuthCookie($auth_cookie, $expire, $expiration, $user_id, $scheme) | |
{ | |
if (!static::$wp_dir) return; | |
if (!isset($this->cookie_prefs[$user_id])) return; | |
# Bail on non-standard admin admin cookie path | |
if ($this->wp->getAdminCookiePath() != $this->wp->getWPCookiePath().'wp-admin') return; | |
$cookie_path = false; | |
if ($this->wp->getWPCookiePath() == $this->wp->getCookiePath()) { | |
$cookie_path = $this->wp->getCookiePath().static::$wp_dir.'/wp-admin'; | |
} | |
elseif ($this->wp->getWPCookiePath() == $this->wp->getCookiePath().static::$wp_dir.'/') { | |
$cookie_path = $this->wp->getCookiePath().'wp-admin'; | |
} | |
# Bail on non-standard cookie paths | |
if (!$cookie_path) return; | |
extract($this->cookie_prefs[$user_id]); | |
$auth_cookie_name = $scheme == 'secure_auth' | |
? $this->wp->getSecureAuthCookieName() | |
: $this->wp->getAuthCookieName(); | |
setcookie( | |
$auth_cookie_name, | |
$auth_cookie, | |
$expire, | |
$cookie_path, | |
$this->wp->getCookieDomain(), | |
$secure_auth, | |
true | |
); | |
} | |
/** | |
* Sets an additional logged in cookie when WP is in a subfolder | |
* | |
* @param string $logged_in_cookie | |
* @param boolean $expire | |
* @param int $expiration | |
* @param int $user_id | |
* @param string $scheme | |
*/ | |
public function setLoggedInCookie($logged_in_cookie, $expire, $expiration, $user_id, $scheme) | |
{ | |
if (!static::$wp_dir) return; | |
if (!isset($this->cookie_prefs[$user_id])) return; | |
extract($this->cookie_prefs[$user_id]); | |
if ($this->wp->getWPCookiePath() == $this->wp->getCookiePath()) { | |
setcookie( | |
$this->wp->getLoggedInCookieName(), | |
$logged_in_cookie, | |
$expire, | |
$this->wp->getCookiePath().static::$wp_dir.'/', | |
$this->wp->getCookieDomain(), | |
$secure_logged_in, | |
true | |
); | |
} | |
} | |
/** | |
* Clears auth cookies | |
*/ | |
public function clearAuthCookie() | |
{ | |
if (!static::$wp_dir) return; | |
# Bail on non-standard admin admin cookie path | |
if ($this->wp->getAdminCookiePath() != $this->wp->getWPCookiePath().'wp-admin') return; | |
if ($this->wp->getWPCookiePath() == $this->wp->getCookiePath()) { | |
$reset = array( | |
$this->wp->getAuthCookieName() => $this->wp->getCookiePath().static::$wp_dir.'/wp-admin', | |
$this->wp->getSecureAuthCookieName() => $this->wp->getCookiePath().static::$wp_dir.'/wp-admin', | |
$this->wp->getLoggedInCookieName() => $this->wp->getCookiePath().static::$wp_dir.'/', | |
); | |
} | |
elseif ($this->wp->getWPCookiePath() == $this->wp->getCookiePath().static::$wp_dir.'/') { | |
$reset = array( | |
$this->wp->getAuthCookieName() => $this->wp->getCookiePath().'wp-admin', | |
$this->wp->getSecureAuthCookieName() => $this->wp->getCookiePath().'wp-admin', | |
); | |
} | |
$year_ago = time() - 365 * 24 * 60 * 60; | |
foreach ($reset as $name => $path) { | |
setcookie($name, ' ', $year_ago, $path, $this->wp->getCookieDomain()); | |
} | |
} | |
/** | |
* Overrides the site url when permalinks are enabled | |
* | |
* @param string $site_url | |
* @return string $site_url | |
*/ | |
public function overrideSiteUrl($site_url) | |
{ | |
# Bail if not applicable | |
if (!$this->override_site_url || !static::$wp_dir) { | |
return $site_url; | |
} | |
# Bail if not installed yet | |
$home_url = $this->wp->getOption('home'); | |
if (!$home_url || !$site_url) { | |
return $site_url; | |
} | |
# Conditionally override the WP url | |
if ($this->wp->isUsingPermalinks() && !$this->wp->isUsingIndexPermalinks()) { | |
$site_url = $home_url; | |
} | |
return $site_url; | |
} | |
/** | |
* Fixes rewrite rules when WP is installed in its own subfolder | |
* | |
* @see https://core.trac.wordpress.org/ticket/23221 | |
* | |
* @param array $rules | |
* @return array $rules | |
*/ | |
public function fixModRewriteRules($rules) | |
{ | |
if ($this->wp->isMultisite()) { | |
return $rules; | |
} | |
extract($this->getModRewriteDirs()); | |
extract($this->getModRewriteRules()); | |
$find = array(); | |
$repl = array(); | |
$find[] = <<<EOS | |
RewriteRule ^index\.php$ - [L] | |
EOS; | |
$repl[] = <<<EOS | |
$rewrite_index | |
EOS; | |
$find[] = <<<EOS | |
RewriteCond %{REQUEST_FILENAME} !-f | |
RewriteCond %{REQUEST_FILENAME} !-d | |
EOS; | |
$repl[] = <<<EOS | |
$rewrite_file | |
EOS; | |
if ($wp_dir) { | |
$find[] = <<<EOS | |
RewriteRule . $home_dir/index.php [L] | |
EOS; | |
$repl[] = <<<EOS | |
$rewrite_admin | |
RewriteRule . $home_dir/index.php [L] | |
EOS; | |
} | |
$rules = str_replace($find, $repl, $rules); | |
return $rules; | |
} | |
/** | |
* Makes WP write to the correct htaccess file's location | |
* | |
* @see https://core.trac.wordpress.org/ticket/23221 | |
* | |
* @param boolean $hard | |
* @return boolean $hard | |
*/ | |
public function fixHtaccessFileLocation($hard) | |
{ | |
# Hope for the best when WP will do the right thing | |
if (!$hard || $this->wp->isMultisite() || !$this->wp->hasModRewrite()) { | |
return $hard; | |
} | |
# Hope for the best when we failed to find where WP is located | |
if (!$wp_dir = $this->getWPDir()) { | |
return $hard; | |
} | |
$path = substr($this->wp->getWPDir(), 0, -(strlen($wp_dir) + 1)); | |
$file = $path.'/.htaccess'; | |
# Generate rules | |
if (!$rules = $this->wp->getModRewriteRules()) { | |
$rules = implode("\n", $this->getModRewriteRules()); | |
$rules = <<<EOS | |
<IfModule mod_rewrite.c> | |
$rules | |
</IfModule> | |
EOS; | |
} | |
# Write rules and prevent WP from creating any mess | |
$this->wp->saveModRewriteRules($file, $rules); | |
return false; | |
} | |
/** | |
* Gets rewrite dirs | |
* | |
* @return array $dirs | |
*/ | |
protected function getModRewriteDirs() | |
{ | |
# Compute $wp_dir | |
if ($wp_dir = static::$wp_dir) { | |
$wp_dir = rtrim($wp_dir, '/'); | |
} | |
# Compute $home_dir | |
$home_dir = parse_url($this->wp->getOption('home'), PHP_URL_PATH); | |
$home_dir = rtrim($home_dir, '/'); | |
return compact('wp_dir', 'home_dir'); | |
} | |
/** | |
* Gets our rewrite rules | |
* | |
* @return array $rules | |
*/ | |
protected function getModRewriteRules() | |
{ | |
extract($this->getModRewriteDirs()); | |
$rewrite_base = <<<EOS | |
RewriteEngine On | |
RewriteBase $home_dir/ | |
EOS; | |
$rewrite_index = <<<EOS | |
# Don't hit the file system when visiting / or /index.php | |
RewriteRule ^(index\.php)?$ - [L] | |
EOS; | |
$rewrite_file = <<<EOS | |
# Only hit the file system once when requesting a genuine file or folder | |
RewriteCond %{REQUEST_FILENAME} -f [OR] | |
RewriteCond %{REQUEST_FILENAME} -d | |
RewriteRule ^ - [L] | |
EOS; | |
if ($wp_dir) { | |
$rewrite_admin = <<<EOS | |
# Make WP files masquerade as being served from the site's home folder | |
RewriteRule ^(wp-(content|admin|includes)(/.*)?)\$ $home_dir/$wp_dir/\$1 [L] | |
RewriteRule ^(wp-[_0-9a-zA-Z-]*\.php|xmlrpc\.php)\$ $home_dir/$wp_dir/\$1 [L] | |
EOS; | |
} | |
else { | |
$rewrite_admin = ''; | |
} | |
return compact('rewrite_base', 'rewrite_index', 'rewrite_file', 'rewrite_admin'); | |
} | |
/** | |
* Fills in the siteurl field with the proper value | |
* | |
* @param string $buffer | |
* @return string $buffer | |
*/ | |
public function restoreSiteUrlOption($buffer) | |
{ | |
# The urls are not configurable on multisite | |
if ($this->wp->isMultisite()) { | |
return $buffer; | |
} | |
# Nothing to do if WP isn't in a subfolder | |
if (!static::$wp_dir) { | |
return $buffer; | |
} | |
# Fetch real and advertised site url | |
$state = $this->override_site_url; | |
$this->override_site_url = true; | |
$site_url = $this->wp->getOption('siteurl'); | |
$this->override_site_url = false; | |
$real_url = $this->wp->getOption('siteurl'); | |
$this->override_site_url = $state; | |
# Process buffer if needed | |
if ($site_url != $real_url) { | |
$find = '<input name="siteurl" type="text" id="siteurl" value="'.$this->wp->escUrl($site_url).'"'; | |
$repl = '<input name="siteurl" type="text" id="siteurl" value="'.$this->wp->escUrl($real_url).'"'; | |
$buffer = str_replace($find, $repl, $buffer); | |
} | |
return $buffer; | |
} | |
/** | |
* Starts an output buffer to show the real site url in Settings / General | |
*/ | |
public function obStartRestoreSiteUrlOption() | |
{ | |
$this->buffers++; | |
ob_start(array($this, 'restoreSiteUrlOption')); | |
} | |
/** | |
* Flushes the output buffer started in obStartRestoreSiteUrlOption() | |
*/ | |
public function obFlushRestoreSiteUrlOption() | |
{ | |
if ($this->buffers > 0) { | |
$this->buffers--; | |
ob_end_flush(); | |
} | |
} | |
} # END class |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, how do i use this file? Im trying to use Bedrock for a WP multisite install with subdir sites.