Screen Shot 2014-04-18 at 11.33.29 AM

Making a Custom Post Type display as a single column

While the default layout of the WordPress post screen is often perfectly suitable for your custom post type display. It’s not always right.

I’m currently working on a plugin where I need a single column for my custom post type so that I have a large area for my metaboxes.

Today I’m going to show you how to force a single column on a custom post type.

Our Starting Plugin

CPT’s should be in plugins. If you wonder why I’ve written about it before. So let’s start by adding a plugin to a site.

Create a folder in the plugins folder called wptt-site-demo and then create a file called wptt-site-demo.php.

<?php
/*
Plugin Name: Single Column Post Type
Plugin URI: http://wpthemetutorial.com
Description: Gives us a post type with a single column
Version: 1.0
Author: SFNdesign, Curtis McHale
Author URI: http://wpthemetutorial.com
License: GPLv2 or later
*/
 
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
 
class WPTT_Single_Column_CPT{
 
    function __construct(){
 
        add_action( 'init', array( $this, 'add_cpt' ) );
 
    } // construct
 
 
    /**
     * Builds out the custom post types for the site
     *
     * @uses    register_post_type
     *
     * @since   1.0
     * @author  WPTT, Curtis McHale
     */
    public function add_cpt(){
 
        register_post_type('wptt_single_column',
            array(
                'labels'                => array(
                    'name'                  => __('Single Column'),
                    'singular_name'         => __('Single Column'),
                    'add_new'               => __('Add New'),
                    'add_new_item'          => __('Add New Single Column'),
                    'edit'                  => __('Edit'),
                    'edit_item'             => __('Edit Single Column'),
                    'new_item'              => __('New Single Column'),
                    'view'                  => __('View Single Column'),
                    'view_item'             => __('View Single Column'),
                    'search_items'          => __('Search Single Column'),
                    'not_found'             => __('No Single Column Found'),
                    'not_found_in_trash'    => __('No Single Column found in Trash')
                ), // end array for labels
                'public'                => true,
                'menu_position'         => 5, // sets admin menu position
                'hierarchical'          => false, // funcions like posts
                'supports'              => array('title', 'editor', 'revisions', 'excerpt', 'thumbnail'),
                'rewrite'               => array('slug' => 'wptt-single-column', 'with_front' => true,), // permalinks format
                'can_export'            => true,
            )
        );
 
    }
 
} // WPTT_Single_Column_CPT
 
new WPTT_Single_Column_CPT();

What our code above does is have a basic plugin header. Then a class for the plugin. The only method we have builds us a generic custom post type to work with using the [register_post_type][register_post_type].

If we activate the plugin and refresh the WordPress admin we’ll have a pretty standard post type called ‘Single Column’. If we click on it and add a new post we’ll get the standard CPT layout for WordPress.

Now let’s make it a single column display.

Single Column Post Types

To do that we need 2 functions. The first will actually make it display as a single column and the second will make it so that the user doesn’t have the option to make it double columns.

Modify they __construct function to look like below.

function __construct(){
 
    add_action( 'init', array( $this, 'add_cpt' ) );
 
    add_filter( 'get_user_option_screen_layout_wptt_single_column', array( $this, 'single_column' ) );
    add_filter( 'screen_layout_columns', array( $this, 'only_single_column_option' ) );
 
} // construct

Then we need to add the 2 functions to our plugin.

/**
* Only gives single column option on our pricing tables
*
* @since 1.0
* @author SFNdesign, Curtis McHale
* @access public
*
* @param array  $columns            The array of column options already present
*
* @rutern array     $columns            Our modified array of columns
*/
public function only_single_column_option( $colmuns ){
    $columns['wptt_single_column'] = 1;
    return $columns;
}
 
/**
* Forces the page in to a single column view
*
* @since 1.0
* @author SFNdesign, Curtis McHale
* @access public
*
* @return int       The number of columns the screen should be
*/
public function single_column(){
 
    return 1;
 
} // single column

The first filter forces the post type to display as a single column. The filter is officially get_user_option_screen_layout_{$post_type}. So if you wanted to change the post type just change the last bit to whatever your registered post type name is.

The second function removes the option of 2 columns from the WordPress admin. Here we change the array of options only for our custom post type. Again you use the registered name of the CPT to make sure you limit it properly.

