Die query_posts()! Die!

For some it’s well known that query_posts() should never be used, but I still see it dotted around here and there, so this is my attempt to help kill it off.

Every time you use query_posts() a kitten does

Every time you use query_posts() a kitten dies. Photo by Brian Scott.

I’m going to keep this post as non-technical as I can. The ins and outs of query_posts() is best left to this video by Andrew Nacin:

Instead, I’m going to talk about what you should be using instead of query_posts() and some of the nuances of pre_get_posts that could land you in hot water. But first…

Why Is query_posts() So Bad?

Mainly, for two reasons:

Query Then Template

It’s bad because runs against WordPress’ handling of website url to page load. query_posts() is directly linked with the main query – this isn’t a handy-wavy notion – it’s a real (global) object: $wp_query. This query is formed from the url of the current page and determines what WordPress displays. But importantly WordPress performs the query first and then chooses the appropriate template.

However, when you use query_posts() in a template file you doing the opposite – you’re trying set the ‘main query’ based on the template used. This can lead to pagination issues and 404s.

It’s Linked to the Main Query

Secondly, the main query is what WordPress thinks is the main content of what it’s displaying. When you change that via query_posts() to display a list of related posts, for example, you end up changing what WordPress thinks it’s displaying. You may end up displaying the comments for the last post on that ‘related post’ section and not the page being viewed.

What’s the solution?

Nice and easy:

1. Secondary queries

For displaying posts in the sidebar / beneath content – or just generally performing ‘secondary queries’ – that is queries that does not related to the main content of the page:

<ul>
  <?php
  global $post;
  $posts = get_posts( array( 'numberposts' => 5, 'category' => 3 ) );
  if( $posts ):
     foreach( $posts as $post ) :   
        setup_postdata($post); ?>

       <li>
          <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
       </li>

     <?php endforeach; 
     wp_reset_postdata(); 
   endif; ?>
</ul>

or

<ul>
  <?php
  $my_query = new WP_Query( array( 'numberposts' => 5, 'category' => 3 ) );
  if( $my_query->have_posts() ):
     while( $my_query->have_posts() ):
        $my_query->the_post(); ?>

       <li>
          <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
       </li>

     <?php endwhile; 
     wp_reset_postdata(); 
   endif; ?>
</ul>

both work fine.

Note the wp_reset_postdata(); – this is important so WordPress doesn’t confuse the last post in your secondary loop for the post associated with the current page!

2. Altering the Main Query

If you wish to alter the main query, for instance to exclude a category from the search page, then the action pre_get_posts (yes it’s an action not a filter).

add_action('pre_get_posts','my_alter_query');
function my_alter_query( $query ){

      //Is $query the main query? And is the main query for a search
      if( $query->is_main_query() && is_search() ){
        //Do something to main query

        //Exclude category with ID 12.
        $query->set( 'cat', '-12' );
      }
}

That snippet should live in a plug-in, though it would also work in your theme’s functions.php.

But before you start tinkering with queries via pre_get_posts, take note of the health warnings…

pre_get_posts is triggered for every query

This includes admin and non-admin queries, for posts, navigation menu items (yes they’re post types), related post widgets. The lot. That is why if you only mean to alter the main query you check the conditional $query->is_main_query().

But this makes it incredibly powerful. Now I can not just alter the main query, but I can alter any query for a particular post type:

