Grunt & WordPress development IV: Another task for internationalisation

grunt

This is part 4 in a series looking at using Grunt in WordPress plug-in/theme development.

  1. Grunt & WordPress development
  2. Grunt & WordPress development II: Installing Grunt
  3. Grunt & WordPress development III: Tasks for internationalisation
  4. Grunt & WordPress development IV: Another task for internationalisation

WordPress has recently (since 3.5) seen a shift towards a more JavaScript codebase. This shift is still minor (it currently accounts for less than 15%, according to its GitHub repository, in 3.9). But the introduction of Backbone.js and the re-factoring of particular the editor in the WordPress admin (media manager, shortcodes “objects” etc.) are testament to Matt Mullenweg’s comment:

I forgot to mention our biggest architectural change, which is already ongoing: an ever-increasing percentage of our codebase is shifting to Javascript instead of PHP. At some point I expect it to be the vast majority.

Matt Mullenweg, https://news.ycombinator.com/item?id=4744542

However, a JavaScript codebase presents difficulties for WordPress in terms of internationalisation. I ran into this while working on Event Organiser Pro’s 1.7 release. This new release saw the booking form customiser rewritten to use Backbone. With so much JavaScript replacing PHP, there became a massive need to allow strings to be translatable (in a sane way).

Received wisdom is that you should use wp_localize_script() to allow strings in javascript files to be translated. Pippin covers the method excellently here but essentially when you enqueue your script you use wp_localize_script() to make a variable available which contains all you translated strings.

function my_load_scripts() {
    wp_enqueue_script( 'my-script', plugin_dir_url( __FILE__ ) . 'my-script.js');
    wp_localize_script('my-script', 'mynamespace', array(
         'helloworld' => __('Hello World', 'mydomain' )
    ));
}
add_action('wp_enqueue_scripts', 'my_load_scripts');

Then in your JavaScript file, instead of the string “Hello World” you would use mynamespace.helloworld. Although this method is common, there are a couple of things wrong with it when you pit it against some sort of gettext function:

It makes code harder to read. For me when reading code it’s much easier to see the actual text rather than a variable. (More so when reading other people’s code as this helps you link the sourcecode with what you actually see)

<script>
  alert( mynamespace.welcome_msg ); //ok
  alert( mynamespace.gettext( "Welcome to..." ) ); //better
</script>

At the very best its slightly more cryptic and uglier.

It makes it harder to maintain code. – When editing a JavaScript file, a gettext function allows me to edit the string there and then. If I use wp_localize_script(), I need to track down the .php file responsible for that and change it there – and then not forget that that string might have been used elsewhere.

It makes the translator’s job harder. – Hands up anyone who is very poor at providing translators with comments or providing a context when appropriate. Me at least. Regardless, .po files provide a line number so that, if necessary, translators can look up that line to get some sort of context for the string they are translating. It’s not very helpful when that line number points them to a large array in some obscure php file, rather than where the text is being used. Nor is it immediately obvious which JavaScript file(s) are using the string.

You may think that my reasons here are weak and pinnikity… and you might right… But I prefer using gettext-esque function for translating strings.

The problem(s)… (and how Grunt helps solve them)

There a couple of problems with using trying to use a gettext function in a JavaScript file, but they are all easily solved:

  1. There is no native gettext function
  2. How do you get the translations from .po to your JavaScript file
  3. How to get translatable strings from your JavaScript file to your .pot

There is no native gettext function in JavaScript

A very simple solution is to roll your own. Below are four functions which handle translatable strings, plurals and contexts. They all expect the translated strings to be found in mynamespace.locale.

mynamespace.gettext = function( msgid ){
    if( this.locale[msgid] !== undefined ){
        return this.locale[msgid];
    }
    return msgid;
};

mynamespace.ngettext = function( msgid1, msgid2, n ){
    var key = ( n > 1 ? msgid1 + '_plural' : msgid1 );
    if( this.locale[key] !== undefined ){
        return this.locale[key];
    }
    return ( n > 1 ? msgid2 : msgid1 );
};

