Skip to content

Instantly share code, notes, and snippets.

@ddebernardy
Last active October 21, 2016 13:34
Show Gist options
  • Save ddebernardy/10529951 to your computer and use it in GitHub Desktop.
Save ddebernardy/10529951 to your computer and use it in GitHub Desktop.
SubdirInstall component for WordPress
<?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
@timcv
Copy link

timcv commented Dec 16, 2014

Hi, how do i use this file? Im trying to use Bedrock for a WP multisite install with subdir sites.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment