Setting up .htaccess URL Redirects in WordPress

The correct way to setup redirects within the WordPress .htaccess file is to put your redirects above the WordPress pretty permalinks code like so:

# BEGIN Redirects

Redirect 301 /from-here/

# END Redirects

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# END WordPress

Scheduling a category change, based on an Advanced Custom Field value, when a post is saved using a WordPress CRON job

The problem:

We had a client’s website that had a custom post type called ‘Events’. We also had a category called ‘Past Events’. This category hid the Event from the Current Events page. When an event had passed our client had to go into the backend and put the Event into the ‘Past Events’ category. As the volume of Events increased this process became too time intensive.

We wanted to maintain the mechanism of using a category to hide them as we had used this in many loops throughout the site but we needed a way to make a past event automatically add to that category at a certain date.

In addition to this we needed the date to be set via a start date or optional end date field created with the brilliant Advanced Custom Fields Plugin.

So, in a nutshell we needed to change an individual post’s category on a given date (via an ACF field) sometime in the future.

The solution:


  1. Create a function that sets a WP Cron job to fire the day after an event ends
  2. Create or update that Cron job every time the post is saved (ie. created or edited)
  3. Create a function that puts the post into the past events category
  4. Run that function when the WP Cron job is fire

The code

The code solutions here are very well commented but I have not gone into detail about why I did certain things certain ways due to time. If there is interest in me doing this then please ask in the comments and I will find the time.

Create a function that sets a WP Cron job to fire the day after an event ends.