That’s it we now have a post type registered and displayed as a single column. You can see the full code just below.

<?php
/*
Plugin Name: Single Column Post Type
Plugin URI: http://wpthemetutorial.com
Description: Gives us a post type with a single column
Version: 1.0
Author: SFNdesign, Curtis McHale
Author URI: http://wpthemetutorial.com
License: GPLv2 or later
*/
 
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
 
class WPTT_Single_Column_CPT{
 
    function __construct(){
 
        add_action( 'init', array( $this, 'add_cpt' ) );
 
        add_filter( 'get_user_option_screen_layout_wptt_single_column', array( $this, 'single_column' ) );
        add_filter( 'screen_layout_columns', array( $this, 'only_single_column_option' ) );
 
    } // construct
 
    /**
     * Only gives single column option on our pricing tables
     *
     * @since 1.0
     * @author SFNdesign, Curtis McHale
     * @access public
     *
     * @param array     $columns     The array of column options already present
     *
     * @return array    $columns     Our modified array of columns
    */
    public function only_single_column_option( $colmuns ){
        $columns['wptt_single_column'] = 1;
        return $columns;
    }
 
    /**
    * Forces the page in to a single column view
    *
    * @since 1.0
    * @author SFNdesign, Curtis McHale
    * @access public
    *
    * @return int     The number of columns the screen should be
    */
    public function single_column(){
 
        return 1;
 
    } // single column
 
 
    /**
     * Builds out the custom post types for the site
     *
     * @uses    register_post_type
     *
     * @since   1.0
     * @author  WPTT, Curtis McHale
     */
    public function add_cpt(){
 
        register_post_type('wptt_single_column',
            array(
                'labels'                => array(
                    'name'                  => __('Single Column'),
                    'singular_name'         => __('Single Column'),
                    'add_new'               => __('Add New'),
                    'add_new_item'          => __('Add New Single Column'),
                    'edit'                  => __('Edit'),
                    'edit_item'             => __('Edit Single Column'),
                    'new_item'              => __('New Single Column'),
                    'view'                  => __('View Single Column'),
                    'view_item'             => __('View Single Column'),
                    'search_items'          => __('Search Single Column'),
                    'not_found'             => __('No Single Column Found'),
                    'not_found_in_trash'    => __('No Single Column found in Trash')
                ), // end array for labels
                'public'                => true,
                'menu_position'         => 5, // sets admin menu position
                'hierarchical'          => false, // funcions like posts
                'supports'              => array('title', 'editor', 'revisions', 'excerpt', 'thumbnail'),
                'rewrite'               => array('slug' => 'wptt-single-column', 'with_front' => true,), // permalinks format
                'can_export'            => true,
            )
        );
 
    }
 
} // WPTT_Single_Column_CPT
 
new WPTT_Single_Column_CPT();

Screencast

When is wp_mail NOT pluggable?

Have you heard about Pluggable Functions in WordPress? A while ago I wrote a tutorial about how to log email by plugging wp_mail which is a pluggable function.

Then I went off skipping on my way in to a land of bliss knowing I’d never again email clients by accident.

Cue the dark clounds and ominous music.

wp_mail is not plugged when…

So there I was skipping along through a field of flowers and troubleshooting some password reset issues on a staging site only to find that the password reset emails were not getting logged.

I had actually just sent a password reset email to a user by accident which made me frown and my client frowned. Lucky for us the user was a developer of a different stripe (not a WordPress developer) so when I emailed him about the mistake he understood.

We still looked silly.

wp_mail is not pluggable during a password reset it will send your emails so my previous plugin didn’t work.

We were also running in to issues with plugins like [wpMandril][mandril] which also ‘plug’ wp_mail. You can’t plug a function twice though so all we got were errors.

Logging all wp_mail without plugging it

Now let’s look at how I log wp_mail without using the pluggable function pattern. We’ll start by taking a look at the full wp_mail code.

