Regularly cleaning up /tmp on a DreamHost VPS

I ran into a problem recently where I was no longer able to upload a file via PHP. I checked the server error log, and I saw entries like:

ModSecurity: Input filter: Failed writing X bytes to temporary file

Looking at my /tmp directory it contained 128MB of data. Apparently ModSecurity tries to prevent the filesystem from being maliciously filled up.

If you try to manually clean up just with a rm -rf /tmp/*, it will fail because the files are owned by dhapache and are not writable by anyone else. But, with DreamHost VPS, you have the ability to add admin users (aka sudoers). As an admin user, you can then set up a cron to automatically clear out the /tmp directory of old files:

su myadminuser
sudo crontab -e

Then in the editor which opens, add this line:

0 0 * * * find /tmp -mtime +5 -exec rm -rf {} \;

This will delete all files under /tmp which haven’t been modified in 5 days; it will happen every day at midnight.

Re: Sharing Sidebars Across a Multisite Network

In a post yesterday at WebDevStudios about “Sharing Sidebars Across a Multisite Network”, I was reminded of a workaround that I had to put into the Widget Customizer plugin. There is this unfortunate condition inside of the core wp_get_sidebars_widgets() function:

// If loading from front page, consult $_wp_sidebars_widgets rather than options
// to see if wp_convert_widget_settings() has made manipulations in memory.
if ( !is_admin() ) {
    if ( empty($_wp_sidebars_widgets) )
        $_wp_sidebars_widgets = get_option('sidebars_widgets', array());
    $sidebars_widgets = $_wp_sidebars_widgets;
} else {
    $sidebars_widgets = get_option('sidebars_widgets', array());
}

So you can see here that if wp_get_sidebars_widgets() gets called early (and it does) then the initial value returned will persist even if the underlying sidebars_widgets option changes (or is filtered); after this function is called on the site frontend, the returned value will persist in that private global variable $_wp_sidebars_widgets.

This was a problem for Widget Customizer because it needed to be able to override the sidebars’ widgets on the fly within the customizer preview. Fortunately, the return value of wp_get_sidebars_widgets() is filterable via the sidebars_widgets hook, and so this is what we did to get around the above behavior:

add_filter( 'sidebars_widgets', function ( $sidebars_widgets ) {
    $sidebars_widgets = get_option( 'sidebars_widgets' );
    unset( $sidebars_widgets['array_version'] );
    return $sidebars_widgets;
} );

Would this workaround also allow switch_to_blog() to be able to render sidebars from other blogs on the network?

Revelations about filter_input

WordPress has the unfortunate legacy situation of having to force input variables to be “magic quoted”, something which has been deprecated in PHP for some time. So when you are working with data passed from the user, you have to run those input variables ($_GET, $_POST, $_REQUEST) through stripslashes(), which is really annoying. At least I\’m annoyed by it.

However, I just made a discovery. As of 5.2, PHP comes with an extension for Data Filtering, including a function filter_input function for fetching input variables and passing them through validation, sanitization, and other filters.

Well, it turns out that when you access input variables through filter_input, it bypasses wp_magic_quotes entirely, and so you’ll get raw un-backslashed data back! So instead of accessing $_GET['x'], you call filter_input( INPUT_GET, 'x' ).

Additionally, using filter_input() has the benefit of not having to add isset() and !empty() checks everywhere that you’re interacting with input variables, so as to avoid the plague of PHP notices about undefined array indexes. (Oh, hello most WordPress plugins in existence!)

And the icing on the cake for filter_input is that you get validation and sanitization.

However, there’s one big caveat about all this. The WordPress API functions like update_post_meta() and friends actually expect the input to be magic-quoted. So if you do this:

update_post_meta( get_the_ID(), 'x', 'I love \o/ WordPress!' );

If you grab that postmeta out:

$x = get_post_meta( get_the_ID(), 'x', true );

Then $x will end up being:

I love o/ WordPress!

Nooooooooooooooooooooooooooo.

For more info, see #18322: The Road to Magic Quotes Sanity.