mynamespace.pgettext = function( ctxt, msgid ){
    if( this.locale[msgid+'_'+ctxt] !== undefined ){
        return this.locale[msgid+'_'+ctxt];
    }
    return msgid;
};

mynamespace.npgettext = function( ctxt, msgid1, msgid2, n ){
    var key = ( n > 1 ? msgid1 + '_' + ctxt + '_plural' : + '_' + ctxt + '_' + msgid1 );
    if( this.locale[key] !== undefined ){
        return this.locale[key];
    }
    return ( n > 1 ? msgid2 : msgid1 );
};

You may have noticed that only one plural form is supported (so a string is either plural or singular), but some languages use more (and some less). There are ways around this, but the limitation is also a result of the Grunt task that’ll we’ll use later. Plural strings and strings with a context expect _plural and _{context} modifiers – I personally think this is less than ideal, but again is forced upon me by my choice of Grunt task. (This is just a start, and I’d like to see these limitations lifted).

Getting translations from .po to your JavaScript file

This is a two-step process:

  1. Use a Grunt task to generate a .json file for each .mo file
  2. Depending on the user’s choice of locale, load that .json file and use wp_localize_script() to make it available in you JavaScipt file.

I went with grunt-i18next-conv to generate the .json files. I found that converting .po to .json included untranslated strings, so I recommend you opt for converting .mo files to .json. (If you need a Grunt task for generating your .mo task, I recommend po2mo task I covered in my last article). It’s this task, by way of the format of the .json file it produces, that imposes some of the limitations already mentioned.

Next you, when enqueuing your JavaScript file, you use wp_localize_script() to ‘attach’ the relevant .json file to it. In the following I expect that the .json files are of the form /languages/mytextdomain-{locale}.json

 $locale = array();
 $file = plugin_dir_path( __FILE__ ) . 'languages/mytextdomain-'.get_locale().'.json';
 if( file_exists( file ) ){
      $locale = json_decode( file_get_contents( $file ), true );    
 }

 wp_localize_script('my-script', 'mynamespace', array(
      'locale' => $locale
 ));

Getting the translatable strings from your JavaScript file to .pot

If you’re using grunt-pot this is easy. Simply include the functions above in the ‘keywords’ option:

  keywords: [ 
        ...
        'namespace.gettext:1',
        'namespace.ngettext:1,2',
        'namespace.pgettext:1c,2',
        'namespace.npgettext:1c,2,3',
        ...
       ]

and ensure the files to search include your JavaScript file.

Limitations

As discussed above there are currently two limitations:

  1. Poor support for plurals other than ‘single form plurals’
  2. Awkward ‘.json’ structure (not a massive issue…)

For the time being, however, and for use in Event Organiser’s booking form customiser, this method was ideal.

Grunt & WordPress development III: Tasks for internationalisation

grunt

This is part 3 in a series looking at using Grunt in WordPress plug-in/theme development.

  1. Grunt & WordPress development
  2. Grunt & WordPress development II: Installing Grunt
  3. Grunt & WordPress development III: Tasks for internationalisation
  4. Grunt & WordPress development IV: Another task for internationalisation

Internationalisation Tasks

One aspect of WordPress plug-in development that involves a lot of mundane work is that of internalisation: ensuring WordPress’ localisation functions are used correctly, generating a .pot file, compiling submitted .po files to .mo files. The latter two you can do with Poedit – but this still involves manually opening the .po/.pot file. These tasks can be completely automated so let’s do that:

po2mo – Compiling to .po files to .mo

The po2mo plug-in automatically compiles given .po files and produces a .mo file of the same name.

To install:

npm install grunt-po2mo --save-dev

<em>Please not an earlier version of this article executed the above as a super user (`sudo npm`). As pointed out by Lacy in the comments, this necessary and can cause permission issues with the npm cache.</em>

The following set up looks in the languages directory for any .po files and compiles them, creating the corresponding .mo in the same directory:

po2mo: {
    files: {
        src: 'languages/*.po',
        expand: true,
    },
},