add_action('pre_get_posts','my_alter_query');
function my_alter_query( $query ){

      $pt = $query->get( 'post_type' );

      if( 'event' == $pt || ( is_array( $pt ) && array( 'event' == $pt )  ){                  
         //Do something to queries which are for events only
      }

      if( 'event' == $pt || ( is_array( $pt ) && in_array( 'event', $pt )  ){                  
         //Do something to queries which include events
      }

      if( $query->is_tax( 'event-category' )  ){                  
         //Is the query for an event category
      }

}

Take away point: pre_get_posts is not just for the ‘main query’

Functions vs Method

You’ll note that in the above example I used the method $query->is_main_query() and the function is_search(). What’s the difference? Quite a lot:

Functions = main query, Methods = current query

Conditional functions such as is_search() and is_tax() are wrappers for

 global $wp_query; 
 $wp_query->is_search();
 $wp_query->is_tax();

That is they apply to the ‘main query’. But since $query passed in pre_get_posts is not the always the main query, using a methods or functions are – in general – not the same thing. You’ll need to think about which you need to use: should the conditional apply to the passed $query object, or the main query object?

Lastly, Tom McFarlin has recently written a couple of articles on query_posts() and pre_get_posts. I recommend you check them out:

And also Rarst’s epic answer on WPSE: When should you use WP_Query vs query_posts() vs get_posts()?

Event Organiser – earn 25% commission

Yesterday I announced the launch of Event Organiser’s Affiliates Program, which rewards with 25% of the checkout price of every successful referral.

Event Organiser Pro comes in three licenses: Personal (£40), Business (£80) and Developer (£120), meaning you could earn up to £30 for one sale.

… but that’s not all, because there’s a lot more add-ons that a due to be rolled out which you could be earning money on:

  • ICAL sync – Subscribe your site to a feed, and automatically import the events
  • Front-end submission – Allows users to submit events from the front-end.
  • Discount Codes – (requires Pro) – Add discount codes for event bookings
  • Mailchimp – (requires Pro) – Subscribe attendees to a MailChimp mailing list.

Some of these will be included free for with the Pro Developer license, but all will be available to purchase separately.

ICAL Sync: Automatically Importing Feeds

I’ve just finished writing up an add-on for Event Organiser which allows you to automatically import events from an ICAL feed.

ical-sync

The standard version of Event Organiser allows you to manually import events by uploading an ICAL file, but this ICAL Sync gives you the ability to easily add feeds and then forget about them: the plug-in periodically checks the feeds for updates, and automatically updates your events and trashes events which were at one point imported from the feed, but are no longer present.

This is incredibly useful for users who prefer to manage their events through their Google calendar, or if you have a network of sites and you wish to collate them on one ‘parent’ site (listing local WordPress meetups anyone?).

The add-on is just going through the last bit of beta-testing, and this is where you can help. Sign up to help beta-test the plug-in and get a free life-time license when the add-on is launched.

Click here to find out more

Nonces That Are Used Only Once

A while back in this article on wptuts I talked about nonces, and how they work. Normally, nonces are ‘one use only’ – once they are used they are stored, and never accepted again. WordPress, on the other hand, generates a new nonce every 12 hours, each with a life span of 24 hours. They can be used as many times during those 24 hours as needed. And that is, usually, all that is necessary. However instances may occur when you want to create and check nonces that are actually only used once.

Telling the time()

So how can we achieve this? First we want to create a fresh nonce for (essentially) every page load – since if our nonce can only be used once, then you’ll want to be able to do more than one action every 12 hours. I could force WordPress to generate new nonces by shortening the life of the nonce – but this is not really what I want and I might end up with a nonce, valid for one second. Not great.

Instead the idea is to create a standard nonce, whose ‘action’ includes a timestamp. This ensures a fresh nonce is generated every second (say) – which is enough for our purpose (nonces are unique to the user, and should also be unique to the action being taken, and if applicable, the object the action is being applied to). Of course to validate the nonce, we’ll also needed to send the timestamp.

So to create our nonce, we just use wp_create_nonce, but after concatenating it with a timestamp. The returned ‘nonce’ now consists of two parts: the nonce value and timestamp, which we join with hyphen

function sh_create_onetime_nonce($action = -1) {
    $time = time();
    $nonce = wp_create_nonce($time.$action);
    return $nonce . '-' . $time;
}

We can then embed the nonce and timestamp together in the form/url – or however the action is being triggered.

 <form>
    <input type="hidden" name="nonce" value="<?php echo sh_create_onetime_nonce( 'this-action' );?>"/>

    <input type="submit" value="Perform this action">
 </form>

Validating the nonce

Now to verify our nonce. The nonce consists of the original nonce generated by WordPress, combiend witht he timestamp of when it was generated (separated by a hyphen). So first we need to extract the constituent parts.

Concatenating the timestamp part with the action, we use the wp_verify_nonce function agains the nonce part. This checks that the nonce is valid. Of course, it already has a validity lifespan of 24 hours – but in order to reduce the number of used nonces we might have to store, I’ve opted to impose a 1 hour life span as well.

If the nonce is valid, we then check that it hasn’t be used. Used nonces are stored in the database as a serialised array. The array is of the form array( $nonce => $expires_timestamp ) and ordered by timestamp. (I did consider storing them in a separate table, but this is beyond the scope of the article and if we try to keep the number of nonces that might be stored down – say by setting the 1 hour life span, storing them in array shouldn’t present any problems).

If the nonce value hasn’t be used, we proceed to remove any ‘expired’ nonces (ones that would now fail the 1 hour life check) and add this used nonce into the array – and then sorting.

function sh_verify_onetime_nonce( $_nonce, $action = -1) {

    //Extract timestamp and nonce part of $_nonce
    $parts = explode( '-', $_nonce );
    $nonce = $parts[0]; // Original nonce generated by WordPress.
    $generated = $parts[1]; //Time when generated

    $nonce_life = 60*60; //We want these nonces to have a short lifespan
    $expires = (int) $generated + $nonce_life;
    $time = time(); //Current time

    //Verify the nonce part and check that it has not expired
    if( ! wp_verify_nonce( $nonce, $generated.$action ) || $time > $expires )
        return false;

    //Get used nonces
    $used_nonces = get_option('_sh_used_nonces');

    //Nonce already used.
    if( isset( $used_nonces[$nonce] ) ) )
        return false;

    foreach ($used_nonces as $nonce=> $timestamp){
        if( $timestamp > $time )
            break;

        //This nonce has expired, so we don't need to keep it any longer
        unset( $used_nonces[$nonce] );
    }

    //Add nonce to used nonces and sort
    $used_nonces[$nonce] = $expire;
    asort( $used_nonces )
    update_option( '_sh_used_nonces',$used_nonces );
}

And there we have it, nonces that can actually only be used once.

Git & the WordPress Repository

A while back I wrote an article on WP.Tuts on using Git to publish plug-ins on the WordPress repository. The wordpress.org plugin repository runs on Subversion, but I, for various reasons prefer to use Git. It’s also incredibly useful for putting your plug-on GitHub or BitBucket where you can collaborate with other developers.

The trouble is two-fold:

  • Git & SVN don’t play particularly nice
  • My local (and Github) Git repository are focussed on development, the wordpress.org is solely for distribution.

Let me explain the second point a bit more. If you’ve worked with Git you’ll probably be aware that you are to commit little and often (see for example, Tom McFarlin’s recent post). But the WordPress repository is for distribution – they don’t need or want all the those commits.

In fact, as you probably know, Otto has said:

“If I catch you at it [pushing each commit separately], then I will ban you from WordPress.org. The SVN only needs your final working version committed to it, not the entire history of the hundreds of changes you made using Git. Flatten your changes to a single commit.”

Now in my original article I outlined a workflow in which you develop in a ‘development branch’ and then when ready to release something, merge and squash those changes into your master, before pushing to the SVN. Unfortunately, beyond you current ‘development branch’ you loose all your history.

A Better Workflow

I’m not sure why this didn’t occur to me earlier, but by essentially reversing this workflow, we can preserver our history, but also commit to the WordPress subversion times only once or twice for each tagged release. We do this by developing in the master (or some other branch), and merge and squash into a release branch. We push from the release branch to the SVN repository.

This isn’t actually different from what we were originally doing since from a git point a view (but not a Github point of view) the master branch is just another branch. But we do need to set up our release branch to track the remote SVN repository.

How It Works

(I’m posting this as much for my own reference as anything else). I’ll assume that you’ve been working on a project for some time, and let’s suppose its in Github, but now you want to publish it to the WordPress repository. Once you’ve been accepted you’ll receive an e-mail containing a link to your wordpress.org hosted SVN repository, something like http://plugins.svn.wordpress.org/your-plugin-name.

Step 1: Clone the SVN Repository

The WordPress repository is big, to clone our plug-in we’ll first tell git where to find it. The following will get the first log of our SVN repository (when it was originally added):

 svn log -r 1:HEAD --limit 1 http://plugins.svn.wordpress.org/your-plug-in-name

It will think for a while and then you should see something like this:

 r651844 | plugin-master | 2013-01-13 05:33:38 +0000 (Sun, 13 Jan 2013) | 1 line

That rXXXXXX (revision) number is what we’ll use to ‘find’ our repository. It’s not necessary, but speeds things up somewhat. So next we clone our repository:

  git svn clone -s -r651844 --no-minimize-url http://plugins.svn.wordpress.org/your-plugin-name

This should take a few minutes, but it will initialise a git repository with the folder your-plug-name. Let’s view the branches in that repository:

  cd your-plugin-name
  git branch -a

This should list master (your local repo – this will probably be starred as its the branch you’re in currently). As a remote repository it will also have (the SVN) remotes/trunk. Currently our repository is empty (since our plug-in’s SVN repository is). (If it wasn’t we could fast-forward through the changes and have an up-to-date copy.)

Step 2: Pull Our Project From Github

Our plug-in is currently sitting on Github, so the idea is to pull it into our newly created repository. So let’s add our Github repository (which I’ll call origin – but it can be anything) and then pull it into our repo:

 git remote add origin git@github.com:your-github-username/your-repo-name.git
 git pull origin master

Our plug-in should now be in our repository, along with its complete history. We can commit further changes if we desired.

Step 3: Create A Release Repository

Next create, and checkout our release repository:

 git checkout --track -b release remotes/trunk

This now tracks our SVN trunk sitting in the WordPress Repository. Everything is now set up. We can return to the master branch and make any changes if desired.

Step 4: Committing to SVN Trunk

Let’s suppose we’re ready to push to the SVN branch the latest and greatest changes we’ve made in our master branch. We checkout our release branch and merge (and squash!) our master branch

 git checkout release
 git merge --squash master

Note: after the first merge you’ll notice that you start getting merge conflicts – assuming you don’t develop on the ‘stable’ branch, these aren’t really conflicts at all, and its much easier to do the following which is the same as the above, but any ‘conflicts’ are automatically resolved in favour of the master branch:

 git merge --squash -Xtheirs master

Just to be sure we don’t loose anything on SVN we can perform a rebase (though I find this is never necessary and if you’re the only commiter you’ll probably find this too)

 git svn rebase

Hopefully you’ll get a message saying Current branch master is up to date. Then we’re ready to push:

 git svn dcommit

This may take a few minutes.

Step 5: Tagging A New Release

Our trunk is now up to date, but if we were tagging a new release (and so had updated the plug-in’s readme.txt stable tag) we also need to create that tag. This is very simple, for tagging “1.6.1” for instance:

 git svn tag "1.6.1"

Again, this may take a few minutes. You shall probably receive an e-mail notification from WordPress.org too. In about 5 minutes time the WordPress plug-in repository should be updated with your new release.

I hope this helps! If you’ve any suggestions on better workflows, I’d love to hear about them, so please leave a comment :).