// Create a cron job to run the day after an event happens or ends
function set_expiry_date( $post_id ) {

  // See if an event_end_date or event_date has been entered and if not then end the function
  if( get_post_meta( $post_id, $key = 'event_end_date', $single = true ) ) {

    // Get the end date of the event in unix grenwich mean time
    $acf_end_date = get_post_meta( $post_id, $key = 'event_end_date', $single = true );

  } elseif ( get_post_meta( $post_id, $key = 'event_date', $single = true ) ) {

    // Get the start date of the event in unix grenwich mean time
    $acf_end_date = get_post_meta( $post_id, $key = 'event_date', $single = true );

  } else {

    // No start or end date. Lets delete any CRON jobs related to this post and end the function.
    wp_clear_scheduled_hook( 'make_past_event', array( $post_id ) );


  // Convert our date to the correct format
  $unix_acf_end_date = strtotime( $acf_end_date );
  $gmt_end_date = gmdate( 'Ymd', $unix_acf_end_date );
  $unix_gmt_end_date = strtotime( $gmt_end_date );

  // Get the number of seconds in a day
  $delay = 24 * 60 * 60; //24 hours * 60 minutes * 60 seconds

  // Add 1 day to the end date to get the day after the event
  $day_after_event = $unix_gmt_end_date + $delay;

  // Temporarily remove from 'Past Event' category
  wp_remove_object_terms( $post_id, 'past-events', 'category' );

  // If a CRON job exists with this post_id them remove it
  wp_clear_scheduled_hook( 'make_past_event', array( $post_id ) );
  // Add the new CRON job to run the day after the event with the post_id as an argument
  wp_schedule_single_event( $day_after_event , 'make_past_event', array( $post_id ) );


Create or update that Cron job every time the post is saved (ie. created or edited)

// Hook into the save_post_{post-type} function to create/update the cron job everytime an event is saved.
add_action( 'acf/save_post', 'set_expiry_date', 20 );

Create a function that puts the post into the past events category

// Create a function that adds the post to the past-events category
function set_past_event_category( $post_id ){

  // Set the post category to 'Past Event'
  wp_set_post_categories( $post_id, array( 53 ), true );


Run that function when the WP Cron job is fire

// Hook into the make_past_event CRON job so that the set_past_event_category function runs when the CRON job is fired.
add_action( 'make_past_event', 'set_past_event_category' );


Good WP Cron job tutorial

ACF save_post function

A plugin used for debugging the CRON jobs

Open a list of URL’s in Google Chrome using Mac Automator

We are currently delving, as a studio, into any automation tasks that can speed up our workflow and make our life less boring. The first step of this was to create a way to speedily open up three webpages that I use to create and send invoices to our clients with. To do this I used Automator which comes as standard with Mac OS and a bit of applescript. The steps I took are outlined below.

1. Open up automator and make a new Workflow document

Screen Shot 2016-01-06 at 12.41.19

2. Add the “get specified URL’s” task to the workflow and enter in the three urls you want to open. They will be passed onto the next task as a list.

Screen Shot 2016-01-06 at 12.42.51

3. Add the “Run Applescript” task to the workflow and enter in the correct applescript to loop through the list of url’s (passed to the task as the input variable) and tell Google Chrome to open the location.

# input is the list of url's from the previous task
on run {input, parameters}
	# The below is an applescript loop
	repeat with theURL in input
		tell application "Google Chrome" to open location theURL
	end repeat
	# We must return something so we just return the input
	return input

end run

Screen Shot 2016-01-06 at 12.43.15

4. Save and run the automator workflow.

This is a relatively simple task but important for us to begin automating some of the more dreary tasks we have to complete in the studio.

Developing a Shopify theme locally

Here at Multiple States we use Shopify as our recommended e-commerce platform. When we need to work on a theme locally we use the Shopify theme gem. It is a command line tool that lets you make live changes to themes in your Shopify account.

Within our clients Shopify account we have two installs of the theme, production and staging. The Shopify theme gem allows us to push changes to the staging theme and preview them in browser for testing before we push them to production.

Installing the Shopify theme gem

To install the Shopify theme gem on OS X El Capitan you will need to follw the following steps:

  1. Open Terminal
  2. First, we need to install Homebrew. Homebrew allows us to install and compile software packages easily from source. Homebrew comes with a very simple install script. When it asks you to install XCode CommandLine Tools, say yes. Open Terminal and run the following command:
    ruby -e "$(curl -fsSL"
  3. Now that we have Homebrew installed, we can use it to install Ruby. We’re going to use rbenv to install and manage our Ruby versions. To do this, run the following commands in your Terminal:
    brew install rbenv ruby-build
    # Add rbenv to bash so that it loads every time you open a terminal
    echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
    source ~/.bash_profile
    # Install Ruby
    rbenv install 2.2.3
    rbenv global 2.2.3
    ruby -v
  4. To install the shopify_theme gem use ‘gem install’ (you might have use ‘sudo gem install’)
    gem install shopify_theme
  5. To setup your config file you can follow the instruction outlined at



A list of the default settings for flexslider

The documentation does not have all of these listed (as far as I can tell) but here they are pulled straight from the code:

//FlexSlider: Default Settings
  $.flexslider.defaults = {
    namespace: "flex-",             //{NEW} String: Prefix string attached to the class of every element generated by the plugin
    selector: ".slides > li",       //{NEW} Selector: Must match a simple pattern. '{container} > {slide}' -- Ignore pattern at your own peril
    animation: "fade",              //String: Select your animation type, "fade" or "slide"
    easing: "swing",                //{NEW} String: Determines the easing method used in jQuery transitions. jQuery easing plugin is supported!
    direction: "horizontal",        //String: Select the sliding direction, "horizontal" or "vertical"
    reverse: false,                 //{NEW} Boolean: Reverse the animation direction
    animationLoop: true,            //Boolean: Should the animation loop? If false, directionNav will received "disable" classes at either end
    smoothHeight: false,            //{NEW} Boolean: Allow height of the slider to animate smoothly in horizontal mode
    startAt: 0,                     //Integer: The slide that the slider should start on. Array notation (0 = first slide)
    slideshow: true,                //Boolean: Animate slider automatically
    slideshowSpeed: 7000,           //Integer: Set the speed of the slideshow cycling, in milliseconds
    animationSpeed: 600,            //Integer: Set the speed of animations, in milliseconds
    initDelay: 0,                   //{NEW} Integer: Set an initialization delay, in milliseconds
    randomize: false,               //Boolean: Randomize slide order
    fadeFirstSlide: true,           //Boolean: Fade in the first slide when animation type is "fade"
    thumbCaptions: false,           //Boolean: Whether or not to put captions on thumbnails when using the "thumbnails" controlNav.

    // Usability features
    pauseOnAction: true,            //Boolean: Pause the slideshow when interacting with control elements, highly recommended.
    pauseOnHover: false,            //Boolean: Pause the slideshow when hovering over slider, then resume when no longer hovering
    pauseInvisible: true,   		//{NEW} Boolean: Pause the slideshow when tab is invisible, resume when visible. Provides better UX, lower CPU usage.
    useCSS: true,                   //{NEW} Boolean: Slider will use CSS3 transitions if available
    touch: true,                    //{NEW} Boolean: Allow touch swipe navigation of the slider on touch-enabled devices
    video: false,                   //{NEW} Boolean: If using video in the slider, will prevent CSS3 3D Transforms to avoid graphical glitches

    // Primary Controls
    controlNav: true,               //Boolean: Create navigation for paging control of each slide? Note: Leave true for manualControls usage
    directionNav: true,             //Boolean: Create navigation for previous/next navigation? (true/false)
    prevText: "Previous",           //String: Set the text for the "previous" directionNav item
    nextText: "Next",               //String: Set the text for the "next" directionNav item

    // Secondary Navigation
    keyboard: true,                 //Boolean: Allow slider navigating via keyboard left/right keys
    multipleKeyboard: false,        //{NEW} Boolean: Allow keyboard navigation to affect multiple sliders. Default behavior cuts out keyboard navigation with more than one slider present.
    mousewheel: false,              //{UPDATED} Boolean: Requires jquery.mousewheel.js ( - Allows slider navigating via mousewheel
    pausePlay: false,               //Boolean: Create pause/play dynamic element
    pauseText: "Pause",             //String: Set the text for the "pause" pausePlay item
    playText: "Play",               //String: Set the text for the "play" pausePlay item

    // Special properties
    controlsContainer: "",          //{UPDATED} jQuery Object/Selector: Declare which container the navigation elements should be appended too. Default container is the FlexSlider element. Example use would be $(".flexslider-container"). Property is ignored if given element is not found.
    manualControls: "",             //{UPDATED} jQuery Object/Selector: Declare custom control navigation. Examples would be $(".flex-control-nav li") or "#tabs-nav li img", etc. The number of elements in your controlNav should match the number of slides/tabs.
    customDirectionNav: "",         //{NEW} jQuery Object/Selector: Custom prev / next button. Must be two jQuery elements. In order to make the events work they have to have the classes "prev" and "next" (plus namespace)
    sync: "",                       //{NEW} Selector: Mirror the actions performed on this slider with another slider. Use with care.
    asNavFor: "",                   //{NEW} Selector: Internal property exposed for turning the slider into a thumbnail navigation for another slider

    // Carousel Options
    itemWidth: 0,                   //{NEW} Integer: Box-model width of individual carousel items, including horizontal borders and padding.
    itemMargin: 0,                  //{NEW} Integer: Margin between carousel items.
    minItems: 1,                    //{NEW} Integer: Minimum number of carousel items that should be visible. Items will resize fluidly when below this.
    maxItems: 0,                    //{NEW} Integer: Maxmimum number of carousel items that should be visible. Items will resize fluidly when above this limit.
    move: 0,                        //{NEW} Integer: Number of carousel items that should move on animation. If 0, slider will move all visible items.
    allowOneSlide: true,           //{NEW} Boolean: Whether or not to allow a slider comprised of a single slide

    // Callback API
    start: function(){},            //Callback: function(slider) - Fires when the slider loads the first slide
    before: function(){},           //Callback: function(slider) - Fires asynchronously with each slider animation
    after: function(){},            //Callback: function(slider) - Fires after each slider animation completes
    end: function(){},              //Callback: function(slider) - Fires when the slider reaches the last slide (asynchronous)
    added: function(){},            //{NEW} Callback: function(slider) - Fires after a slide is added
    removed: function(){},           //{NEW} Callback: function(slider) - Fires after a slide is removed
    init: function() {}             //{NEW} Callback: function(slider) - Fires after the slider is initially setup

Add custom image sizes to the WordPress media upload image size menu

When adding a custom image size in your WordPress functions file using add_image_size() it is not automatically added to your media upload image size dropdown menu. To add your new image size use:

add_filter( 'image_size_names_choose', 'my_custom_sizes' );

function my_custom_sizes( $sizes ) {
    return array_merge( $sizes, array(
        'your-custom-size' => __('Your Custom Size Name'),
    ) );