Finally load the task by adding grunt.loadNpmTasks('grunt-po2mo'); at the bottom of your Gruntfile.js, just after grunt.loadTasks('tasks');. Then whenever you add or change a .po file:

grunt po2mo

(You can see a live example of this task, and the others listed below, here.

pot – Create a .pot template file

For users to be able to translate your plug-in you’ll need to create a .po template file ( a .pot file). The pot plug-in does exactly that.

You just need to provide it with:

  • The files to search in,
  • The keywords to search for (and indicate which arguments are translatable strings, and which are context specifiers)
  • A text domain (used only for naming the the .pot file)
  • The directory where you wish to output the .pot file.

To install:

npm install grunt-pot --save-dev

Then

pot: {
      options:{
          text_domain: 'my-plugin', //Your text domain. Produces my-text-domain.pot
          dest: 'languages/', //directory to place the pot file
          keywords: [ //WordPress localisation functions
            '__:1',
            '_e:1',
            '_x:1,2c',
            'esc_html__:1',
            'esc_html_e:1',
            'esc_html_x:1,2c',
            'esc_attr__:1', 
            'esc_attr_e:1', 
            'esc_attr_x:1,2c', 
            '_ex:1,2c',
            '_n:1,2', 
            '_nx:1,2,4c',
            '_n_noop:1,2',
            '_nx_noop:1,2,3c'
           ],
      },
      files:{
          src:  [ '**/*.php' ], //Parse all php files
          expand: true,
      }
},

Finally load the task by adding grunt.loadNpmTasks('grunt-pot'); to the bottom. Then to generate your .pot file:

grunt pot

checktextdomain – Verify localisation functions have been used correctly

Having generated a .pot file, gathered translations for your plug-in and then compiled them – it would be entirely wasted if you haven’t used the WordPress localisations functions properly. In particular, if you had failed to specify the correct domain, your efforts would have been wasted.

When coding it’s easy to forget to specify a text domain, or to mistype it. Or perhaps you’ve been using a variable for the domain, and now want to switch to a literal string.

The checktextdomain – not only checks if you’ve used the correct textdomain in the localisation function it can also correct it for you.

Simply provide it with:

  • Files to look in,
  • Keywords to look for (important: you must provide a domain argument specifier)
  • A text-domain to check against
  • Whether you want mistakes corrected (it will not add missing domains… yet).

The plug-in will then

  • Warn you if some keywords have been used without a text domain
  • Warn you if some keywords have been used with an incorrect text domain (optionally correct it for you)
  • Warn you if some keywords have been used with a variable text domain (optionally correct it for you)

There are various options for this plug-in to enable you to check (and correct) the things you want to. You can see all the available options for this Grunt plug-in on its Github page.

To install:

npm install grunt-checktextdomain --save-dev

You’ll notice that the keywords option is very similar to grunt-pot. There is an important distinction. For this plug-in to work you must extend the keyword specifier and indicate where the domain should be.

E.g. 2d indicates that the domain should be passed as the second argument of the localisation function

checktextdomain: {
   options:{
      text_domain: 'my-plugin',
      correct_domain: true, //Will correct missing/variable domains
      keywords: [ //WordPress localisation functions
            '__:1,2d',
            '_e:1,2d',
            '_x:1,2c,3d',
            'esc_html__:1,2d',
            'esc_html_e:1,2d',
            'esc_html_x:1,2c,3d',
            'esc_attr__:1,2d', 
            'esc_attr_e:1,2d', 
            'esc_attr_x:1,2c,3d', 
            '_ex:1,2c,3d',
            '_n:1,2,4d', 
            '_nx:1,2,4c,5d',
            '_n_noop:1,2,3d',
            '_nx_noop:1,2,3c,4d'
      ],
   },
   files: {
       src:  [ '**/*.php', ], //All php files
       expand: true,
   },
},

Finally load the task by adding grunt.loadNpmTasks('grunt-checktextdomain'); to the bottom. Then to check your files:

grunt checktextdomain

I’m planning on improving this further to warn you of missing contexts which using functions that expect one.

Final remarks

Remembering to add grunt.loadNpmTasks(...); at the bottom of your Gruntfile.js, just after grunt.loadTasks('tasks'); is easily forgotten. But there’s a way around this which I’ll discuss in my next post.

Just before publishing Brady Vercher announced his Grunt plug-in, which allows you to utilize the internationalisation tools that WordPress uses. There’s a bit more set-up involved, but a notable advantage over grunt-pot is that it recognises theme template headers as translatable.

Grunt & WordPress development

grunt

This is the first post of a five part series looking at WordPress plug-in/theme development with Grunt.

  1. Grunt & WordPress development
  2. Grunt & WordPress development II: Installing Grunt
  3. Grunt & WordPress development III: Tasks for internationalisation
  4. Grunt & WordPress development IV: Another task for internationalisation

Back in August the WordPress core team announced they were going to use Grunt in WordPress’ development. This in my view is a major stride forward for WordPress (more so than the much celebrated ‘features as plug-ins’ – which itself marked an improvement to WordPress’ development cycle).

This series of articles however, will be focussed on using Grunt for WordPress plug-ins and themes (which are fundamentally the same thing), and the tasks I use in development. In this first post I’ll discuss the what and the whys:

What is Grunt?

Grunt is a javascript based task runner from Ben Alman. It performs repetitive task such as compression, unit testing, linting, concatenation, preprocessing etc. Almost any task in the development, building or deployment of your WordPress plug-in which can be automated can be performed by Grunt – freeing you from those tedious, and potentially human-error-prone routines.

(Once Grunt is installed) there are two files which set up Grunt for use in your project:

  • package.json – which details your project (in this case: a WordPress plug-in) and it’s dependencies (in this context, Grunt and any Grunt plug-ins you want to use).
  • Gruntfile.js – listing the tasks you wish to perform and their configuration

Those tasks can be executed simply by running

 grunt [task name] 

in your command line. You’ll probably have multiple tasks that you’d want to run one after than other (e.g. one task to copy/generate files from your development environment to a build directory, and another to upload that directory to an Amazon S3 server). Instead of calling each manually Grunt allows you to create tasks which simply call a collection of other tasks. For example

 grunt test

might be configured to trigger unit-test and linting tasks.

Why Grunt?

The idea of automating deployments, unit testing, compressing images, scripts & stylesheets and other tasks you may wish to perform in your plug-in’s development, build and release cycle is certainly not unique to Grunt. Before I switched to Grunt I had a home-grown Makefile to perform a lot of my routine tasks.

Grunt however, brings this all under one roof: giving a familiar command line procedure to execute task(s). Importantly it allows (Grunt) plug-ins, and their end-users to add structure to their tasks. By this I mean tasks being able to call other tasks, being able to initiate an entire list of tasks, being able to configure all your tasks in only one file and easy of portability. In fact, if you download a development repository for a plug-in which includes the package.json and Gruntfile.js files, in one command you can install all the Grunt plug-ins it requires for use in testing, building and deploying that plug-in. (This assumes you have Node.js installed, which I’ll cover in part two).

Grunt doesn’t offer much new – but it does offer a much better solution.

It’s also popular – and popularity is key for any library, platform or tool to succeed and to grow. Popularity brings greater number of developers, they drive the growth of plug-ins & features and the increased functionality drives popularity. (Perhaps not dissimilar to WordPress’ own growth). Grunt’s ecosystem is already very substantial: there’s phpunit for PHP unit testing, jshint for Javascript linting, there is uglifyjs for compressing javascript files and imagemin to optimise images.

The point is: it has a large, and growing ecosystem. In the majority cases, for any task you might want to perform, there will exist a grunt plug-in to perform it.

And if there isn’t? Grunt is incredibly well documented, open-source and easy to dive into. If you find a gap in its armoury, the chances are it’s easy enough to fill.

What’s next?

If you weren’t already sold on Grunt, hopefully that will do it. The next post will be on installing Grunt and executing your first task.

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 :).

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

