Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

  • Cacheless (or not)

    Cacheless (or not)

    ETA: As of a month later, I’ve actually switched from APC to Zend Optimizer+

    FilesDon’t get me wrong, I love caching. I love W3 Total Cache (I’m willing to spend my ‘free time’ testing it, after all), and WP Super Cache saved my life once. So why, on a day where I got a 400-600% uptick in traffic (not a joke), did I turn all my caching off? I’m daring, and a little crazy, but I wanted to see if it could be done. I would not have tried this if I was on a smaller server: if you’re getting as much traffic as I am, and you’re on shared hosting, you really need to move to a VPS or Dedicated Server if you want to turn off caching via plugins. It’s not to say that caching is better or worse than not-caching, or vice versa, or that one is a rich-man/poor-man equivalent of the other. Caching plugins are an inexpensive way to speed up your site, and if you can’t afford a bigger server they will buy you the time you need to figure out a better solution. Even with a good plugin and setup, if you get hammered with a lot of traffic, you will crash your site unless the server’s optimized too. Again, what I’ve done is not something I’d try on a low-end server with high traffic.

    When I started measuring the effectiveness of all this, I used:

    To understand what caching is and why we use it, it’s good to understand the basic concepts, and to start by looking at what caching plugins are, how they work, and where their pitfalls are.

    • There are parts of your website that don’t change often (images, javascript, CSS, etc).
    • You want the user to only download what’s changed.

    That sounds easy, but WordPress isn’t static HTML, it’s PHP, and that means every time you visit the page, it runs various proceses to give you the latest and greatest files. The problem with this dynamic code is where content changes rapidly (think ‘comments’ or ‘forums’ or ‘BuddyPress Groups’). Suddenly caching ‘pages’ as wholesale chunks of html doesn’t help if you have to re-cache when someone leaves a remark. Add in the possibility of 4 or 5 people commenting at once, for 12 hours, and now you’re risking a thrashing situation where you keep trying to cache, but it keeps flushing. This is why most people use plugins that handle things elegantly, or try to, where the ‘static’ part of the page (sidebar, etc) are HTMLized, but the dynamic part is left alone. This helps when you have a portion of every page is dynamic, like a shopping cart with a ‘Your order…’ box.

    StorageBut the downside is that you have to write fancy code that remains dynamic portions, and while it certainly can be done, it’s not fun, and let’s be honest, a theme developer doesn’t know which cache you’re going to use, so how can it write the right way for that? The only way to make a truly dynamic and cachable site is to do it from day one, with your theme, server, and plugins all crafted to provide the best experience. And then we have reality, which is we start with something simple, wake up to something large, and experience growing pains.

    Accepting the fact that we’re not starting from nothing, that we have an existing site with content and activity, the first thing most people do is install a plugin. Now, back to what I said before, this isn’t a bad thing. It’s a good first step and will buy you time. It’ll also show you where you need to go. If you don’t have server root access, this may be a your limit, too, as some of the other things I like to do to speed things up without a cache will require it (or you’ll have to ask your hosts and they may tell you to upgrade).

    If you’re going to use a plugin, WP Super Cache (WPSC) and W3 Total Cache (W3TC) are the best two. W3TC is way more advanced, and has a lot of extra bells and whistles, but personally I find that once you can master it, you’re well on your way. Remember though, you’re sacrificing a lot of control here by using a plugin. They’re going to, by their nature, cache everything they can, and we’re back to where we were with the dynamic site generation issue. W3TC has a bunch of extra .htaccess/nginx rules which parse data before you hit WordPress. WPSC can do that, or use PHP (which is slower).

    The dynamic nature of my site is what drove me away from caching plugins. I use other CMS tools, and for my infrequently updated Wiki and ZenPhoto Gallery, where content is very much static, caching makes perfect sense. But when I want to run a simple community site with WordPress, I have to consider all aspects of user experience. Speed is hugely important, but so is the user getting the content they want. Stale content is a killer.

    The reason I decided to see if my site ran slower without caching was that I was reinstalling caching and I thought “This is a perfect time to benchmark.” When I did I was astounded. There was very little difference in a benchmark test. Really no difference between at all, since it was within the results of each other, but I neglected to save the results at the time. I did however snap a picture of my server load(The unrelated part is where I was uploading 10megs of media. Unrelated.):

    load-graph

    Browser Caching is the first thing to tweak, as that tells browsers to cache content. The way this works is your .htaccess tacks on extra information while content like images and CSS are being downloaded, to say “This content is good for X days.” With WordPress, you don’t have to worry about changing the CSS, as most themes and plugins are extra smart, in that they append a version to the end of your CSS like this: style.css?ver=1.9.1 That 1.9.1 is the version of Genesis I’m running, so when that changes, the version changes, and browsers see it as a new file and re-download. That’s pretty cool. (I do wish that child themes pulled in their version, so you could increment that way.) We still have to tell the broswers to cache, and for how long, so near the top of my .htaccess (just below my hotlink protection) I have this:

    ## BEGIN EXPIRES ##
    
        ExpiresActive On
        ExpiresByType image/jpg "access 1 year"
        ExpiresByType image/jpeg "access 1 year"
        ExpiresByType image/gif "access 1 year"
        ExpiresByType image/png "access 1 year"
        ExpiresByType image/x-icon "access 1 year"
    
        ExpiresByType text/css "access 1 month"
        ExpiresByType text/html "access 1 hour"
    
        ExpiresByType application/pdf "access 1 month"
        ExpiresByType application/x-javascript "access 1 month"
        ExpiresByType application/javascript "access 1 month"
        ExpiresByType text/javascript "access 1 month"
        ExpiresByType text/x-js "access 1 month"
    
        ExpiresByType application/x-shockwave-flash "access 1 month"
        
        ExpiresByType video/quicktime "access 1 month"
        ExpiresByType audio/mpeg "access 1 month"
        ExpiresByType video/mp4 "access 1 month"
        ExpiresByType video/mpeg "access 1 month"
        ExpiresByType audio/ogg  "access 1 month"
        ExpiresByType video/ogg  "access 1 month"
    
        ExpiresDefault "access 2 days"
    
    ## END EXPIRES ##
    

    I’ve added in only the types used by my site. I used to use Pragma caching headers as well, but I noticed that Google PageSpeed Insights and YSlow ignore them. Turns out that Pragma headers aren’t honored all the time, in fact, they aren’t honored often, so I just removed them. I don’t think it slowed my site down to have them, but the less to maintain, the better. This had an immediate positive impact, so it was time to look at the server.

    ShapesOver the years, I’ve tuned httpd.conf so it doesn’t crash, I’ve got CSF locked down to prevent people from DoS’ing me over TimThumb, and I of course have APC turned on. Recently I broke down and installed mod_pagespeed when I upgraded to PHP 5.4. Just those things have done a lot to make my site run faster. I intentionally skipped things like Varnish or TrafficServer, as well as a CND or Google’s PageSpeed Service. I (still) don’t need them.

    Since I’m new to Page Speed, I decided to look deep into the filters and enabled the following for the whole server: rewrite_javascript, rewrite_css, collapse_whitespace, elide_attributes. This had a right-away impact of what I jokingly called ‘Effective Minification.’ These filters are new to me, so I spent a lot of time reading up on all the filters, and I find them highly interesting. By having PageSpeed handle things like offloading jQuery, I take the load off of WordPress and other CMS tools, and don’t have to use a plugin.(Don’t get the wrong idea. There are uses for plugins! But I’m all about using the right tool for the right job. I don’t have plugins handle my WordPress database, because I feel it’s like using a screwdriver to hammer in a nail. You can….)

    I added in a couple more to my standard: remove_comments and rewrite_images. Then I went back to my site’s .htaccess and started turning on the things I wanted per-site.

    The ones I picked are:

    Putting those in my .htaccess looks like this (note: no spaces between the filter names, or it all blows an error 500):

    
        ModPagespeedEnableFilters move_css_to_head,defer_javascript,insert_ga
        ModPagespeedAnalyticsID UA-MYCOOLID-1
    
    

    That also means I don’t have to use a plugin to use Google Analytics for my whole site! This may not mean a lot to you, but I have multiple ‘apps’ on my site (four now) and when I edit themes, if I don’t have to do anything, it’s easier. Google will tell you not to do this, but unless they have a way for me to set pagespeed.conf in the /home/user/ folder of my server, I don’t know another per-user way about this.

    Finally I went back on my word, and I installed a plugin. APC by Mark Jaquith. This isn’t a full reversal on my ‘No Plugins!’ stance before, though. All APC is, you see, is but one file that sites in wp-content and kicks things over to APC. Doing this alone moved my TTFB from an F to a B. Which is pretty impressive. Giving it a little time to bake, this worked out okay.

  • wp-cron it up

    wp-cron it up

    CronIf you’ve spent much time mastering *nix servers, you’ve run into cron. I love cron. Cron is a special command that lets you schedule jobs. I use it all the time for various commands I want to run regularly, like the hourly check for new posts in RSS on TinyRSS or RSS2Email, or the nightly backups, or any other thing I want to happen repeatedly. Cron is perfect for this, it runs in the background and it’s always running.

    My first thought, when I heard about wp-cron, was that it clearly tapped into cron! If I scheduled a post, it must write a one-time cron job for that. I was wrong. Among other reasons, not everyone has cron on their servers (Windows, I’m looking at you), but more to the point, cron is a bit overkill here. Instead WordPress has a file called wp-cron.php which it calls under special circumstances. Every time you load a page, WordPress checks if there’s anything for WP-Cron to run and, if so, calls wp-cron.php.

    Some hosts get a little snippy about that, claiming it overuses resources and ask that you disable cron. One might argue that checking on every pageload is overkill, but realistically, with all the other checks WordPress puts in for comments and changes, that seems a bit off. In the old WordPress days, this actually was a little true. If you have a server getting a lot of traffic, it was possible to accidentally loop multiple calls at the same time. This was fixed quite a bit in WordPress 3.3 with better locking (so things only run once). Multisite was a hassle for a lot of people, but seems to have been sorted out.

    StopwatchWe already know that WordPress only calls wp-cron if it needs to, so if you disable it, you have to run that manually (which ironically you could do via a cron job (Read How to Disable WordPress WP-Cron for directions on how to disable and then use real cron.)). To disable wp-cron, just toss this in your wp-config.php:

    define('DISABLE_WP_CRON', true);

    Now, remember, you still want to run a ‘cron’ job to fire off anything scheduled, so you have to add in your own cron-job for this, which I actually do! I have cron turned off, and a ‘real’ cron job runs at 5 and 35 past the hour to call wp-cron with a simple command:

    curl http://example.com/wp-cron.php
    

    Now since I have multiple domains on this install, I actually have that for every site on my Multisite. After all, wp-cron.php on Ipstenu.org doesn’t run for photos.ipstenu.org and so on. Doing this sped up my site, and put the strain back where I felt it should be. The server.

    By the way, wp-cron does more than schedule posts. That’s what checks for plugin, theme and core updates, and you can write code to hook into it to schedule things like backups. Remember before how I said that WP checks to see if there’s anything scheduled? This is a good thing, since if you have your server run a long job (like backing up a 100meg site), you don’t want to wait for that to be done before you page loaded, right?

    For most people, on most sites, this is all fine to leave alone and let it run as is. For the rest, if you chose to use cron instead, keep in mind how things are scheduled. Like I set my cron to run and 5 and 35, so I tend to schedule posts for 30 and the hour. That means my posts will go up no later that 5 minutes after. That’s good, but if I use my DreamObjects plugin, the ‘Backup ASAP’ button schedules a backup to run in 60 seconds (so as not to stop you from doing anything else, eh?), which means I’d have to manually go to example.com/wp-cron.php in order to kick it off. Clearly there are issues doing this for a client.

  • Backwards Settings API

    Backwards Settings API

    backwardsThe biggest problem with documentation is that if you don’t think the same way the doc was written, the examples will frustrate you to the point where you want to cry. If you’re not techy at all, directions to move WordPress to a subfolder will piss you off. If you’re a basic webmaster, who can edit posts and maybe FTP, they’re scary but doable. It’s all a measure of leveling up and seeing things in a way you understand.

    Recently I was banging around with the Settings API in WordPress to fix some debug errors in a plugin. It took me about 5 hours to figure out what it was I was doing, and how to fix it. Actual fixing? About half an hour, including the time it took to make fresh coffee.

    What was my problem? I was looking at code from a different angle. If you start ‘right’ it’s really easy to follow tutorials, but when you start from an already functioning plugin and want to correct it, it’s a nightmare. Reverse engineering things isn’t easy. You’re used to looking at things in one way, and changing that is a headache. What I had was working, up until you turned on debug, which is why I got away with it for so long, and why I hated having to change it.

    But I did. I had a plugin admin page that let you enter two text strings, an access key and a secret key, which the rest of the code used to do it’s magic. Because of that, I couldn’t just be lazy and use a basic register_setting() call like this:

    register_setting( 'myplugin-retain-settings', 'myplugin-retain');
    

    That’s easy, you put it in, you call it with settings_fields('myplugin-retain-settings');, and you go to the races. But I have two strings, and if you call the settings fields twice, you’ll find all sorts of fun errors.

    To explain, let me show you what I did wrong, and what right is.

    Doing It Wrong

    <form method="post" action="options.php">
    <input type="hidden" name="action" value="update" />
    <?php wp_nonce_field('update-options'); ?>
    <input type="hidden" name="page_options" value="myplugin-accesskey,myplugin-secretkey" />
    
    <table class="form-table">
        <tbody>
            <tr valign="top"><th colspan="2"><h3><?php _e('MyPlugin Settings', myplugin); ?></h3></th></tr>
            <tr valign="top">
                <th scope="row"><label for="myplugin-accesskey"><?php _e('Access Key', myplugin); ?></label></th>
                <td><input type="text" name="myplugin-accesskey" value="<?php echo get_option('myplugin-accesskey'); ?>" class="regular-text"/></td>
            </tr>
    
            <tr valign="top">
                <th scope="row"><label for="myplugin-secretkey"><?php _e('Secret Key', myplugin); ?></label></th>
                <td><input type="text" name="myplugin-secretkey" value="<?php echo get_option('myplugin-secretkey'); ?>" class="regular-text"/></td>
            </tr>
    </tbody>
    </table>
    
    <p class="submit"><input class='button-primary' type='Submit' name='update' value='<?php _e("Update Options", dreamobjects); ?>' id='submitbutton' /></p>
    </form>
    

    Doing It Right

    <form method="post" action="options.php">
    	<?php
            settings_fields( 'myplugin-keypair-settings' );
            do_settings_sections( 'myplugin-keypair_page' );
            submit_button();
    	?>
    </form>
    

    Making Wrong Right

    As you can tell, there’s a huge difference between the two, and one is a lot easier to look at than the other.

    First you have to set up registering settings. Since I already had an admin action for my settings page, I wanted to just add to that:

    function add_settings_page() {
            load_plugin_textdomain(myplugin, MYPLUG::getPath() . 'i18n', 'i18n');
            add_menu_page(__('MyPlugin Settings'), __('MyPlugin'), 'manage_options', 'myplugin-menu', array('MYPLUG', 'settings_page'), plugins_url('myplugin/images/myplugin-color.png'));
    }
    

    I added this in(I’m using an array to isolate my plugin functions, so I don’t have to worry as much about namespace clases.):

            add_action('admin_init', array('MYPLUG', 'add_register_settings'));
    

    That was the easy part. The hard part is converting all my table information into settings. Making another new function, I want to add a setting section, just as you would for a one-off. But in this case I need to make a group. Since I named my settings field myplugin-keypair-settings, and my section myplugin-keypair_page, that’s the first important information I need. But backwards.

    First I have to add my section using add_settings_section():

    add_settings_section( 'myplugin-keypair_id', 'MyPlugin Settings', 'myplugkeypair_callback', 'myplugin-keypair_page' );
    

    Once you do that, you want to register the setting, just like normal:

    register_setting( 'myplugin-keypair-settings','myplugin-key');
    

    The complete function

    function add_register_settings() {
         // Keypair settings
            add_settings_section( 'myplugin-keypair_id', 'MyPlugin Settings', 'myplugkeypair_callback', 'myplugin-keypair_page' );
            
            register_setting( 'myplugin-keypair-settings','myplugin-key');
            add_settings_field( 'myplugin-key_id', 'Access Key', 'myplugkey_callback', 'myplugin-keypair_page', 'myplugin-keypair_id' );
            
            register_setting( 'myplugin-keypair-settings','myplugin-secretkey');
            add_settings_field( 'myplugin-secretkey_id', 'Secret Key', 'myplugsecretkey_callback', 'myplugin-keypair_page', 'myplugin-keypair_id' );
    
            function myplugkeypair_callback() { 
                echo '<p>'. _e("Once you've configured your keypair here, you'll be able to use the features of this plugin.", MyPlugins).'</p>';
            }
        	function myplugkey_callback() {
            	echo '<input type="text" name="myplugin-key" value="'. get_option('myplugin-key') .'" class="regular-text"/>';
        	}
        	function myplugsecretkey_callback() {
            	echo '<input type="text" name="myplugin-secretkey" value="'. get_option('myplugin-secretkey') .'" class="regular-text"/>';
        	}
    
    }
    

    imagesWhen I got around to the massive amounts of design I put into things, I ended up making a file called lib/settings.php where I stored everything related to the settings. For me, that’s easier to manage than one massive file with lots of different calls. Easier to debug and edit too, without panicing that I broke something.

    I don’t know if it was so much I think backwards, or the API was backwards, but whatever it was, I had a massive brain-block about all this for about a day. I really have to thank Kailey and Pippin for pointing me at examples that ‘clicked’ in a way that I suddenly could figure it all out.

  • Multisite Stands Alone

    Multisite Stands Alone

    NKOTBWhile I wrote up a lot of reasons why you shouldn’t use Multisite, the truth is I really like it and find it well suited to my needs. But one of the big problems with it is that everyone’s network setup is different. Many times, when people ask for help in the forums, I have to sketch out the bare ideas of what to do and why. This brings people to their biggest complaint in Multisite: Why isn’t there a plugin for this already?

    First and formost, Multisite is still ‘new’ for the mainstream. Thelonius, the 3.0 release, is only two and a half years old! WordPress 1.0 came out in 2004, just as a reminder. 3.0 is when Multisite was folded into full WordPress, and it’s not been there very long, but it’s changed a lot since the start. In 2010, when 3.0 came out, there were 10,000 plugins in the repository. Today we’re at 22,000 and growing every day. When it comes to single site WordPress, the plugin world is mature and populated. Of those 22,000, about 300 are specific for Multisite. That’s a pretty small number when you look at the over 1000 plugins that do something with Twitter. Of course, most plugins work with Multisite anyway, but the ones that are Multisite specific are the ones to look at here.

    A plugin is developed to fit a need. A good developer tries to keep the plugin simple and adaptable, so as many people as possible can use it for as many situations as suit their needs. Doing that has a lot of weird scope creep, of course, and if you consider that we do have 1000 twitter related plugins, you may understand why. Plugins fit a need, and when they don’t we extend/fork/hack the plugin to fit our specific need. Multisite, in and of itself, is fitting a need, and while that need is very specific, it’s also very broad. There are myriad reasons to use Multisite, and because of that, those 300 plugins that are built specifically for Multisite have to meet all of our needs. Of course, that’s not how it works.

    While the majority of themes and plugins work just fine on Multisite, the ones people get in arms about are the ones that are supposed to be ‘for’ Multisite. These plugins should do more, but not too much. They should meet my specific needs, as well as all of yours and hers and his too. There should be a plugin for everything. Why am I the only person who wants this? I walk away from those arguments a lot, or I point out “You’re not the only person who wants it, but you’re possibly the only person who wants it in that way.”

    Plugins are not the silver bullet for everyone. Your site may be a werewolf, mine a zombie, and his a vampire. Each one has their own needs and because of that, their own requirements. So let’s be direct. The whole reason you can’t find the perfect plugin is because no one’s a mind reader. The reason you can’t find everything you want in one plugin is because you didn’t write it (or have it written).

    What does that mean? Multisite is still young, and those 300 Multisite specific plugins aren’t all Multisite specific first of all. At least a third are listed as Multisite because they work on Multisite. For example, bbPress is tagged ‘multisite’ but it wasn’t built ‘for’ Multisite. On the other hand, Networks for WordPress is built for Multisite, and nothing else.

    The best way to tell if a plugin is built just for Multisite is to see if it set ‘Network:true’ in the code. If you open up the code of the plugin, you can see this in the header:

    Now, not all Multisite plugins are meant to be Network enabled, but only plugins that are for Multisite will be Network only like that, so it’s one way to make sure that plugin is intended for Multisite. The downside to that is expressed via something like BuddyPress, which if you use on a Multisite must be network activated, but you don’t have to use it on Multisite. This means the check isn’t perfect.

    NKOTB 2010The point to all this is that Multisite’s still the new kid in town. It isn’t perfect, and it is still evolving and changing in pretty dramatic ways. Also, I find it pretty cool to watch it grow. But the reason you can’t find all the plugins you want with it is really simple: They haven’t been written. You may actually be the first person who wants something done in that specific way, and with the myriad new methods Multisite gives us to do amazing things, there’s a lot of room for different options.

    What should you do if you’re not a coder and want something that’s never been done? Well, learn to code. Or hire a coder. I wish there was another way, but when you want that special one-off toy that really isn’t suitable for everyone, you’re going to need to meet the challenge head out. None of this is meant as an excuse, by the way, but an explanation. It’s just not done. Yet.

  • Dumping ms-files

    Dumping ms-files

    Trash Can On It's SideNOTE: You do not, under any circumstances, have to do this to continue using WordPress Multisite. I just wanted to see if I could.

    I have been toying around with this. Since WP 3.5 doesn’t use ms-files anymore, I wanted to see how much of a pain this would be. While I was at the DMV (I now have a California Drivers License), I started sketching out the methods and possibilities for how one might do this as safely as possible, without bolluxing up your site. I got about 90% successful with it, so this is something I would never suggest you do on a massive, live site. If you compare it to what I did to move images from uploads to blogs.dir for the main site, it’s far more complex and annoying.

    Do you have to do this? Again, no! Everything will work just fine. Can you do it? Sure. Why did I? Support. I already know all the mishegas with blogs.dir, but the new location, and it’s lack of ms-files, promises other, weird, errors. I want to repeat, you don’t need to do anything. The Core Team was fantastic with the work they did to turn off ms-files for fresh 3.5 and up installs is nothing short of phenomenal. This was just to assuage my curious nature, and learn more about the way things work.

    You ready? Here we go.

    I decided to move the images at http://test.ipstenu.org/ to start with, so everything will use that as my example. This is after I played with it on a local site and got my steps mostly solid. I knew that live is always different than test, so I backed up the DB first (always, always, backup first!) and went to town.

    Move the images

    This is obvious. Move your images from blogs.dir/SITENUM/files/ to /uploads/sites/SITENUM/ (or make an alias). I went and did it via command line in the /uploads/sites/ folder, doing this:

    $ cp -r ~/public_html/wp-content/blogs.dir/10/files .
    $ mv files 10
    

    Lather, rinse, repeat. I could have scripted it, but I was working out the kinks until I had two left.

    Edit all sites

    Upload Path, Upload URL Path and Fileupload Url. You can blank them all out.(Corrected thanks to Nacin.)

    Blank me out

    Since you’re blanking it out for everyone you can probably do this via SQL, but since I was doing the sites one at a time, I did them one at a time.

    Fix the Database
    Search/replace each posts table for each site, changing /files/ to /uploads/SITENUM/

    UPDATE wp_10_posts SET post_content = REPLACE (
    post_content,
    '=&quot;http://test.ipstenu.org/files/',
    '=&quot;http://test.ipstenu.org/wp-content/uploads/sites/10/');
    

    Full Trash, ColorWhy did I do it that way? Because of this blog. I talk a lot about code here, and I know I’ve talked about moving files around before. If you don’t do that, you’re okay with a less precise search, but why bother? This works, it’s safe, and I’d use it again.

    That got annoying really fast. I went and grabbed my favorite Search And Replace for WordPress (and any other database) tool. Seriously I love that. I used that to follow up, change everything, and it actually worked really well for me.

    Another DB Fix!

    One of the changes in 3.5 was turning off rewriting. This took me forever and a day to find. After I did that, my images showed up fine, but the little buggers kept uploading to /files! Turns out it was all because of the site option ms_files_rewriting

    The way I got around this was by putting the following in my wp-config.php file:

    define( 'UPLOADBLOGSDIR', 'wp-content/uploads/sites' );

    And then I ran this in SQL to turn off ms_files_rewriting. Or so I thought. More in a second.

    INSERT INTO `my_database`.`wp_sitemeta` (`meta_id`, `site_id`, `meta_key`, `meta_value`) VALUES (NULL, '1', 'ms_files_rewriting', '0');
    

    I came up with that after reading through /wp-includes/functions.php line 1515.

    For most sites, this worked, but in my later work, I determined that it actually wasn’t working. It was ignoring this. I don’t know why, but every test I did merrily ignored this setting, so I finally growled and wrote this mu-plugin function:

    function stupid_ms_files_rewriting() {
            $url = '/wp-content/uploads/sites/' . get_current_blog_id();
            define( 'BLOGUPLOADDIR', $url );
    }
    add_action('init','stupid_ms_files_rewriting');
    

    It’s stupid simple, it’s probably not a good idea, but it works for the three sites that have the stupids.

    Finish up .htaccess.

    .htaccess, remove the ms-files.php line for ms-files, or comment it out. This is pretty simple.

    Empty TrashWhy not move the main site to /uploads/?

    Because of the way I fixed the uploadblogsdir. It defaulted everyone to /sites/ and after an hour I said fuck it.

    Any weird problems?

    Yeah, two sites (this one and my grandmothers) decided that they wanted to be repetitious and spat out URLs like this: wp-content/uploads/sites/8/sites/8/

    Since that wasn’t right at all, and I was a little too eggnoggy to parse why, I did this:

    RewriteCond %{HTTP_HOST} ^taffys\.org
    RewriteRule ^wp-content/uploads/sites/8/sites/8/(.*) /wp-content/uploads/sites/8/$1 [L,R=301]
    

    I swear I have no idea why three sites got stuck with /files/ and two more decided to double down, Vegas style, but frankly I’m pleased I got through this far on my own.

    I can’t stress enough that you do not have to do this!

  • Subdomain vs Domain

    Subdomain vs Domain

    When two words are very similar, it’s easy to get confused. Which witch is which? Whether, weather, and wether. Affect vs effect… Okay, you know, English sucks. We have way too many words that will drive you to drink, and if you know anyone who’s learned English as a second language, please take time to tell them how amazing they are. My father’s wife is Japanese, bilingual in French, and is learning English. I know a smattering of French. Our conversations are fantastically amusing and thankfully we have great senses of humor.

    Because I’m that familiar with the crazy of my native language, I have no surprise that people get subdomains and domains confused. Here’s the basic statement:

    A subdomain is not the same as a domain.

    That’s it. But since I don’t expect everyone to know what the heck I just said, I’m going to explain. Remember, don’t think you’re stupid for not knowing this! You can’t magically know everything, you have to learn it, and there are people like me who want to help you. Where the confusion kicks in isn’t that we call it a ‘subdomain’ but that the official definition is “a subdomain is a domain that is part of a larger domain.” So we’ve just said a domain of a domain, and yet here I am pushing you and saying that a subdomain isn’t a domain when it clearly is.

    It is and it isn’t.

    • A domain is pretty simple: elftest.net is a domain. It’s the solid basis that all websites are built on.
    • A subdomain is a subset of the domain: tools.elftest.net is a subdomain on elftest.net.

    Notice how ‘tools.’ is in front of elftest.net? That extra period between tools and elftest is how we know this is a sub domain. The .net part is called the ‘Top Level Domain’ and any time you see www, that actually isn’t a subdomain, but a special term… You know what, let’s break this down with a picture.

    Domain Example

    You can ignore protocol for now (we can get into that another time). What we’re looking at is this:

    URL: http://www.example.com/index.html
    Top-level domain name: com
    Second-level domain name: example.com
    Host name: www.example.com OR example.com

    Why is www special? It has to do with a lot of boring history, but suffice to say that used to be how we knew it was a webpage! Now we use http:// to say ‘This will be a webpage’ so many of us (myself included) feel that www is unnecessary and just makes URLs longer. However because of history, http://www.elftest.net and http://elftest.net will forever point you to the same place. This actually means that www is a subdomain, but it’s a very special one that points to the same place as no www at all. In very rare cases, a fancy website will redirect www and non-www to different places, but this is the exception, not the rule. Good SEO practices are to have the www and non-www point to the exact same place.

    The meat of the matter is that most of the time, when someone asks ‘What’s your domain?’ they really mean ‘What’s your host name?’ My host name is elftest.net (or ipstenu.org or halfelf.org…. I have a lot of domains

    Now let’s look at a subdomain.

    Subdomain Example

    They look shockingly similar, except that instead of www in front, I have sub. So what’s the deal here? Well because I’m using something other than www, I’ve designated sub.example.com as a subset of example.com, and thus a subdomain. Yes, it’s backwards. Sub should be below or behind, but remember, we’re calling .com the top level domain, so right-to-left this makes more sense.

    I know. It’s all clear as mud. Even writing this I sat there and muttered “This stuff is nuts.” I know all this didn’t explain everything as clearly as I could wish, but I’ll break it down into the simplest terms that, while not 100% technically accurate, will tell every decent web tech what you mean:

    When someone asks “What’s the subdomain?” you answer ‘sub.example.com’

    If someone asks “What’s the domain?’ you say ‘example.com’ (sometimes they’ll ask “What’s the main domain?”)

    If you’re on Multisite and someone asks “What’s the mapped domain, and what subdomain does it point to?” you say ‘mappeddomain.com and it points to mapped.example.com’

    And never ever use domain mapping plugins for your subdomains. Those are for grownup domains only, not your subdomains.

    For extra credit: Third level domains are what you get when you see things like example.co.uk – example isn’t a subdomain here, it’s the main domain. co.uk is the TLD. Why third? Well, we’d already used sub and second, and we needed some way to say that this is part of the primary URL, and not a subset. Also geeks love to confuse people.