if ( !function_exists( 'wp_mail' ) ) :
/**
 * Send mail, similar to PHP's mail
 *
 * A true return value does not automatically mean that the user received the
 * email successfully. It just only means that the method used was able to
 * process the request without any errors.
 *
 * Using the two 'wp_mail_from' and 'wp_mail_from_name' hooks allow from
 * creating a from address like 'Name <email@address.com>' when both are set. If
 * just 'wp_mail_from' is set, then just the email address will be used with no
 * name.
 *
 * The default content type is 'text/plain' which does not allow using HTML.
 * However, you can set the content type of the email by using the
 * 'wp_mail_content_type' filter.
 *
 * The default charset is based on the charset used on the blog. The charset can
 * be set using the 'wp_mail_charset' filter.
 *
 * @since 1.2.1
 * @uses apply_filters() Calls 'wp_mail' hook on an array of all of the parameters.
 * @uses apply_filters() Calls 'wp_mail_from' hook to get the from email address.
 * @uses apply_filters() Calls 'wp_mail_from_name' hook to get the from address name.
 * @uses apply_filters() Calls 'wp_mail_content_type' hook to get the email content type.
 * @uses apply_filters() Calls 'wp_mail_charset' hook to get the email charset
 * @uses do_action_ref_array() Calls 'phpmailer_init' hook on the reference to
 *      phpmailer object.
 * @uses PHPMailer
 *
 * @param string|array $to Array or comma-separated list of email addresses to send message.
 * @param string $subject Email subject
 * @param string $message Message contents
 * @param string|array $headers Optional. Additional headers.
 * @param string|array $attachments Optional. Files to attach.
 * @return bool Whether the email contents were sent successfully.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // Compact the input, apply the filters, and extract them back out
    extract( apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) ) );
 
    if ( !is_array($attachments) )
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
 
    global $phpmailer;
 
    // (Re)create it, if it's gone missing
    if ( !is_object( $phpmailer ) || !is_a( $phpmailer, 'PHPMailer' ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true );
    }
 
    // Headers
    if ( empty( $headers ) ) {
        $headers = array();
    } else {
        if ( !is_array( $headers ) ) {
            // Explode the headers out, so this function can take both
            // string headers and an array of headers.
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = array();
        $cc = array();
        $bcc = array();
 
        // If it's actually got contents
        if ( !empty( $tempheaders ) ) {
            // Iterate through the raw headers
            foreach ( (array) $tempheaders as $header ) {
                if ( strpos($header, ':') === false ) {
                    if ( false !== stripos( $header, 'boundary=' ) ) {
                        $parts = preg_split('/boundary=/i', trim( $header ) );
                        $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
                    }
                    continue;
                }
                // Explode them out
                list( $name, $content ) = explode( ':', trim( $header ), 2 );
 
                // Cleanup crew
                $name    = trim( $name    );
                $content = trim( $content );
 
                switch ( strtolower( $name ) ) {
                    // Mainly for legacy -- process a From: header if it's there
                    case 'from':
                        if ( strpos($content, '<' ) !== false ) {
                            // So... making my life hard again?
                            $from_name = substr( $content, 0, strpos( $content, '<' ) - 1 );
                            $from_name = str_replace( '"', '', $from_name );
                            $from_name = trim( $from_name );
 
                            $from_email = substr( $content, strpos( $content, '<' ) + 1 );
                            $from_email = str_replace( '>', '', $from_email );
                            $from_email = trim( $from_email );
                        } else {
                            $from_email = trim( $content );
                        }
                        break;
                    case 'content-type':
                        if ( strpos( $content, ';' ) !== false ) {
                            list( $type, $charset ) = explode( ';', $content );
                            $content_type = trim( $type );
                            if ( false !== stripos( $charset, 'charset=' ) ) {
                                $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset ) );
                            } elseif ( false !== stripos( $charset, 'boundary=' ) ) {
                                $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset ) );
                                $charset = '';
                            }
                        } else {
                            $content_type = trim( $content );
                        }
                        break;
                    case 'cc':
                        $cc = array_merge( (array) $cc, explode( ',', $content ) );
                        break;
                    case 'bcc':
                        $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
                        break;
                    default:
                        // Add it to our grand headers array
                        $headers[trim( $name )] = trim( $content );
                        break;
                }
            }
        }
    }
 
    // Empty out the values that may be set
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();
 
    // From email and name
    // If we don't have a name from the input headers
    if ( !isset( $from_name ) )
        $from_name = 'WordPress';
 
    /* If we don't have an email from the input headers default to wordpress@$sitename
     * Some hosts will block outgoing mail from this address if it doesn't exist but
     * there's no easy alternative. Defaulting to admin_email might appear to be another
     * option but some hosts may refuse to relay mail from an unknown domain. See
     * http://trac.wordpress.org/ticket/5007.
     */
 
    if ( !isset( $from_email ) ) {
        // Get the site domain and get rid of www.
        $sitename = strtolower( $_SERVER['SERVER_NAME'] );
        if ( substr( $sitename, 0, 4 ) == 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }
 
        $from_email = 'wordpress@' . $sitename;
    }
 
    // Plugin authors can override the potentially troublesome default
    $phpmailer->From     = apply_filters( 'wp_mail_from'     , $from_email );
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name  );
 
    // Set destination addresses
    if ( !is_array( $to ) )
        $to = explode( ',', $to );
 
    foreach ( (array) $to as $recipient ) {
        try {
            // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
            $recipient_name = '';
            if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                if ( count( $matches ) == 3 ) {
                    $recipient_name = $matches[1];
                    $recipient = $matches[2];
                }
            }
            $phpmailer->AddAddress( $recipient, $recipient_name);
        } catch ( phpmailerException $e ) {
            continue;
        }
    }
 
    // Set mail's subject and body
    $phpmailer->Subject = $subject;
    $phpmailer->Body    = $message;
 
    // Add any CC and BCC recipients
    if ( !empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddCc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }
 
    if ( !empty( $bcc ) ) {
        foreach ( (array) $bcc as $recipient) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddBcc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }
 
    // Set to use PHP's mail()
    $phpmailer->IsMail();
 
    // Set Content-Type and charset
    // If we don't have a content-type from the input headers
    if ( !isset( $content_type ) )
        $content_type = 'text/plain';
 
    $content_type = apply_filters( 'wp_mail_content_type', $content_type );
 
    $phpmailer->ContentType = $content_type;
 
    // Set whether it's plaintext, depending on $content_type
    if ( 'text/html' == $content_type )
        $phpmailer->IsHTML( true );
 
    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );
 
    // Set the content-type and charset
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
 
    // Set custom headers
    if ( !empty( $headers ) ) {
        foreach( (array) $headers as $name => $content ) {
            $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
        }
 
        if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
            $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
    }
 
    if ( !empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }
 
    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );
 
    // Send!
    try {
        return $phpmailer->Send();
    } catch ( phpmailerException $e ) {
        return false;
    }
}
endif;

