Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: multisite

  • Mailbag: Trash the Blog Slug

    Mailbag: Trash the Blog Slug

    If you only knew how many times I got this one…

    When I made my multisite, it changed all the URLs on my main site from example.com/postname to example.com/blog/postname ! How do I change that!?

    This is because you picked Subfolders for your network.

    Now before you get into this, ranting that it’s wrong, please actually read all of https://core.trac.wordpress.org/ticket/12002 first. The initial ticket was that if you used subdomains, you were also locked into using /blog/. We’ve obviously fixed that, but it brought up a bigger issue.

    Why do we keep it? To prevent conflicts. If you use /blog/%postname% and have a post named “humperdink” and another subsite named the same, it would cause a mess of problems. It’s one thing to search all your pages for possible conflicts (remember, your pages will still show up as example.com/pagename), most people only have a few pages. But once you factor in the hundreds of posts, it gets really crazy. If you have an open Multisite, where anyone can register any site, you have no way to doublecheck the URLs.

    So we’re making sure we don’t conflict with posts and sites, which is pretty impossible to do without a massive DB query every time you post, as well as pages and sites (less massive). I think that we should have some slug in there for those reasons.

    Or as Nacin put it when he detailed out a potential roadmap for Multisite:

    Dealing with URL Conflicts

    Perhaps the greatest change will be addressing the issue of the main site gaining a /blog prefix. This is ostensibly to avoid top-level pages on the main site from clashing with sub-sites. With arbitrary domain support (via domain mapping primarily, and secondarily via secondary networks), any site with path / can clash with any other site with the same domain but a different path. With multiple path segments (nested sites), any site with path /X/ can have pages that clash with site /X/Y/.

    Ultimately, this requires two-way blacklisting. Before a site is created, it must be checked against top-level URLs of the possibly conflicting site. And, before a page is created, it must be checked against sub-sites that already exist. If an /about/ page already exists on example.com/, an /about/ site cannot be created. But if an example.com/blog/ site already exists, a /blog/ page cannot be created on example.com. This gets complicated quickly, and is a very strong argument for only supporting one path segment in core by default, and allowing plugins to handle these potential conflicts on their own. In most cases, simply ignoring the potential conflicts is going to be sufficient.

    You see the headache? But hey, if you’re sure it won’t be a problem, you can do this yourself.

    Edit the site via network admin -> sites

    Click on settings and scroll till you find the permalink settings:

    Site Settings, Permalink Options

    Remove blog and save. Done.

    Now bear in mind, should you ever change permalinks on the main site, you will have to go back and do that again. This is because on the permalinks page, it’s hardcoded in:

    Multisite Permalinks hardcode blog

    Also some plugins will refresh permalinks and accidentally put it back in, so you need to be very careful. Someone wrote a cron job to re-write that value every hour in the DB.

  • Mailbag: Have You Ever Split a Multisite?

    Mailbag: Have You Ever Split a Multisite?

    That was the question.

    Have you ever split a multisite? If so, how?

    I wrote about Breaking Up Multisite before, but this was more specific.

    Yes. And it’s a funny story.

    I should preface the story with the reminder that in general when someone asks me how to do it, I casually mention that they can’t pay me enough to do it. This turned out to be inaccurate, as I was paid to do it. One of my first tasks at DreamHost was to take three separate sites and turn it into a two-site Multisite network. Two blogs were merged into one, then the new site was moved to Multisite. We did that with the export/import tools in WordPress. Fast-forward two years (my how time flies, Simon!) and now I’m asked to un-do it. But they only want site now. The main site is being deleted.

    I was actually glad, since this gave me a chance to handle the site properly and upgrade it correctly. I could clean out the old posts and content, re-sync users, tighten security, and undo the nightmare that was our old process. Plus the exercise of unraveling would give me more experience in WordPress shenanigans. And finally, it answered the question of how much you would have to pay me in order to do this (answer: more than most people would).

    It started out as a massive 30 step process, but after running through it a few times, I was able to speed it up into five, simple, sections. I make use of WP-CLI here, but if you don’t have it you’ll want to get interconnectit’s search and replace tool to save you a migraine.

    Bring it Local

    I use Vagrant and I made example.dev for this.

    Then I just copied down all the files from example.com/wp-content/blogs.dir/2/files/ to example.dev/wp-content/uploads-orig/ and did a database dump. Since I use WP-CLI, this was just a wb db export command.

    That was the full database, though, all 64megs of it, and I only wanted the second site. But we’ll get there in a second. I knew I had WP-CLI on my test box, but if I didn’t, I would have zipped the file in order to use phpMyAdmin (which would make it about 6megs). I’m lazy. I like GUIs. Either way, I imported the entire database to my new server.

    I also made a new wp-config.php file while I was at it, for multiple reasons. The one we were using did a check to see what domain you were on, and loaded different database params based on that. It was a cool bit of code, but it was unnecessary here. Making a new config file is easy (for me), and it ensured I had it clean and only set to a single install of WordPress. After all, I’m de-multisiting.

    Fix the Tables

    Of course, I had to clean that database. The first step was simple and I dropped all wp_FOO tables except wp_users and wp_usermeta. That left me with all the wp_2_ tables.

    Next I renamed wp_2_ to wp_ so I could have everything nice and orderly. But there’s a catch there, becuase there’s an option in my wp_options table that has the name wp_2_user_roles. Can you see what’s wrong? I need that to become wp_user_roles and I need to update any usermeta.

    Break out WP-CLI again and run this: wp search-replace "wp_2_" "wp_"

    So nice. So easy. That actually took care of 100% of the issues with the table renames. Were I doing it manually, it would be time for tears in your beers.

    Clean the Images

    With WP-CLI this is a snap:

    wp search-replace http://example.com/foldername/files/ http://example.dev/wp-content/uploads/
    wp search-replace http://example.com/foldername http://example.dev
    wp search-replace wp-content/blogs.dir/2/files wp-content/uploads
    

    I ran it like that for a reason. I like to do my searches in order of smallest catch to biggest, and this way it kept my possible gaffs to a minimum. I knew I had to fix all the images and post content, so it was safer this way.

    Clean up Users, Themes, and Plugins

    We had a lot of old, duplicate, users who had no posts or had left the project. I went over everyone’s permissions, dropped them down as low as I could, and removed half the admins. It’s just a good time for that.

    Next I reinstalled themes and plugins. I could have just copied them down, but I reinstalled everything because I wanted to take the time to make sure they were all clean and the latest versions. This is also where I paused to do a security review of everything we had.

    Move it Live

    Well now we’re just moving WordPress like normal. Copy it all up via FTP, copy up the database, run a last search replace to change example.dev to the real, new, domain (which I don’t actually know yet know), and it’s done. If I use wp-cli again, this will be as simple as running this: wp search-replace example.dev newsite.com

    All that extra work I did before pays off here.

    The nice thing about this is that I could have done this and then keep the main site if I’d wanted to. I didn’t, but I could have easily deleted all the wp_2_ tables and just cleaned up the multisite stuff. The headache is I’d have to do this multiple times if I’d had, say, ten sites on the network and wanted to move them all. If that had been the case, I would have only exported the wp_2_ tables and the wp_users and wp_usermeta ones.

    But yes. I have un-multi’d a site.

  • Subdomains and Subfolders, One Network

    Subdomains and Subfolders, One Network

    For the longest time, if someone wanted to have example.com, foo.example.com and example.com/bar for their Network on Multisite, I’d tell them to use a Multinetwork Plugin like Networks+ (which you can buy from e-Books by Ron and Andrea) or WP Multi Network (free from JJJ).

    But sometimes you don’t need multiple networks and the multiple admin sections. Sometimes you just want to have options. Thanks to the work that started with the roadmap you can have your cake and eat it too.

    If you’ll recall, I detailed how you can map a domain without a plugin on Multisite these days. Guess what? You can also do this with subfolders and subdomains.

    I did this with a subdomain install, since it made more sense to go that way.

    WordPress is installed at multisite.dev and I have subsites of foo.multisite.dev and bar.multisite.dev

    I then made a new site called baz.multisite.dev:

    Creating baz.multisite.dev

    Then I edited that from this:

    Editing baz.multsite.dev

    To this:

    Now it's multisite.dev/baz

    Be careful here! If you don’t put the trailing slash on the folder name, this will not work. And does this work? Yes it does. Of course there is the small issue of how this looks on my list of domains:

    Domain List is Ugly

    I have two sites as ‘multisite.dev’ and I have two ‘foo’ sites (because you can also make foo.multisite.dev/zot if you want to). The problem is that the Sites page in the Network Admin has a check:

    $blogname_columns = ( is_subdomain_install() ) ? __( 'Domain' ) : __( 'Path' );

    This means the ‘domain’ of foo.multisite.dev/zot and foo.multisite.dev are (correctly) foo. I couldn’t see how to filter, so I made a quick MU Plugin:

    class Add_Blog_Blogname {
    	
    	public function __construct() {
            add_filter( 'wpmu_blogs_columns', array( $this, 'blogname' ) );
            add_action('manage_sites_custom_column',  array( $this, 'blogname_columns' ) , 10, 3);
            add_action('manage_blogs_custom_column', array( $this, 'blogname_columns' ) , 10, 3);
        }
    
    	function blogname_columns($column, $blog_id) {
    	        global $wpdb;
    	        
    	        $blog_details = get_blog_details($blog_id);
    	        
    	        if ( $column == 'my_blogname' ) {
    	                echo $blog_details->blogname;
    	        }
    	        return $value;
    	}
    	
    	// Add in a column header
    	function blogname($columns) {
    	    $columns['my_blogname'] = __('True BlogName');
    	    return $columns;
    	}
    }
    
    new Add_Blog_Blogname();
    

    This tosses the True Blog Name to the end of the sites list. It’s not perfect, but it gets the job done.

  • Hide Your Site on Multisite

    Hide Your Site on Multisite

    Sometimes when you’re building a network, you don’t want all your sites to be available just yet. While you can install a ‘Coming Soon’ plugin, there are also built in ways to handle this.

    First you’ll want to take advantage of two of the Network’s least loved features: Deactivate and Archive. When you go to the Sites page on the Network Admin and hover over the items, you have new options appear:

    The edit options for your sites

    Should you click on Deactivate, you’ll be asked to confirm and then you get this:

    A deactivated site - it says 'Deleted'

    Don’t panic!!

    I know it says Deleted. It’s not. A deleted site is 100% deleted, the DB tables dropped and the images nuked. So while it ‘says’ deleted, it’s not. If you press Archive it’s a little more realistic:

    A site that has been archived is in light pink and says 'archived'

    What’s the difference? In both cases, this is what a non-logged in user sees:

    What a visitor sees on a deactivated site is 'This site is no longer available.'
    This site is no longer available.

    And in both cases, you can’t log in, because this is what you see for wp-admin and wp-login.php.

    Archived site WP Admin says the site is suspended

    Deleted site WP Admin says the site isn't available

    It’s weird, but it pretty much ‘archived’ the sites. You can, as a Super Admin, see it, but you can’t even change user roles from the network dashboard. (I spent about an hour trying to debug why I, as a Super Admin, couldn’t get to the dashboard at all, and it turned out I needed to flush my cache, so remember folks, caching is wonderful until you shoot your foot.) Still this presents a predicament.

    Frankly, I don’t want people to know a site doesn’t exist. That can be easily done with a filter and a redirect:

    // Archived sites only I can see
    function helf_redirect_hidden_sites() {
    
    	// Super Admins always get in
    	if ( is_super_admin() || current_user_can( 'manage_options' ) ) {
    		return true;
    	} else {
    		// Defines
    		if ( defined( 'NOBLOGREDIRECT' ) ) {
    			$goto = NOBLOGREDIRECT;
    		} else {
    			$goto = network_site_url();
    		}
    
    		$blog = get_blog_details();
    
    		if( '1' == $blog->deleted || '2' == $blog->deleted || '1' == $blog->archived || '1' == $blog->spam ) {
    			wp_redirect( $goto );
    	        die();
    		}
    	}
    }
    add_filter('ms_site_check','helf_redirect_hidden_sites');
    

    I wanted to allow my site admins and my super admin to view it, but if you don’t, edit if ( is_super_admin() || current_user_can( 'manage_options' ) ) to only allow what you want. And because I’m using a subdomain site, this makes it look like an archived/deleted site is just another non-existent site, by redirecting to NOBLOGREDIRECT.

    But this doesn’t work around the problem that my whole wp-admin is blocked off to non logged in users. I mean, how can I log in? The only workaround is that if the site is a subdomain (test.halfelf.org) or a subfolder (halfelf.org/test), then I can log in at halfelf.org/wp-admin and then visit over. If this was a mapped domain, I’d be in trouble. So it’s clearly not a perfect solution for everyone.

    By the way, you can customize the various messages for suspended or deleted sites by creating the following files in wp-content:

    blog-suspended.php
    blog-deleted.php
    blog-inactive.php

    So if you just want it to be pretty, that’s easy.

  • Switching The Main Blog on Multisite

    Switching The Main Blog on Multisite

    Previously I’d only ever posted this in my ebook, WordPress Multisite 110.

    What people mean by this is the site they originally set to be seen at domain.com is no longer the one they want to use, but the one at (say) domain.com/temp or temp.domain.com. If this is what you’re wanting to due, it’s not impossible, but it is annoying and a little tricky. If you’re using the trick to give WP it’s own directory, these are not the directions you’re looking for. I haven’t written those out yet.

    First you have to go to Network Dashboard > Sites and edit the site you want to be the main site.

    Example.com using two

    This you want to to look like this:

    Changing the subset two to no folder name

    Make certain you leave ‘Update siteurl and home as well’ checked! If you forget that, you’ll be sad. You no longer need to check the box (it’s gone in newer versions of WP), but if you DO have it, check it.

    Now you’d think you go to edit the main site and change it, but you can’t.

    Default main site is not editable

    By default, the main site is not editable. This makes sense when you think about how messy this might be, so in order to edit it you have to go to your wp-config.php file and look for this line:

    define('BLOG_ID_CURRENT_SITE', 1);

    Change it to the site ID you want to use as your main site. In this example, I want site , aka, two, to become my main site.

    define('BLOG_ID_CURRENT_SITE', 2);

    Save the file and then you have to go back to your Sites and edit the old main site.

    Main site can now be edited

    Give its path a new name and press save, making sure you keep that checkbox checked if it’s there.

    Change the original main site to new

    In this example, I’ve picked a new URL for my formerly main site becuase I don’t want any conflicts, but there’s nothing stopping me from picking ‘two’ again and just totally swapping things.

    The last step is to change your post content. Using a plugin like Velvet Blues Update URLs, you will need to search each site separately and replace the URLs. If you have wp-cli, you can do that too like this:

    wp search-replace 'example.com' 'example.com/new' wp_posts wp_postmeta --dry-run
    wp search-replace 'example.com/two' 'example.com' wp_2_posts wp_2_postmeta --dry-run
    

    If those look good, rerun without dry run and call it a day!

    An interesting quirk is that you may need to edit the Fileupload URL if you’re using blogs.dir for your images. I noticed that on one site it was set to http://example/two/files which clearly is wrong. To fix that, go to Network Admin, click on Sites, and click on edit for the site. From there, click on the dangerous “Settings” tab and look for “Fileupload URL” and edit as needed to match things.

    Cool tricks like this can be found in WordPress Multisite 110.

  • Multisite Favicons

    Multisite Favicons

    One of the challenges with WordPress Multisite is that everything is on the same install. This means something that is rather trivial, like a favicon, becomes exceptionally complex and tiresome.

    On a normal site, when I want to make a favicon, I just toss the file into my main HTML folder, next to that .htaccess file, and I’m done. Now, for me it’s a bit of an extra step because I use StudioPress’ Genesis theme, and it applies its own favicon. It’s probably the only thing about Genesis that makes me annoyed, though it’s right next to when my host does that for me. Still, there’s a trick with Genesis.

    Because of Genesis

    Genesis is my parent theme, and it defines my favicon. So I have to delete its and put in mine:

    // No, you may not have your favicon
    remove_action('genesis_meta', 'genesis_load_favicon');
    
    add_filter( 'genesis_pre_load_favicon', 'halfelf_favicon_filter' );
    function halfelf_favicon_filter( $favicon_url ) {
    	return 'https://halfelf.org/code/images/favicons/halfelf.ico';
    }
    

    And it’s that simple.

    But I’m talking about Multisite, and I want to have a different favicon per site, I end up facing a new challenge.

    Because of Genesis and Multisite

    I still need to tell Genesis to shove it, but then I need to check what site I’m on and call the right favicon per site and this works very well. I’m using it today (you can see the code on that other link). But what if there was another option? What if I didn’t have to code it? Because the real problem with that code is I have to do it! If someone wants a new site to have their own favicon, I have to go in and add it to my mu-plugin, save it, commit it, and push it. Ew.

    Jetpack Site Icons

    Jetpack has a newish feature called Site Icon. With that module activated, I can allow each site to upload their own site icon and it will make a mobile icon too where I won’t have to.

    Uploading my Space Invader

    For a Multisite running Jetpack auto-activated on all sites, this is perfect. And yes, I do that because it saves me a headache of having to set it up later. I allow all sites to connect on their own, but I can also control all that from the network admin.

    So what does that old code look like now?

    With Genesis and Jetpack

    Now my code is simple. For everyone, I kill the default favicon, and then I set my own:

    // No, you may not have your favicon
    remove_action('genesis_meta', 'genesis_load_favicon');
    
    global $blog_id;
    
    add_filter( 'genesis_pre_load_favicon', 'example_favicon_filter' );
    function ipstenu_favicon_filter( $favicon_url ) {
    	return 'http://example.com/images/favicons/example.ico';
    }
    

    And the best part is if I set a site icon in Jetpack, it uses that instead.

    Best of both worlds.