Deactivate Other Plug-ins On Deactivation

Note: The original proposed fix might cause undesirable effects if more than one plug-in is deactivated at once. The reason being that ‘aborting’ a deactivation, will abort all subsequent deactivations when deactivating more than one plug-in. I’ve included a better workaround which is far more reliable.

In this article I considered the situtation where you were developing an add-on plug-in, B, for a plug-in A, where B will obviously requrie A to be installed and active to function correctly. That article focused on how you can check that A was active, when activating B and if not displaying an admin notice.

Now we flip the problem on its head. Let’s suppose that the user is now deactivating A. We want to make sure that we then deactivate B (obviously only if it’s active – but WordPress handles these checks).

How we might like it to work but it doesn’t…

We might hope that we can just use deactivate_plugins inside the deactivation callback for A… unfortunately this doesn’t work and inspecting the code reveals why.

When the user deactivates A, WordPress calls deactivate_plugins(A).This function does the following:

  1. Gets an arary of all the current active plugins from the database.
  2. Performs some checks (e.g. is plugin A actually active?)
  3. Removes A from this array
  4. Fires the hook deactivate_A (which we hook onto using register_deactivation_hook)
  5. Updates the array to the database.

Now at step 4, we call deactivate_plugins(B) to deactivate B. 1-5 run through for B and is completed. The eagle eyed will notice that at this point the database is updated to consider B inactive, but A still active – but this isn’t the problem, in fact the reverse.