Event Organiser 1.6 – What’s New

1.6 is seeing a lot of improvement in terms of functionality – as well as some additional hooks and functions (documentation will follow 1.6 release). There are also some nagging bug fixes.

First things first..

Beta Testers…. please :)

Obviously I would like the release of 1.6 to go without hitch – so I have been, and will be, testing 1.6 before I release it. But the more testers – the more chance we have of catching any bugs. 1.6. is already pretty stable – so if you just want to take it for a spin then feel free – and please report any issues!

The beta version is available on GitHub (dev branch): https://github.com/stephenh1988/Event-Organiser/tree/dev

New Features

Multiple Venues On Maps

Venue maps can be produced in one of two ways:

  • via shortcodes in post/page/event content [eo_venue_map]
  • or via a function for use in templates (echo eo_get_venue_map();)

By default they use the current (in the Loop) event’s venue. But now you can have several locations on one map. With the shortcode use a comma delimited string:

 [eo_venue_map venue="london-eye,edinburgh-castle"]

With the function pass an array:

  echo eo_get_venue_map(array('london-eye','edinburgh-castle'));

As you may have noticed you can also have tooltips for venues which appear when you click the location. I talk about how to do this a little further down. Note that it doesn’t by default list upcoming events – but you can do this by using this script in your functions.php: https://gist.github.com/4190351

Extra Options For Venue Maps

The venue shortcode also supports the following attributes which are used to change the settings of the Google map:

  • zoomcontrol
  • rotatecontrol
  • pancontrol
  • overviewmapcontrol
  • streetviewcontrol
  • maptypecontrol
  • draggable
  • maptypeid – (e.g. ‘ROADMAP’, ‘SATELLITE’)

All but the last take true/false values and default to true. The last accepts a string (see Google map type IDs) and defaults to ‘ROADMAP’.

 [eo_venue_map venue="london-eye,edinburgh-castle" zoomcontrol=false]

With the function pass an array:

  echo eo_get_venue_map(array('london-eye','edinburgh-castle'),array('zoomcontrol'=>false));

Tooltips for Venue Maps

There is also ‘tooltip’ option (defaults to true) which displays a tooltip with the venue’s address.

The content of this tooltip is filterable using the hook eventorganiser_venue_tooltip.. So you can, for instance have a map with venues on it, and the tooltip for each venue lists (and links to) upcoming events at that venue.

 [eo_venue_map venue="london-eye,edinburgh-castle" tooltip=true]