That’s a lot of code!

The important parts are in the last 5 lines of the main function though. Specifically do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );. Here we have a WordPress action that we can hook and it passes in all of the stuff thats been set up inwp_mail` to the PHPMailer class.

At this point in wp_mail everything is setup. We have a full message contents, proper headers, all the from/to/cc email addresses are set. The very next thing that wp_mail tries to do is send the email from the server.

So that phpmailer_init hook is where we need to be.

function wptt_mail_log( $phpmailer ){
 
    $phpmailer->ClearAllRecipients();
    return $phpmailer;
 
} // mail_log
add_action( 'phpmailer_init', 'wptt_mail_log' );

Now we have a function that turns off all email going out of WordPress by clearing all the recipients for the email. There are literally no email addreses to send to so it goes no where.

Now of course that doesn’t create the log so take a look at the full code of my WPTT Basic Email Logging plugin to see how we create the logs and how we limit it to only capturing emails if we are not on a live environment.

Screencast

Screen Shot 2014-03-20 at 8.06.42 AM

Change product category order in WooCommerce

The default setting for WooCommerce product category listing is to order them however you entered them.

I’ve never found this to be useful. Every client I’ve had wants categories to be ordered alphabetically. Today I’ll show you how.

woocommerce_product_subcategories_args filter

WooCommerce provides a nice filter to change the product category archive parameters which you can find with the rest of the documented filters.

Here’s an excerpt of the filter out of the main function in WooCommerce that displays categories. You can see the whole thing by opening wc-template-functions.php found inside the WooCommerce plugin includes folder.

        // NOTE: using child_of instead of parent - this is not ideal but due to a WP bug ( http://core.trac.wordpress.org/ticket/15626 ) pad_counts won't work
        $args = apply_filters( 'woocommerce_product_subcategories_args', array(
            'child_of'      => $parent_id,
            'menu_order'    => 'ASC',
            'hide_empty'    => 1,
            'hierarchical'  => 1,
            'taxonomy'      => 'product_cat',
            'pad_counts'    => 1
        ) );
 
        $product_categories     = get_categories( $args );

You can see here that the filter is allowing you to change the args that are sent to a standard WordPress get_categories call.

That means we can use any of the get_categories arguments here. So we’re going to change the orderby and order parameters.

/**
 * Changes the order of the product categories to be by slug ASC.
 * Essentially alphabetical.
 *
 * @since 1.0
 * @author WPTT, Curtis McHale
 */
function wptt_cat_order( $args ){
 
    $args['orderby'] = 'slug';
    $args['order'] = 'ASC';
    return $args;
 
} // wptt_cat_order
add_filter( 'woocommerce_product_subcategories_args', 'wptt_cat_order' );

Now you might be wondering why I’m using ‘slug’ instead of name. See if your category titles have fancy characters like & the alphabetical order on name gets thrown off. Slug is a normalized field, which means that it gets all the fancy text stripped out.

Setting up Stripe with WooCommerce

Today’s screencast will show you how to set up Stripe with WooCommerce since I’ve had a few questions about it.

Screen Shot 2014-02-01 at 9.19.18 PM

New Guide to deployment with Beanstalk

Quite a while ago I wrote about deployment with Beanstalk and that’s still how I do it.

Beanstalk just also released a guide to deploying WordPress for a slightly different take on the matter.

Screen Shot 2014-01-22 at 2.13.02 PM

Serializing and Saving a complete form with jQuery Form in WordPress

WordPress comes bundled with a great jQuery library called jQuery Form which makes saving forms via AJAX super easy.

Today I’m going to show you why you shouldn’t be using standard AJAX calls to save forms and how to use jQuery Form in WordPress.

You can download the plugin I use in the screencast from Github[plugin].

Screencast

Screen Shot 2014-01-22 at 1.19.02 PM

Easier Documentation Searching with Dash

Part of being a great developer is simply knowing how to search for your current issue. There is no way I could keep all the documention for WordPress, PHP, jQuery, Sass…in my head.

My typical workflow has been to simply Google for the term and then click the first link since it’s usually the WordPress Codex entry.

Wouldn’t it be nicer to be able to search across many languages right out of your editor?

How about StackExchange and Google results mixed right in?

Well you can.

It is called Dash

Dash is a Mac App that is free to trial and has a $19.99 in app purchase.

At it’s simplest level dash is just a dedicated application to search documentation than includes Google Results and StackExchange results.

Where it really gets powerful (and what convinced me to part with $19.99 in 2 minutes) is the integration with your editor.

I use Vim so for me I hop on to GitHub and download the dash Vim plugin. That gives me access to the :Dash command from within my editor.

:Dash wp_send_json will bring up all the json functions in WordPress with Google and Stackexchange right under.

:Dash esc_ wordpress will give me all the escaping functions in WordPress (since I limited it) along with the Google and StackExchange results under it. If I wanted anything with esc_ in it then I’d omit the wordpress exclusion and I’d see PHP options as well.

dash-documentation-results

Installing documention is a simple as clicking download from the Dash preferences.

dash-preferences

Dash is awesome, go get it.

Screencast

Screen Shot 2013-12-19 at 2.50.07 PM

Add a custom folder on Plugin activation

Recently I needed to do a custom export of WooCommerce orders so we could send them to an external service. The external service could already read orders formatted in an XML file, so all I needed to do was get the WooCommerce orders exported in the XML format and put in a directory that could be accessed by the external service.

The first part of that was creating a new place to store files in WooCommerce which meant I needed to create a new folder when my plugin was activated.

Today we’ll take a look at how to add a folder on plugin activation.

Plugin Skeleton

The first thing we need is a plugin skeleton.

<?php
/*
Plugin Name: WPTT mkdir
Plugin URI: http://wpthemetutorial.com
Description: Demo to make a directory on plugin activation
Version: 1.0
Author: WPTT, Curtis McHale
Author URI: http://wpthemetutorial.com
License: GPLv2 or later
*/
 
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
 
class WPTT_Mkdir{
 
    function __construct(){
 
        add_action( 'admin_notices', array( $this, 'check_required_plugins' ) );
 
        // Register hooks that are fired when the plugin is activated, deactivated, and uninstalled, respectively.
        register_activation_hook( __FILE__, array( $this, 'activate' ) );
        register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
        register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
 
    } // construct
 
    /**
     * Checks for WooCommerce and GF and kills our plugin if they aren't both active
     *
     * @uses    function_exists     Checks for the function given string
     * @uses    deactivate_plugins  Deactivates plugins given string or array of plugins
     *
     * @action  admin_notices       Provides WordPress admin notices
     *
     * @since   1.0
     * @author  SFNdesign, Curtis McHale
     */
    public function check_required_plugins(){
 
        if( ! is_plugin_active( 'woocommerce/woocommerce.php' ) ){ ?>
 
            <div id="message" class="error">
                WPTT mkdir expects WooCommerce to be active. This plugin has been deactivated.
            </div>
 
            <?php
            deactivate_plugins( '/wptt-mkdir/wptt-mkdir.php' );
        } // if
 
    } // check_required_plugins
 
    /**
     * Fired when plugin is activated
     *
     * @param   bool    $network_wide   TRUE if WPMU 'super admin' uses Network Activate option
     */
    public function activate( $network_wide ){
 
    } // activate
 
    /**
     * Fired when plugin is deactivated
     *
     * @param   bool    $network_wide   TRUE if WPMU 'super admin' uses Network Activate option
     */
    public function deactivate( $network_wide ){
 
    } // deactivate
 
    /**
     * Fired when plugin is uninstalled
     *
     * @param   bool    $network_wide   TRUE if WPMU 'super admin' uses Network Activate option
     */
    public function uninstall( $network_wide ){
 
    } // uninstall
 
} // WPTT_Mkdir
 
new WPTT_Mkdir();

The function we’re going to spend our time in today is activate. activate is called by register_activation_hook which makes any of the content of the function run on plugin activation. Sure we could also run it on some WordPress hook like init but then it would run every time that the site is visited which would be dump.

Adding to register_activation_hook

The first thing we want to add to register_activation_hook is our function that will create the directory.

    /**
     * Fired when plugin is activated
     *
     * @param   bool    $network_wide   TRUE if WPMU 'super admin' uses Network Activate option
     */
    public function activate( $network_wide ){
 
        $this->create_custom_dir();
 
    } // activate

Now let’s take a look at our $this->create_custom_dir function.

    /**
     * Creates our custom upload directory
     *
     * @since 1.0
     * @author SFNdesign, Curtis McHale
     * @access private
     *
     * @uses $this->check_custom_dir()          Returns true if the dir already exists
     * @uses wp_mkdir_p()                       Creates dirpath recursively and sets file permissions if possible
     */
    public function create_custom_dir(){
 
        if ( $this->check_custom_dir() === true ) return;
 
        $url = WP_CONTENT_DIR . '/uploads/wpttcustom';
 
        wp_mkdir_p( $url );
 
    } // create_custom_dir

The first thing we do is check if the directory exists with another custom function (I’ll show you that in a second). If our directory already exists, because the user has already activated the plugin once, then we stop what we are doing.

If the directory hasn’t been created then we want to create it.

To do that we first use the WordPress constant WP_CONTENT_DIR which will give us the path to the wp-content folder on the server.

To that constant we append the path we want to create a directory in. For our case we use /uploads/wpttcustom.

Then we pass the path to wp_mkdir_p. wp_mkdir_p will do it’s best to create our directory recursively. So if we really wanted a path like /uploads/wptt/custom/anotherlevel it would create the whole path for us.

Checking if the directory exists

At the top of my function I had a check to see if the directory existed. Lets look at that now.

    /**
     * Returns true if the custom directory is in the uploads folder
     *
     * @since 1.0
     * @author SFNdesign, Curtis Mchale
     * @access private
     */
    private function check_custom_dir(){
 
        $url = WP_CONTENT_DIR . '/uploads/wpttcustom';
 
        if ( file_exists( $url ) ){
            return true;
        }
 
        return false;
 
    } // check_custom_dir

Here we use the same constant WP_CONTENT_DIR and define the same path and then use the PHP function file_exists to see if the directory exists.

If it does we will return true. If it doesn’t our function will send false, which would mean that we create the directory.

Now you may wonder why I broke 5 lines out in to it’s own function. A programming convention is the Single Responsibility Principle. So the only job of our create function is to create the directory. The only job of our check function is to check if the directory exists. You can read more about SOLID on Wikipedia.

Wrap up

That’s it, we have now created a custom directory on plugin activation.

Screencast

Screen Shot 2013-12-19 at 2.07.23 PM

Adding a new domain to an existing VVV (Vagrant) box

I’ve already written 2 posts on Vagrant, specifically using VVV to run your WordPress development boxes.

  1. Working with WordPress and Vagrant – Basics
  2. Vagrant and Custom Domains with WordPress

The second post did show you how you could add a custom domain to a Vagrant install, but didn’t specifically call out my day to day workflow for using Vagrant.

My VVV workflow

Most of the time I use the same VVV box for all projects. So all theme builds and plugin builds…To add a new site to an existing VVV box I use the instructions you’ll find below.

If I have to build a WordPress MU site I usually build a custom VVV box that has all the domains mapped and the dabatase setup for the user. Then the next developer just has to clone our VVV box and add the hosts file entries to get the project running.

Adding the new domain

Our first step is to open you terminal program and change directory (cd) to the VVV install you want to add a domain to. From the root type cd config/nginx/sites.

Here you should find a few files ending in the .conf extension. Copy one and rename it to screencast.wpthemetutorial.com.conf then open it in your editor of choice. You’ll need to change 2 lines.

  1. On line 28 change the server name to match the domain you want to see the site at: server_name screencast.wpthemetutorial.com;
  2. On line 31 you should see the path to the install location in your www directory: root /srv/www/wptt-screen;

Now we need to go back to the root of the Vagrant box and go in to our database directory by typing cd database.

Then open init-custom.sql so we can add our custom database commands. I copy the last entry and pasted it below. I’ll change all our values to swptt. I don’t think it’s worth making it super long/hard since this is not a live web sever and anyone that can access this has access to your machine.

CREATE DATABASE IF NOT EXISTS `swptt`;
GRANT ALL PRIVILEGES ON `swptt`.* TO 'swptt'@'localhost' IDENTIFIED BY 'swptt';

Now we need to go back to the root of the VVV box and type cd www to get to the web directory on our server. Here we need to move all the WordPress files in to their proper spot in the wptt-screen folder.

You can do this by typing cp -rv wordpress-default/ wptt-screen.

Finally I want to remove the wp-config.php file that was in the copied WordPress install. Type rm -rf wptt-screen/wp-config.php and the wp-config.php file will be gone.

Now you need to type vagrant up to start the VVV box.

Once Vagrant comes up all you should need to do is type screencast.wpthemetutorial.com in to your browser and then follow the WordPress install steps.

Screencast

WordPress Environment Configuration

I always work locally on client sites. It’s simply way faster to save a file and refresh the browser instead of waiting for it to upload then refreshing. You can also make mistakes locally and kill the whole site without worrying about downtime for your client.

It’s also pretty common for me to have a staging environment so clients can approve work before it launches to the live site.

These different environments do present some issues though. We don’t want users of the site to be able to access the staging site and we certainly don’t want to send any automated emails (like orders from WooCommerce) from the staging site as we test things. We do want to know if the emails got sent though and what their content was.

To catch emails I wrote a plugin that replaces the wp_mail function. There I recommended you set a constant in wp-config.php of DEVELOPMENT but there can be issues with setting those. Specifically if you’re using WPEngine and make use of their live to staging push features.

When you push live->staging on WPEngine it ends up changing wp-config.php back to it’s default values. That means your DEVELOPMENT constant is gone and you’re sending emails to clients again from the staging site. So any of the other cool options based on wp-config.php won’t work either.

To get around this quirk of how WPEngine operates I’ve released a plugin called WPTT Developer Constants. It’s meant to be run as an mu-plugin on your site. That means it will always be run and the constants will be available before your theme or plugins are loaded.

That means you could hook plugins_loaded and check to see if certain plugins are turned on based on the environment you are in. Like turn on the email logging plugin for STAGING_ENV and LOCAL_ENV and make sure it’s off for LIVE_ENV.

If you were using the older style DEVELOPMENT constant then I’ve kept that around as well. I’m not using it anymore since I found I want different environment configuration locally (like why restrict access to outsiders, it’s my computer it’s running on).

Go check the plugin out.