Paulund
2013-10-15 #wordpress

WordPress Multisite With Nested Folder Paths

When you setup a new site with WordPress multisite you have the option to create your site as sub domains or into sub folders.

  • site1.domain.com
  • site2.domain.com
  • site3.domain.com

Or using sub-folders your sites URLs will look like this. - domain.com/site1

  • domain.com/site2
  • domain.com/site3

But one limitation of using sub-folders is that by default you can't have sites in sub-sub-folders, this is normally used as a way of groups sites together as you can't assign categories to site. - domain.com/services/site1

  • domain.com/services/site2
  • domain.com/tools/site3

When you create a new site you can enter the path that you want for the site but if you wanted your site in nested folders WordPress will not be able to find the site to display this data. When a new site is created a new record is added to the wp_blogs table, two of the columns on this table is the domain and the path this site uses. When the page is loaded WordPress will get the current domain and the site path and search for this site in the wp_blogs table. If a site is found then this site ID is populated into the $wpdb object which is then used to get the correct content. WordPress only expects your site path to be one level deep so nested folders will not be found, to change this default behaviour you need to use a WordPress dropin called sunrise.php, this file is ran before the multisite is loaded so it can be used to load the correct site.

Sunrise.php

First you need to start off by creating a new file called sunrise.php and save this into the wp-content folder, normally WordPress dropins are activated by placing the file in the wp-content folder. But with the sunrise.php file you need to add a constant variable inside the wp-config.php file.

// Activate sunrise script
define('SUNRISE', TRUE);

In this file we need to access the current domain and the site path and change the query to search for sites with nested paths. Below is the code you can copy into the sunrise.php file. This first starts off by seeing what the current defined site is, WordPress has a couple of useful constant variables to get the current site domain and the current path. Once we have gathered the information of the current site we can then use the current URL to search the database for the site.


if( defined( 'DOMAIN_CURRENT_SITE' ) && defined( 'PATH_CURRENT_SITE' ) ) {
    $current_site->id = (defined( 'SITE_ID_CURRENT_SITE' ) ? constant('SITE_ID_CURRENT_SITE') : 1);
    $current_site->domain = $domain = DOMAIN_CURRENT_SITE;
    $current_site->path  = $path = PATH_CURRENT_SITE;

    if( defined( 'BLOGID_CURRENT_SITE' ) )
        $current_site->blog_id = BLOGID_CURRENT_SITE;
 
    $url = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
 
    $patharray = (array) explode( '/', trim( $url, '/' ));
    $blogsearch = '';

    if( count( $patharray )){
        foreach( $patharray as $pathpart ){
            $pathsearch .= '/'. $pathpart;
            $blogsearch .= $wpdb->prepare(" OR (domain = %s AND path = %s) ", $domain, $pathsearch .'/' );
        }
    }
 
    $current_blog = $wpdb->get_row( $wpdb->prepare("SELECT *, LENGTH( path ) as pathlen FROM $wpdb->blogs WHERE domain = %s AND path = '/'", $domain, $path) . $blogsearch .'ORDER BY pathlen DESC LIMIT 1');
 
    $blog_id = $current_blog->blog_id;
    $public  = $current_blog->public;
    $site_id = $current_blog->site_id;

    $current_site = pu_get_current_site_name( $current_site );
}

When we have returned a site from the wp_blogs table then we need to set the cache data to be the current site by using the wp_cache_set() function.


function pu_get_current_site_name( $current_site ) {
    global $wpdb;
    $current_site->site_name = wp_cache_get( $current_site->id . ':current_site_name', "site-options" );
    if ( !$current_site->site_name ) {
        $current_site->site_name = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE site_id = %d AND meta_key = 'site_name'", $current_site->id ) );
        if( $current_site->site_name == null )
            $current_site->site_name = ucfirst( $current_site->domain );
        wp_cache_set( $current_site->id . ':current_site_name', $current_site->site_name, 'site-options');
    }
    return $current_site;
}

The above script will create a SQL query to search for the blogs with a number of different nested paths, this means that it doesn't matter how many nested folders we have as this query will check at each level of the path.


SELECT *, LENGTH( path ) as pathlen
     FROM wp_blogs
     WHERE domain = 'domain.org' AND path = '/'"
         OR (domain = 'domain.org' AND path = '/path/')
         OR (domain = 'domain.org' AND path = '/path/to/')
         OR (domain = 'domain.org' AND path = '/path/to/blog/')
         OR (domain = 'domain.org' AND path = '/path/to/blog/and/')
         OR (domain = 'domain.org' AND path = '/path/to/blog/and/things/')
     ORDER BY pathlen DESC
     LIMIT 1

Htaccess Nested Sub Folders

When you use WordPress sub-folders for a multisite you need to add code to your htaccess file so that WordPress can rewrite the URLs correctly so that you can see the WordPress admin area.


RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-.*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

As you can see from the code above that WordPress will search for one level deep folders with wp-content/wp-admin/wp-include and write the content to be back to the root level of your WordPress install. This doesn't allow you to use multiple nested folders, so if you navigate to the admin area you will get a 404 error returned. This means we need to change this line in the htaccess to allow you to go multiple levels deep and then check for the wp-content/wp-admin/wp-include pages. Search for the below line in your htaccess file.


RewriteRule  ^([_0-9a-zA-Z-]+/)?(wp-.*) $2 [L]

And replace it will this line.


RewriteRule  ^(.+)?/(wp-.*) /$2 [L]

Now when you go to your nested site you will be able to access the admin area by typing in the current site path followed by wp-admin.