Once that’s completed we proceed to step 5 (in the original deactivate_plugins() call for A). Now this array is updated to the database – but this array was the very original one retrieved in step 1 and only has A removed. In particular we retrieved it at the beginning when B was still active, and so it contains B.

Note: your deactivation callbacks for B are fired, even through WordPress still thinks its active next time the page loads.

Solution – see better solution below

The fix is simple. In the callback of A, deactivate B as before. Then retrieve the updated active plug-ins array, and remove A from it and update the database. The database now has both A and B as inactive. Finally, to prevent WordPress undoing this, wp_redirect and exit to ‘abort’ the original deactivate_plugins process:

//This goes inside plugin A.
//When A is deactivated. Deactivate B.
register_deactivation_hook(__FILE__,'my_plugin_A_deactivate'); 
function my_plugin_A_deactivate(){
     $dependent = 'B/B.php';

    if( is_plugin_active($dependent) ){

        $dependent = plugin_basename( trim( $dependent) );
        $parent = plugin_basename( __FILE__  );

        //Deactivate the dependent 'add on'.
        deactivate_plugins($dependent);

        //Here comes the work around.... Manually remove THIS plugin from updated active_plugins
        $current = get_option( 'active_plugins', array() );
        $key = array_search( $parent, $current );
        if ( false !== $key ) {
            array_splice( $current, $key, 1 );
        }
        update_option('active_plugins', $current);

        //Now redirect to prevent WordPress from adding 'dependent' back in...
        wp_redirect(admin_url('plugins.php?deactivate=true&plugin_status=all&paged=1'));
        exit();
    }
}

A better solution

For reasons given at the top of this post, the above solution is less than idea. However, a better work-around is at hand. When WordPress updates the database with active arrays it does so with the generic update_option function. Consequently update_option_{$option} is triggered (after the option has been updated). You can use this to hook to deactivate your plug-in.

(There is the pre_update_option_{$option} filter in which you could filter the active plug-ins – but this is a bit too low level, and among other things occurs after sanitize_option is applied. While that doesn’t do anything for this option, it might do in the future. Also, we still want to use deactivate_plugins rather than handle the database option directly).

Here’s the complete solution:

//This goes inside Plugin A.
//When A is deactivated. Deactivate B.
  register_deactivation_hook(__FILE__,'my_plugin_A_deactivate'); 
function my_plugin_A_deactivate(){
    $dependent = 'B/B.php';
    if( is_plugin_active($dependent) ){
         add_action('update_option_active_plugins', 'my_deactivate_dependent_B');
    }
}

function my_deactivate_dependent_B(){
    $dependent = 'B/B.php';
    deactivate_plugins($dependent);
}

A similar workaround exists for network wide deactivation for multi sites.

I’ve posted this as a solution to this question on WordPress StackExchange. A trac ticket is been opened for this bug.