With the function pass an array:

  echo eo_get_venue_map(array('london-eye','edinburgh-castle'),array('tooltip'=>true);

Extra Hooks

These will be documented shortly – feel free to poke me via twitter @stephenharris88 if I forget…

  • eventorganiser_event_color Filters an events colour (HEX code). This, for instance, allows you to colour events according to venue rather than category. See this post.
  • eventorganiser_calendar_event_link – allows you to replace or remove the link from the fullcalendar. See this post.
  • eventorganiser_event_tooltip – filtering the content of event tooltips on the fullcalendar. See this gist.
  • eventorganiser_fullcalendar_event – filters the entire event array before it is sent to the fullCalendar. This is an array that contains the event’s categories, venue, description, colour, dates etc.
  • eventorganiser_menu_position – change where EO appears in the admin menu (default: 5).
  • eventorganiser_widget_calendar_date_link – filters the widget calendar link. See this post.
  • eventorganiser_venue_tooltip – filters the tooltip for venues on venue map. See this gist

Create Venues On the Fly

There is now a ‘+’ button next to the Venue drop-down on the events admin page. Clicking this allows you to create a new venue for this event. The venue is created when the event is saved. A cancel button also appears when creating a new venue so that you can abandon that new venue and select a pre-existing one instead.

Widget Calendar Options

Aside from including/excluding past events, the Widget calendar has options to restrict the events it shows to certain categories or venues.

Improved UI

It won’t be long before WordPress ditches thickbox in core – so in EO I’ll be phasing out its use too. The admin calendar now uses jQuery UI Tabs and Dialogs – which I think looks a lot better. In case you’re wondering what the tabs are for – more tabs will be added by the booking add-on I’m creating. (More on that soon!).

More fullCalendar options

A massive thanks to Pascal Zerr who contributed this. The fullCalendar now accepts the following additional arguments (defaults are also given):

  • axisformat (default: ‘ga’ ) – time format displayed on the vertical axis of the agenda views
  • weekends (default: true’) – whether to include weekends in the calendar
  • mintime (default: 0′) – Determines the first hour/time that will be displayed, even when the scrollbars have been scrolled all the way up.
  • maxtime (default: 24′) – Determines the last hour/time that will be displayed, even when the scrollbars have been scrolled all the way down.
  • alldayslot (default: true’) – Determines if the “all-day” slot is displayed at the top of the calendar
  • alldaytext (default: __(‘All Day’,’eventorganiser’)) – Sets the text for the all day alot
  • columnformatmonth(default: ‘D’) – Determines format of the date in the column headers in month view
  • columnformatweek(default: ‘D n/j’) – Determines format of the date in the column headers in month view
  • columnformatday (default: ‘l n/j’) – Determines format of the date in the column headers in day view

Note that all date-time formats are in php format.

More tags for shortcode & event list widget ‘template’

You can now use %event_content% and %event_title_attr% and %event_duration% were added in previous versions. %event_excerpt% now takes an optional %event_excerpt{30}% (the excerpt length in words). Also%event_thumbnail%now accepts an additional second (optional) parameter: attributes (a string passed to get_post_thumbnail();

Improved Performance

1.6 removes some ‘dead wood’ from the database. Namely columns that have long been deprecated. The date columns are now indexed, which should improve performance of date based queries. Finally there’s been a bit of code refractoring to improve readability and performance. Javascript files have been ‘reshuffled’ and some code duplication removed.

As mentioned there have also been numerous bug fixes – including some styling issues on the front-end calendar and some ICS import issues. Even if you don’t need the above features, I recommend upgrading.

Event Organiser 1.5 – What’s New

1.5 adds some great, and much asked for, features – as well as fixing reported bugs. It’s currently in beta testing. Any volunteers for beta testing are very much welcome!. Please get in contact with me if you are interested.

What’s better:

Performance

1.5 sees a massive performance boost for the plug-in. This is a result of some reshuffling of how data is stored – but also due to caching calendars & widgets.

UI

The plug-in now uses the jQuery UI theme being thrashed out here. While not yet ‘official’ this represents the closest thing to the WordPress UI – which means the plug-in looks much more like the Admin UI (though personally, I didn’t think the original theme was not far off…). An added bonus is that the jQuery UI theme used depends on the user’s theme settings (in case you use the ‘classic’ blue theme).

Public fullCalendar

The fullCalendar now looks a bit more like the admin calendar (which is much better in my opinion). I’m working on improving the category/venue filters for this calendar too.

Code base

Functions concerning updating, creating and deleting events has seen a major overhaul and will be accompanied by full documentation that allows third-party developers to extend the plug-in (for instance creating events on the ‘front end’). These will be added to the function reference.

Other functions have been added, and some deprecated (though not removed) – these deprecated functions are giving way to ‘better’ functions. For example the function eo_get_the_occurrences_of() replaces eo_get_the_occurrences() and returns an array of occurrences – each occurrence being an array of start/end DateTimes. eo_get_the_occurrences() just returned an array of start DateTimes.

Event Agenda

You can now group events by day or by month. You can also change the ‘grouping’ date format (this appears for each month / day) and the ‘event’ date format (appears for each event). Obviously for grouping by day, the grouping format should probably contain the date, and for grouping by month, it should contain the month.

There also improvements in how the agenda deals with events with long titles.

Event List

CSS classes (e.g. eo-event-venue-{venue-slug}) have been added to event lists. This will allow for styling according to category, venue, and whether the event has ‘past’, is running, or is in the future.

Permalink options

There is now separate event and archive permalink structure

Venue admin page

More of a bug fix, but the venue admin page now allows you to hide/minimize and move the metaboxes.

What’s New

Add / Remove arbitrary dates

A much requested feature was the ability to add events that reoccur, but not according to any schedule. 1.5 allows you to add or remove dates from the schedule. A ‘custom’ schedule option has been added (in effect this is essential a single-occurrence where dates have been added. All schedules allow dates to be added/removed, but they retain their original label ‘daily’,’weekly’, etc.

Tooltips for the fullCalendar

Another much requested feature. This can switched on/off for each individual calendar using the tooltip=true / tooltip=false attribute.

Adding time format option for admin calendar

The admin calendar now show 12 hour / 24 hour time. See the screen option (top right) on the Calendar admin page.

Added tag:

You can now use %event_duration{'format here'}% in the event list template (widget/shortcode). To display the event’s duration. The format can be anything from here.

Added functions

Here’s just a few of the functions which are being added. These are the result of a massive code tidy up, and although what they did occurred before 1.5 – these functions are now available for use by third-party plug-ins without fear of them disappearing.

  • eo_get_venue_map() returns the HTML for a venue’s map. Can be used within the loop without a parameter or by passing a venue ID (as an integer) or slug (as a string).
  • eo_insert_event($post_data,$event_data) – creates a post of type ‘event’ with occurrences generated from and schedule data given by $event_data.
  • eo_update_event($post_id, $event_data,$post_data) – update an event with ID $post_id.
  • eo_get_event_schedule($post_id) – get an event’s schedule data (schedule occurrence, frequency, start & end dates, included & excluded dates, all day or not etc)

and more!

Added hooks!

In line with my goal of trying to make Event Organiser malleable and extendable I’ll be introducing some actions and filters. These will be documented in the developer section of the plug-in website.

Other improvements

  • Various bug fixes (including for this bug with W3 Total Cache – thanks to Ben Huson for resolving this)
  • Metaboxes on the venue admin page are now moveable, hide-able and minimize-able.
  • Added in a ‘duration’ template tag (for use in the event list shortcode / widget),
  • Separate permalink structure for event archive and single event pages
  • Improved Agenda ‘cut off’ for title (if space doesn’t allow)

As previously mentioned, I’m still after beta testers – so feel free to volunteer :).

PHP implementation of AES-CBC cipher

In a (WordPress) project I was working on I needed to store a password for use with a third-party (unfortunately there wasn’t a better way of doing this). Needless to say, storing someone’s password in the database was not making me feel all that comfortable – so I decided to encrypt it. I decided to go with a symmetric cipher. Great, PHP comes with these right? Well, yes – in PHP 5.3. Unfortunately, this might not be running on PHP 5.2 (a lot of hosts still don’t provide it!). Right then… implement my own… I decided to go with AES (it’s the approved standard) and CBC mode. Why CBC?

  • ECB shouldn’t really ever be used.
  • I didn’t need to stream the data – and there seemed no inherent advantage in my use case of OFB/CFB over CBC. So admittedly an element of ‘CBC being canonical’ crept in.

So based on this standard I produced the class below. I’ve posted it here for others to use – but also in the hope that any errors on my part might be brought to light. (I have, of course, conducted tests using example vectors).

Other notes

I have also added a MAC to the plaintext (after padding) – this helps authenticate the message (though this itself was not entirely needed in my use case) but also prevents a padding oracle attack. (I’m not sure how, in practise, this would be implemented – but its generally a good idea to always authenticate your messages – why not?). The class does not generate an IV for you.

The class

You can find the class here also.

/**
 * An implementation of the AES cipher (CBC mode).
 * For reference http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
 *
 * @author Stephen Harris (contact@stephenharris.info)
 * @license GPL
 *
 * Example usage:
 * <code>
 * <?php
 *    include('/aes.php');
 *    $aes = new SH_AES_Cipher();
 *
 *    $key = '0011223344556677'; 128/192/256 bits
 *    $plaintext = 'The quick brown fox jumped over the lazy cat';
 *    $iv =''; //Random (unpredictable!) IV 128 bits.
 *
 *    $ciphertext = $aes->encrypt($plaintext,$key,$iv);
 *    $original =$aes->decrypt($ciphertext,$key,$iv);
 * ?>
 * </code>
*/

class SH_AES_Cipher{

    /**
     * Generated key schedule
    */
    private $key_schedule;

    /**
     * The size of the blocks (in bytes), in which to break up the plain/cipher text
    */
    private $block_size;


    /**
     * Set's up encryption constants (number of rounds, the key length in 32-bit words, blocksize)
    */
    function setup($key){

        $length = strlen($key); //Number of bytes
        $key_bits = $length*8;

        $this->num_k = $length >> 2; //Number of 32-bit words
        $this->block_size =16; //Blocks are always 128-bites (16 bytes)
        $this->num_b = 4; //Block length (in bits) divided by 32

        switch( $this->num_k ){
            case 4:
                $this->rounds = 10;
                break;
            case 6:
                $this->rounds = 12;
                break;
            case 8:
                $this->rounds = 14;
                break;
        }
    }


    /**
     * The encryption method
     * @param $plaintext (string) - the text to be encrypted
     * @param $key (string) - the key to be used. This should be 128/192/256 bits in size. This should be kept secret.
     * @param $iv (string) - the initialisation vector. This should be 128/192/256 bits in size. This should be stored with the encrypted text for 
     * use in deciphering. This must be unpredictable!
    */
    function encrypt( $plaintext, $key, $iv,$mac_key){

        //Set up cipher
        $this->setup($key);

        //Pad plaintext so it fills block
        $plaintext = $this->pad($plaintext);

        //Create MAC
        $plaintext .=  hash_hmac('md5',$plaintext,$mac_key);
        $ciphertext = '';

        //Split the plaintext into blocks
        $blocks = str_split($plaintext, $this->block_size);

        $key = array_values(unpack('C*', $key)); //Array of bytes
        $this->key_expansion($key);

        $xor = array_values(unpack('C*',$iv));
        foreach ($blocks as $block){
            $block = array_values(unpack('C*', $block));

            for($i=0; $i< count($block); $i++){
                $block[$i] = $xor[$i]^$block[$i];
            }

            $block = $this->encryptBlock($block);
            foreach($block as $byte )
                $ciphertext .=pack('C', $byte);

            $xor =  array_values($block);
        }

        //Append IV to the beginning of the cipher text
        return $iv.$ciphertext;
    }


    /**
     * The decryption method
     * @param $ciphertext (string) - the text to be decrypted
     * @param $key (string) - the key to be used. This should be the same that was used for encryption.
     * @param $iv (string) - the initialisation vector. This should be the same that was used for encryption.
    */
    function decrypt( $ciphertext, $key, $mac_key){

        //Set up cipher
        $this->setup($key);

        $plaintext = '';
        $hmac = '';

        //Split the ciphertext into blocks
        $blocks = str_split($ciphertext, $this->block_size);

        //Extract IV from the beginning of the cipher text
        $iv = array_shift($blocks);

        $key = array_values(unpack('C*', $key)); //Array of bytes
        $this->key_expansion($key);

        $xor = array_values(unpack('C*',$iv));
        foreach ($blocks as $index => $block){
            $block = array_values(unpack('C*', $block)); //Array of bytes
            $dec_block = array_values($this->decryptBlock($block));

            for($i=0; $i<count($dec_block); $i++){
                $byte = $xor[$i]^$dec_block[$i];
                if( $index < count($blocks)-2 ){
                    $plaintext .= pack('C', $byte);
                }else{
                    $hmac .= pack('C', $byte);
                }
            }
            $xor = $block;
        }

        //Detect tampering
        if( $hmac != hash_hmac('md5',$plaintext,$mac_key) ){
            return false;
        }

        //Unpad
        $plaintext = $this->unpad($plaintext);

        return $plaintext;
    }

    /**
     * Encrypts a block of text.
     * @param $block (array) - array of bytes (block to be encrypted)
     * @return $block (array)  - array of bytes (block encrypted)
    */
    function encryptBlock( $block ){

        /* Initial state */
        $state = $this->initial_state($block);

        /* Initial add round key */
        $state = $this->add_round_key(0,$state);


        /* The rounds */
        for ($i=0; $i < $this->rounds; $i++){

            //Byte Sub (S-box) See Sec. 5.1.1
                for ($r = 0; $r < 4; $r++) {
                    for ($c= 0; $c < $this->num_b; $c++) {
                    $state[$r][$c] = $this->sub_byte($state[$r][$c]);
                }
            }


            // Shift Row - See Sec. 5.1.2
            $temp=array();
                for ($r = 0; $r < 4; $r++) {
                    for ($c= 0; $c < $this->num_b; $c++) {
                    $temp[$r][$c] = $state[$r][ ($c+$r)%$this->num_b ];
                }
            }
            $state = $temp;


            //Mix Column See Sec. 5.1.3
            if( $i != $this->rounds-1 ){
                for ($c= 0; $c < $this->num_b; $c++) {

                    $column =array();
                    for ($r = 0; $r < 4; $r++)
                        $column[$r] = $state[$r][$c];

                    $column = $this->mix_column($column);

                    for ($r = 0; $r < 4; $r++)
                        $state[$r][$c] = $column[$r];
                }
            }

            // Add Round Key
            $state = $this->add_round_key($i+1,$state);

        }

        /* Flatten state and return*/
        return $this->flatten_state($state);
    }

    /**
     * Decrypts a block of text.
     * @param $block (array) - array of bytes (block to be decrypted)
     * @return $block (array)  - array of bytes (block decrypted)
    */
    function decryptBlock( $block ){

        /* Initial state */
        $state = $this->initial_state($block);

        // Add Round Key
        $state = $this->add_round_key($this->rounds,$state);


        /* The rounds */
        for ($i=$this->rounds-1; $i >-1; $i--){

            // Inverse Shift Row
            $temp=array();
            for ($r = 0; $r < 4; $r++) {
                for ($c= 0; $c < $this->num_b; $c++) {
                    $temp[$r][$c] = $state[$r][ ($c+4-$r)%$this->num_b ];
                }
            }
            $state = $temp;


            //Inverse Byte Sub (S-box) See Sec. 5.1.1
            for ($r = 0; $r < 4; $r++) {
                for ($c= 0; $c < $this->num_b; $c++) {
                    $state[$r][$c] = $this->inverse_sub_byte($state[$r][$c]);
                }
            }

            // Add Round Key
            $state = $this->add_round_key($i,$state);


            //Inverse Mix Column See Sec. 5.1.3
            if( $i !=0 ){
                for ($c= 0; $c < $this->num_b; $c++) {

                    $column =array();
                    for ($r = 0; $r < 4; $r++) {
                        $column[$r] = $state[$r][$c];
                    }

                    $column = $this->inverse_mix_column($column);

                    for ($r = 0; $r < 4; $r++) {
                        $state[$r][$c] = $column[$r];
                    }
                }
            }

        }

        /* Flatten state and return*/
        return $this->flatten_state($state);    
    }


    /**
     * Used in encryptBlock and decryptBlock
     * Initialises the state array using the recieved block
     * @param (array) array of bytes for current block
     * @return (array) initialised state
    */
    function initial_state($bytes){ 
        $state=array();
        for( $r=0; $r<4; $r++ ){
            for( $c= 0; $c < count($bytes)/4; $c++){
                $state[$r][$c] = $bytes[$r+4*$c];
            }
        }
        return $state;
    }


    /**
     * Flattens the two dimensional state array into a one dimensional
    * @param (array) current state
    * @return (array) flattened state
    */
    function flatten_state($state){
        $flattened = array();
        for( $r=0; $r<4; $r++ ){
            for( $c= 0; $c < $this->num_b; $c++){
                $flattened[1+$r+4*$c] = $state[$c][$r];
            }
        }
        return $flattened;
    }


    /**
     * Adds the key for a given round to the current state
    * @param (array) current state
    * @return (array) new state
    */
    function add_round_key($round, $state){
        for( $r=0; $r<4; $r++ ){
            for( $c= 0; $c < $this->num_b; $c++){
                $state[$r][$c] = $state[$r][$c] ^ $this->key_schedule[$c+4*($round)][$r];
            }
        }
        return $state;
    }


    /**
     * Pads a text so that its length is a multiple of the block size.
     * All content is padded.
     * See http://tools.ietf.org/html/rfc5652#section-6.3
    * @param (string) text to be padded
    * @return (string) padded text
    */
    function pad( $text='' ){
        $length = strlen($text);
        $padding =  $this->block_size - ($length  % $this->block_size );
        $text = str_pad($text,  $length + $padding, chr($padding) );
        return $text;
    }

    /**
     * Unpads a text to restore it to its original length
     * It only checks the last character. Padding is set to 16 if there is an error.
    * @param (string) text to be unpadded
    * @return (string) unpadded text
    */
    function unpad($text=''){
        $padded = (int) ord($text[strlen($text)-1]);
        $padded = ($padded > 16 ? 16 : $padded);
        return substr($text,0,strlen($text)-$padded);

    }

    /**
     * Performs the mix_columns function.
    * @param (array) a column (array of 4 bytes)
    * @return (array) the column after the mix column matrix has been applied in GF.
    */
    function mix_column($col) {
        $a = $col;
        $b = array();
        $h='';

         /* The array 'a' is simply a copy of the input array 'r'
          * The array 'b' is each element of the array 'a' multiplied by 2 in Rijndael's Galois field
          * a[n] ^ b[n] is element n multiplied by 3 in Rijndael's Galois field */ 

         for($r=0;$r<4;$r++) {

            $h = ($col[$r] >> 7); 
            $b[$r] = ($col[$r] << 1); 

            if($h)
                      $b[$r]  ^= 0x11B;
         }

            $col[0] = $b[0] ^ $a[3] ^ $a[2] ^ $b[1] ^ $a[1]; /* 2 * a0 + a3 + a2 + 3 * a1 */
         $col[1] = $b[1] ^ $a[0] ^ $a[3] ^ $b[2] ^ $a[2]; /* 2 * a1 + a0 + a3 + 3 * a2 */
         $col[2] = $b[2] ^ $a[1] ^ $a[0] ^ $b[3] ^ $a[3]; /* 2 * a2 + a1 + a0 + 3 * a3 */
         $col[3] = $b[3] ^ $a[2] ^ $a[1] ^ $b[0] ^ $a[0]; /* 2 * a3 + a2 + a1 + 3 * a0 */

        return $col;
    }


    /**
     * Performs the inverse mix_columns function.
    * @param (array) a column (array of 4 bytes)
    * @return (array) the column after the inverse mix column matrix has been applied in GF.
    */
    function inverse_mix_column($col){

        $gfx14 =array(0x00,0x0e,0x1c,0x12,0x38,0x36,0x24,0x2a,0x70,0x7e,0x6c,0x62,0x48,0x46,0x54,0x5a,
                        0xe0,0xee,0xfc,0xf2,0xd8,0xd6,0xc4,0xca,0x90,0x9e,0x8c,0x82,0xa8,0xa6,0xb4,0xba,
                        0xdb,0xd5,0xc7,0xc9,0xe3,0xed,0xff,0xf1,0xab,0xa5,0xb7,0xb9,0x93,0x9d,0x8f,0x81,
                        0x3b,0x35,0x27,0x29,0x03,0x0d,0x1f,0x11,0x4b,0x45,0x57,0x59,0x73,0x7d,0x6f,0x61,
                        0xad,0xa3,0xb1,0xbf,0x95,0x9b,0x89,0x87,0xdd,0xd3,0xc1,0xcf,0xe5,0xeb,0xf9,0xf7,
                        0x4d,0x43,0x51,0x5f,0x75,0x7b,0x69,0x67,0x3d,0x33,0x21,0x2f,0x05,0x0b,0x19,0x17,
                        0x76,0x78,0x6a,0x64,0x4e,0x40,0x52,0x5c,0x06,0x08,0x1a,0x14,0x3e,0x30,0x22,0x2c,
                        0x96,0x98,0x8a,0x84,0xae,0xa0,0xb2,0xbc,0xe6,0xe8,0xfa,0xf4,0xde,0xd0,0xc2,0xcc,
                        0x41,0x4f,0x5d,0x53,0x79,0x77,0x65,0x6b,0x31,0x3f,0x2d,0x23,0x09,0x07,0x15,0x1b,
                        0xa1,0xaf,0xbd,0xb3,0x99,0x97,0x85,0x8b,0xd1,0xdf,0xcd,0xc3,0xe9,0xe7,0xf5,0xfb,
                        0x9a,0x94,0x86,0x88,0xa2,0xac,0xbe,0xb0,0xea,0xe4,0xf6,0xf8,0xd2,0xdc,0xce,0xc0,
                        0x7a,0x74,0x66,0x68,0x42,0x4c,0x5e,0x50,0x0a,0x04,0x16,0x18,0x32,0x3c,0x2e,0x20,
                        0xec,0xe2,0xf0,0xfe,0xd4,0xda,0xc8,0xc6,0x9c,0x92,0x80,0x8e,0xa4,0xaa,0xb8,0xb6,
                        0x0c,0x02,0x10,0x1e,0x34,0x3a,0x28,0x26,0x7c,0x72,0x60,0x6e,0x44,0x4a,0x58,0x56,
                        0x37,0x39,0x2b,0x25,0x0f,0x01,0x13,0x1d,0x47,0x49,0x5b,0x55,0x7f,0x71,0x63,0x6d,
                        0xd7,0xd9,0xcb,0xc5,0xef,0xe1,0xf3,0xfd,0xa7,0xa9,0xbb,0xb5,0x9f,0x91,0x83,0x8d);

        $gfx13 =array(0x00,0x0d,0x1a,0x17,0x34,0x39,0x2e,0x23,0x68,0x65,0x72,0x7f,0x5c,0x51,0x46,0x4b,
                        0xd0,0xdd,0xca,0xc7,0xe4,0xe9,0xfe,0xf3,0xb8,0xb5,0xa2,0xaf,0x8c,0x81,0x96,0x9b,
                        0xbb,0xb6,0xa1,0xac,0x8f,0x82,0x95,0x98,0xd3,0xde,0xc9,0xc4,0xe7,0xea,0xfd,0xf0,
                        0x6b,0x66,0x71,0x7c,0x5f,0x52,0x45,0x48,0x03,0x0e,0x19,0x14,0x37,0x3a,0x2d,0x20,
                        0x6d,0x60,0x77,0x7a,0x59,0x54,0x43,0x4e,0x05,0x08,0x1f,0x12,0x31,0x3c,0x2b,0x26,
                        0xbd,0xb0,0xa7,0xaa,0x89,0x84,0x93,0x9e,0xd5,0xd8,0xcf,0xc2,0xe1,0xec,0xfb,0xf6,
                        0xd6,0xdb,0xcc,0xc1,0xe2,0xef,0xf8,0xf5,0xbe,0xb3,0xa4,0xa9,0x8a,0x87,0x90,0x9d,
                        0x06,0x0b,0x1c,0x11,0x32,0x3f,0x28,0x25,0x6e,0x63,0x74,0x79,0x5a,0x57,0x40,0x4d,
                        0xda,0xd7,0xc0,0xcd,0xee,0xe3,0xf4,0xf9,0xb2,0xbf,0xa8,0xa5,0x86,0x8b,0x9c,0x91,
                        0x0a,0x07,0x10,0x1d,0x3e,0x33,0x24,0x29,0x62,0x6f,0x78,0x75,0x56,0x5b,0x4c,0x41,
                        0x61,0x6c,0x7b,0x76,0x55,0x58,0x4f,0x42,0x09,0x04,0x13,0x1e,0x3d,0x30,0x27,0x2a,
                        0xb1,0xbc,0xab,0xa6,0x85,0x88,0x9f,0x92,0xd9,0xd4,0xc3,0xce,0xed,0xe0,0xf7,0xfa,
                        0xb7,0xba,0xad,0xa0,0x83,0x8e,0x99,0x94,0xdf,0xd2,0xc5,0xc8,0xeb,0xe6,0xf1,0xfc,
                        0x67,0x6a,0x7d,0x70,0x53,0x5e,0x49,0x44,0x0f,0x02,0x15,0x18,0x3b,0x36,0x21,0x2c,
                        0x0c,0x01,0x16,0x1b,0x38,0x35,0x22,0x2f,0x64,0x69,0x7e,0x73,0x50,0x5d,0x4a,0x47,
                        0xdc,0xd1,0xc6,0xcb,0xe8,0xe5,0xf2,0xff,0xb4,0xb9,0xae,0xa3,0x80,0x8d,0x9a,0x97);

        $gfx11=array(0x00,0x0b,0x16,0x1d,0x2c,0x27,0x3a,0x31,0x58,0x53,0x4e,0x45,0x74,0x7f,0x62,0x69,
                        0xb0,0xbb,0xa6,0xad,0x9c,0x97,0x8a,0x81,0xe8,0xe3,0xfe,0xf5,0xc4,0xcf,0xd2,0xd9,
                        0x7b,0x70,0x6d,0x66,0x57,0x5c,0x41,0x4a,0x23,0x28,0x35,0x3e,0x0f,0x04,0x19,0x12,
                        0xcb,0xc0,0xdd,0xd6,0xe7,0xec,0xf1,0xfa,0x93,0x98,0x85,0x8e,0xbf,0xb4,0xa9,0xa2,
                        0xf6,0xfd,0xe0,0xeb,0xda,0xd1,0xcc,0xc7,0xae,0xa5,0xb8,0xb3,0x82,0x89,0x94,0x9f,
                        0x46,0x4d,0x50,0x5b,0x6a,0x61,0x7c,0x77,0x1e,0x15,0x08,0x03,0x32,0x39,0x24,0x2f,
                        0x8d,0x86,0x9b,0x90,0xa1,0xaa,0xb7,0xbc,0xd5,0xde,0xc3,0xc8,0xf9,0xf2,0xef,0xe4,
                        0x3d,0x36,0x2b,0x20,0x11,0x1a,0x07,0x0c,0x65,0x6e,0x73,0x78,0x49,0x42,0x5f,0x54,
                        0xf7,0xfc,0xe1,0xea,0xdb,0xd0,0xcd,0xc6,0xaf,0xa4,0xb9,0xb2,0x83,0x88,0x95,0x9e,
                        0x47,0x4c,0x51,0x5a,0x6b,0x60,0x7d,0x76,0x1f,0x14,0x09,0x02,0x33,0x38,0x25,0x2e,
                        0x8c,0x87,0x9a,0x91,0xa0,0xab,0xb6,0xbd,0xd4,0xdf,0xc2,0xc9,0xf8,0xf3,0xee,0xe5,
                        0x3c,0x37,0x2a,0x21,0x10,0x1b,0x06,0x0d,0x64,0x6f,0x72,0x79,0x48,0x43,0x5e,0x55,
                        0x01,0x0a,0x17,0x1c,0x2d,0x26,0x3b,0x30,0x59,0x52,0x4f,0x44,0x75,0x7e,0x63,0x68,
                        0xb1,0xba,0xa7,0xac,0x9d,0x96,0x8b,0x80,0xe9,0xe2,0xff,0xf4,0xc5,0xce,0xd3,0xd8,
                        0x7a,0x71,0x6c,0x67,0x56,0x5d,0x40,0x4b,0x22,0x29,0x34,0x3f,0x0e,0x05,0x18,0x13,
                        0xca,0xc1,0xdc,0xd7,0xe6,0xed,0xf0,0xfb,0x92,0x99,0x84,0x8f,0xbe,0xb5,0xa8,0xa3);

        $gfx9 =array(0x00,0x09,0x12,0x1b,0x24,0x2d,0x36,0x3f,0x48,0x41,0x5a,0x53,0x6c,0x65,0x7e,0x77,
                        0x90,0x99,0x82,0x8b,0xb4,0xbd,0xa6,0xaf,0xd8,0xd1,0xca,0xc3,0xfc,0xf5,0xee,0xe7,
                        0x3b,0x32,0x29,0x20,0x1f,0x16,0x0d,0x04,0x73,0x7a,0x61,0x68,0x57,0x5e,0x45,0x4c,
                        0xab,0xa2,0xb9,0xb0,0x8f,0x86,0x9d,0x94,0xe3,0xea,0xf1,0xf8,0xc7,0xce,0xd5,0xdc,
                        0x76,0x7f,0x64,0x6d,0x52,0x5b,0x40,0x49,0x3e,0x37,0x2c,0x25,0x1a,0x13,0x08,0x01,
                        0xe6,0xef,0xf4,0xfd,0xc2,0xcb,0xd0,0xd9,0xae,0xa7,0xbc,0xb5,0x8a,0x83,0x98,0x91,
                        0x4d,0x44,0x5f,0x56,0x69,0x60,0x7b,0x72,0x05,0x0c,0x17,0x1e,0x21,0x28,0x33,0x3a,
                        0xdd,0xd4,0xcf,0xc6,0xf9,0xf0,0xeb,0xe2,0x95,0x9c,0x87,0x8e,0xb1,0xb8,0xa3,0xaa,
                        0xec,0xe5,0xfe,0xf7,0xc8,0xc1,0xda,0xd3,0xa4,0xad,0xb6,0xbf,0x80,0x89,0x92,0x9b,
                        0x7c,0x75,0x6e,0x67,0x58,0x51,0x4a,0x43,0x34,0x3d,0x26,0x2f,0x10,0x19,0x02,0x0b,
                        0xd7,0xde,0xc5,0xcc,0xf3,0xfa,0xe1,0xe8,0x9f,0x96,0x8d,0x84,0xbb,0xb2,0xa9,0xa0,
                        0x47,0x4e,0x55,0x5c,0x63,0x6a,0x71,0x78,0x0f,0x06,0x1d,0x14,0x2b,0x22,0x39,0x30,
                        0x9a,0x93,0x88,0x81,0xbe,0xb7,0xac,0xa5,0xd2,0xdb,0xc0,0xc9,0xf6,0xff,0xe4,0xed,
                        0x0a,0x03,0x18,0x11,0x2e,0x27,0x3c,0x35,0x42,0x4b,0x50,0x59,0x66,0x6f,0x74,0x7d,
                        0xa1,0xa8,0xb3,0xba,0x85,0x8c,0x97,0x9e,0xe9,0xe0,0xfb,0xf2,0xcd,0xc4,0xdf,0xd6,
                        0x31,0x38,0x23,0x2a,0x15,0x1c,0x07,0x0e,0x79,0x70,0x6b,0x62,0x5d,0x54,0x4f,0x46);

        $_col=array();
        $_col[0] = $gfx14[$col[0]] ^ $gfx11[$col[1]] ^ $gfx13[$col[2]]^ $gfx9[$col[3]];
        $_col[1] = $gfx9[$col[0]] ^ $gfx14[$col[1]] ^ $gfx11[$col[2]]^ $gfx13[$col[3]];
        $_col[2] = $gfx13[$col[0]] ^ $gfx9[$col[1]] ^ $gfx14[$col[2]]^ $gfx11[$col[3]];
        $_col[3] = $gfx11[$col[0]] ^ $gfx13[$col[1]] ^ $gfx9[$col[2]]^ $gfx14[$col[3]];
        return $_col; 
    }

    /**
     * Generates the key schedule from the given key.
    * @param (string) key
    */
    function key_expansion( $key =''){

        $this->key_schedule=array();

        for($i=0;  $i < $this->num_k; $i++){
            $this->key_schedule[$i] = array($key[4*$i],$key[4*$i+1],$key[4*$i+2],$key[4*$i+3]);
        }

        $i = $this->num_k;
        while ($i < $this->num_b * ($this->rounds+1) ){

            $word = $this->key_schedule[$i-1];

            if ($i % $this->num_k == 0){
                $word = $this->sub_word($this->rot_word($word));
                $rcon = $this->rcon($i/$this->num_k);

                for($j=0; $j<4; $j++){
                    $word[$j] = $word[$j]^$rcon[$j];
                }

            }elseif ($this->num_k > 6 && $i % $this->num_k == 4){
                $word = $this->sub_word($word);
            }

            for($j=0; $j<4; $j++){
                $word[$j] = $word[$j]^$this->key_schedule[$i-$this->num_k][$j];
            }

            $this->key_schedule[$i] = $word;
            $i++;
        }
    }

    /**
     * 'Rotates' a word (array of 4 bytes) so that the first byte is shifted to the end.
     * @param (array) array of 4 bytes
     * @return (array) array of 4 bytes
    */
    function rot_word($word){
        $first = array_shift($word);
        $word[] = $first;
        return $word;
    }

    /**
     * The input word (array of 4 bytes) is replaced by a another word, where each byte has been replaced using the substitution box
     * @param (array) array of 4 bytes
     * @return (array) array of 4 bytes
    */
    function sub_word($word){
        for( $i=0; $i<4; $i++ ){
            $word[$i] = $this->sub_byte($word[$i]);
        }
        return $word;
    }


    function rcon($i){
        $rcon = array(
            0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 
            0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 
            0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 
            0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 
            0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 
            0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 
            0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 
            0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 
            0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 
            0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 
            0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 
            0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 
            0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 
            0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 
            0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 
            0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d
            );
        return array($rcon[$i],0,0,0);
    }
    /**
     * Acts as look-up array of the AES s-box
     * The input byte (as a hex) is replaced with a SubByte using an 8-bit substitution box
     * @param (byte) input byte
     * @return (byte) substitued byte
    */
    function sub_byte( $hex ){
        $sbox = array(
                  0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
                  0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
                  0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
                  0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
                  0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
                  0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
                  0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
                  0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
                  0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
                  0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
                  0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
                  0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
                  0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
                  0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
                  0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
                  0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
        );
        return $sbox[$hex];
    }

    /**
     * Acts as look-up array of the inverse of the AES s-box
     * The input byte (as a hex) is replaced with a SubByte using an 8-bit (inverse) substitution box
     * @param (byte) input byte
     * @return (byte) substitued byte
    */
    function inverse_sub_byte( $hex ){
        $invsbox = array(
            0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
            0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
            0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
            0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
            0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
            0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
            0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
            0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
            0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
            0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
            0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
            0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
            0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
            0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
            0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
            0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
            );
        return $invsbox[$hex];
    }


}

Date Intervals in PHP 5.2

PHP provides a very useful DateTime class, and the related DateTimeZone, and DateInterval classes. (Most of) These have proven very useful when developing my plug-in Event Organiser, in particular making date generation from complex patterns fairly painless. However there is one problem = some of the more advanced methods of DateTime, and the entire DateInterval class are only supported in PHP 5.3. Quite a lot of websites are still hosted on the old PHP 5.2.

This means I’ve had to produce work-arounds in the cases where Event Organiser is run on PHP 5.2.

DateInterval

With events you might also want to describe how long the event is. In PHP 5.3 this is easy:

$date1 = new DateTime('2012-10-28 00:00:00',new DateTimeZone('Europe/London'));
$date2 = new DateTime('2012-10-28 03:00:00',new DateTimeZone('Europe/London'));
$interval = date_diff($date1, $date2);
$format = '%y years, %m months, %d days, %h hours, %i minutes, %s seconds, %a total days %R';
echo $interval->format($format);

You use the date_diff function which returns a DateInterval object. This can be formatted into string using any of the available place-holders.

Work around for PHP 5.2

It would be great to replicate this for PHP 5.2 – and it’s actually quite easy. We’ll produce a function sh_date_interval($date1,$date2,$format) which should return (almost!) the same as $interval->format($format) above.

To this, we’ll take the earlier date and increment by one year until it’s after our later date. We’ll then reverse the last increment (so the modified date is now less than a year before the later date) and record how many increments. This tells us how many years apart the dates are. We do this for months, days and hours.

For minutes and seconds do something different. There are a fixed number of seconds in a minute, and fixed number of minutes in an hour. Consequently rather than a while loop of an extra ~120 loops we just use simple calculations.

Note: Incrementing 2012-01-31 (1st January) by one month gives 2012-03-02 (2nd March). So these dates are a month apart. Incrementing 2012-02-29 (29th February) by a month gives 2012-03-29 (29th March) – so the 29th February and 31st March are 1 month and 2 days apart. This is expected behaviour and mimics that of DateInterval. The function handles leap years in the same way as DateInterval.

Note 2: The function does not handle daylight savings in in the same way as DateInterval. If at 01:00 you are to move the clocks forward to 02:00, DateInterval says that there are three hours between 00:00 and 03:00. The below function says there are two (which I believe to be correct – at least in human terms).

Note 3: I’m using this in a WordPress context – so I’m using the function zeroise which is not a native PHP function.

function sh_date_interval($_date1,$_date2, $format){

    //Make sure $date1 is ealier
    $date1 = ($_date1 <= $_date2 ? $_date1 : $_date2);
    $date2 = ($_date1 <= $_date2 ? $_date2 : $_date1);

    //Calculate R values
    $R = ($_date1 <= $_date2 ? '+' : '-');
    $r = ($_date1 <= $_date2 ? '' : '-');

    //Calculate total days
    $total_days = round(abs($date1->format('U') - $date2->format('U'))/86400);

    //A leap year work around - consistent with DateInterval
    $leap_year = ( $date1->format('m-d') == '02-29' ? true : false);
    if( $leap_year ){
        $date1->modify('-1 day');
    }

    $periods = array( 'years'=>-1,'months'=>-1,'days'=>-1,'hours'=>-1);

    foreach ($periods as $period => &$i ){

        if($period == 'days' && $leap_year )
            $date1->modify('+1 day');

        while( $date1 <= $date2 ){
            $date1->modify('+1 '.$period);
            $i++;
        }

        //Reset date and record increments
        $date1->modify('-1 '.$period);
    }
    extract($periods);

    //Minutes, seconds
    $seconds = round(abs($date1->format('U') - $date2->format('U')));
    $minutes = floor($seconds/60);
    $seconds = $seconds - $minutes*60;

    $replace = array(
        '/%y/' => $years,
        '/%Y/' => zeroise($years,2),
        '/%m/' => $months,
        '/%M/' => zeroise($months,2),
        '/%d/' => $days,
        '/%D/' => zeroise($days,2),
        '/%a/' => zeroise($total_days,2),
        '/%h/' => $hours,
        '/%H/' => zeroise($hours,2),
        '/%i/' => $minutes,
        '/%I/' => zeroise($minutes,2),
        '/%s/' => $seconds,
        '/%S/' => zeroise($seconds,2),
        '/%r/' => $r,
        '/%R/' => $R
    );

    return preg_replace(array_keys($replace), array_values($replace), $format);
}

Testing it out

The following dates are chosen to provide an example of leap year and daylight savings. Clocks are due to go forward an hour on Sunday, 31 March 2013, 01:00.

$date1 = new DateTime('2012-02-29 00:00:00',new DateTimeZone('Europe/London'));
$date2 = new DateTime('2013-03-31 03:00:00',new DateTimeZone('Europe/London'));

$interval = date_diff($date1, $date2);
$format = '%y year, %m month, %d day, %h hours, %i minutes, %s seconds, %a total days';
echo $interval->format($format); 
echo sh_date_interval($date1,$date2,$format); 

    /*
    * Prints
    * 1 year, 1 month, 2 day, 3 hours, 0 minutes, 0 seconds, 396 total days
    * 1 year, 1 month, 2 day, 2 hours, 0 minutes, 0 seconds, 396 total days
    */

How to Get the Current URL in WordPress

Note: The original post wouldn’t work for WordPress on sub-domains, so I’ve updated it to include a solution that will

The other day I needed to get the current url the user was on – and I was convinced that there would be a better way than handling $_SERVER directly.

I found this solution by Konstantin Kovshenin, but which unfortunately gave me

http://example.com/blog/category/cata1?category_name=cata1 

when on

http://example.com/blog/category/cata1

Now add_query_arg, when passed no url uses the current url but, unfortunately, when on www.example.com/page/subpage/ it gives you: /page/subpage/. To get around that, you can use home_url.

To get the ‘current url’ in WordPress:

 $current_url = home_url(add_query_arg(array()));

Unfortunately if you WordPress install lives on a subdomain this won’t work, since:

  • home_url() gives you something like www.example.com/blog
  • add_query_arg(array()) gives you something like blog/page/subpage

So you get a repeat of blog. A more general solution would be

 $current_url = home_url(add_query_arg(array(),$wp->request));