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
    */