Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: plugins

  • 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!

  • WordPress Cancel Post Button

    WordPress Cancel Post Button

    Someone asked about this in the forums. I can see why folks would find it useful, so here’s a simple addition to add a ‘Cancel Post’ button to your publishing metabox.

    This plugin checks what post-type you’re on and redirects you correctly for pages and CPTs.

    To Install

    Make a file called cancel-button.php and put it in your mu-plugins folder (if you don’t have one, just make it in /wp-content/). In that file, paste the following (yes there’s no ending PHP tag, it’s okay, you don’t need it):

    <?php
    
    /* 
    Plugin Name: Cancel
    Plugin URI: https://halfelf.org/
    Description: Adds a 'cancel' button to posts, next to Publish.
    Version: 1.0
    Author: Ipstenu
    License: GPL2
    
    */
    
    add_action( 'post_submitbox_misc_actions', 'author_in_publish' );
    
    function author_in_publish() {
        $screen = get_current_screen();
        $cancel = get_admin_url();
        if ( $screen->id == 'post') {$cancel = "edit.php"; }
        else { $cancel = "edit.php?post_type=$screen->id";}
        
        echo "<div class=\"misc-pub-section\"><a class=\"button\" href=\"".$cancel."\" id=\"post-preview\">".__('Cancel Post')."</a><input type=\"hidden\" name=\"wp-cancelpost\" id=\"wp-cancelpost\" value=\"\"></div>";
    }
    

    This will add the button for all your sites on a network, if you happen to use Multisite.

  • Auto Multisite Registration

    Auto Multisite Registration

    There are plugins for this, and I’m rather fond of them. If you just want all new users to be added to some (or all) sites, then I suggest you use Multisite User Management. It’s a great plugin, and lets you pick and chose. But similar to how sometimes you want users to register per site, you may have a situation where you want users to be added to any and all sites when they visit.

    So how do we do this? It’s really not that painful, just make a add-users.php file in your mu-plugins folder with this:

    <?php
    
    /*
    Plugin Name: Add Users
    Description: Add users to blogs when they visit.
    */
    
    function helf_add_users( ) {
        global $current_user, $blog_id;
    
        if(!is_user_logged_in())
        return false;
     
        if( !is_user_member_of_blog() ) {
            add_user_to_blog($blog_id, $current_user->ID, "subscriber");
        }
    }
    
    add_action( 'wp' , 'helf_add_users' , 10);
    

    This code will automatically detect “John Doe is logged in.” and “John is not a member of this site.” If both of those are true, then it says “We will add him as a subscriber.” You can change subscriber as you see fit.

    Would it be more efficient to just do this check on registration? Sure. But there are moments you don’t want this. If you wanted to get extra sneaky, you could set this to only run on a specific page, and make that page password protected.

    If we want to add in a couple extra layers, that’s also not terrible. Let’s say we want people to press a button ‘Join This Site’? I have to admit, this actually took me longer to hash out than I wanted it to, mostly because I decided if I was going to do this, I should do it all the way.

    That’s why I came up with Join My Multisite, which is a handy plugin to give you some more options. It’s per-site configurable, so your site-admins will get to decide if they want everyone logged in user to be added to their site, or if they want a widget, or if they want nothing at all. I put some time into thinking about if it would be a good idea to have the network admin be able to pick, but when I got down to brass tacks, I realized there was no way to easily force a widget (i.e. the button to join the site) for all sites and make it always look good. If you need that, you should fork the plugin and hard-code in the widget.

    Join or Die

    In a way, this is an extension of the old ‘Add Users Sidebar’ plugin, which drifted off into the wayward lands of not-supported. I never looked at that code, though, and instead wrote all this from scratch.

    In addition to all that, Join My Multisite gives each site the ability, if registration is open, to easily make a per-site registration page. That one was Mason James’ suggestion.

  • Contact Form 7 and Anti-Spam

    DreamHost has a fairly simple anti-spam policy, which can be summed up as this: You cannot send email from an address that isn’t your domain.

    If that was greek to you, don’t worry. What that means is that my WordPress blog here can only send emails as elftest.net. That poses a small problem if you’re not using your domain-name to send email (a rare occurrence in WordPress), and a large one if you happen to be using the popular Contact Form 7 plugin.

    Contact Form 7 lets you create robust contact forms for your site, however it has one minor ‘flaw’ (and I hesitate to use that word). When it sends email, it sends it from the user who submits the form. DreamHost, naturally, doesn’t like this. joe@gmail.com isn’t an elftest user!

    Thankfully there’s a work-around for you, and it’s really easy. For most people, the plugin SMTP Configure, once installed and activated, will automatically fix this for you! It’s written by a reliable and trusted programmer, and I highly recommend it. Remember! Once you install the plugin, just activate it. For the vast majority of people, this was it. Everything magically worked.

    Then there were some people who came and said “No, this does not work.” I’ve yet to reproduce it, but one person told me that after putting in his SMTP credentials, just like you would setting up email clients, it worked perfectly.

    Additional: If you’re using Jetpack’s contact form, and you’ve changed the ‘to’ email address, you will also need this plugin. You’ll know you’re using that option because you’ll see this in your contact form shortcode:

    to="me@myotherdomain.com"