I have recently been spending a lot of time developing the IssueM plugin for a company I do a lot of contract work for. IssueM is an issue manager plugin for WordPress, to manage issues for online magazines, periodicals, and such. The idea for IssueM started a couple years ago, and even started as a plugin, then morphed into a theme, and now it is back to a plugin. There was a lot of reasoning for these changes, but the biggest was that we wanted to make a plugin that could be used in any theme. Since the IssueM theme didn’t suit everyone’s needs, it was much easier to turn the functionality into shortcodes and widgets. Plus, since WordPress 3.1, we have all the capabilities we need to make a plugin extremely useful and successful in this arena.
Anyway, since this is a premium plugin we need a way to notify people of plugin updates. Frankly, I have never needed to do this, so I needed to figure it out. Luckily, I have a copy of Professional WordPress Plugin Development and in Chapter 9 there is very simple example of exactly what I wanted to do. Unluckily, the example doesn’t work… well, not entirely. It was always reporting an update, even if it was on the latest version. But I had enough information to trace down what was happening and figure out what I needed to change. By the way, the example code for the book is available from the publisher’s website.
So, without further adieu, this is a modified version of what I did:
This is the code I added to my plugin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
function my_plugin_plugins_api( $false, $action, $args ) { $plugin_slug = 'my_plugin_slug'; // Check if this plugins API is about this plugin if( $args->slug != $plugin_slug ) return $false; // POST data to send to your API $args = array( 'action' => 'get-plugin-information' ); // Send request for detailed information $response = $this->my_plugin_api_request( $args ); return $response; } add_filter( 'plugins_api', 'my_plugin_plugins_api', 10, 3 ); function my_plugin_update_plugins( $transient ) { // Check if the transient contains the 'checked' information // If no, just return its value without hacking it if ( empty( $transient->checked ) ) return $transient; // The transient contains the 'checked' information // Now append to it information form your own API $plugin_path = plugin_basename( __FILE__ ); // POST data to send to your API $args = array( 'action' => 'check-latest-version' ); // Send request checking for an update $response = $this->my_plugin_api_request( $args ); // If there is a new version, modify the transient if( version_compare( $response->new_version, $transient->checked[$plugin_path], '>' ) ) $transient->response[$plugin_path] = $response; return $transient; } add_filter( 'pre_set_site_transient_update_plugins', 'my_plugin_update_plugins' ); function my_plugin_api_request( $args ) { // Send request $request = wp_remote_post( http://api.url/location, array( 'body' => $args ) ); if ( is_wp_error( $request ) || 200 != wp_remote_retrieve_response_code( $request ) ) return false; $response = unserialize( wp_remote_retrieve_body( $request ) ); if ( is_object( $response ) ) return $response; else return false; } |
Then I setup a WordPress install for my API and created a custom template for the API page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/** * Template Name: API **/ $action = $_REQUEST['action']; // Create new object $response = new stdClass; switch( $action ) { // API is asked for the existence of a new version of the plugin case 'check-latest-version': $response->slug = 'my_plugin_slug'; $response->new_version = '1.0.2'; $response->url = 'http://plugin.url/'; $response->package = 'http://plugin.url/download/location'; break; // Request for detailed information case 'get-plugin-information': $response->name = 'my_plugin_name'; $response->slug = 'my_plugin_slug'; $response->requires = '3.3'; $response->tested = '3.3.1'; $response->rating = 100.0; //just for fun, gives us a 5-star rating :) $response->num_ratings = 1000000000; //just for fun, a lot of people rated it :) $response->downloaded = 1000000000; //just for fun, a lot of people downloaded it :) $response->last_updated = "2012-04-15"; $response->added = "2012-02-01"; $response->homepage = "http://plugin.url/"; $response->sections = array( 'description' => 'Add a description of your plugin', 'changelog' => 'Add a list of changes to your plugin' ); $response->download_link = 'http://plugin.url/download/location'; break; } echo serialize( $response ); |
This isn’t exactly what I did, but it’s much more basic. Here are some things you can think about. First, the IssueM plugin uses an API key, so I send the API Key to api to verify the user has the right to update the plugin (say they don’t pay next year or something). The download_link setting is dynamic, for IssueM, it’s something like http://api.url/download/location?api=API_KEY. Then I have a separate script at the download location that will also verify the API Key and will send the latest IssueM plugin (which is located in a hidden directory). I also didn’t hard-code the downloaded argument, I actually track the downloads by using the dynamic download script. So my $response->downloaded actually equals get_option( ‘my_plugin_downloads’ ).
The download script I setup looks something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Template Name: Download Latest Version * **/ $downloads = get_option( 'my_plugin_downloads', 100 ); update_option( 'my_plugin_downloads', ++$downloads ); $file = '/physical/location/of/your/file.zip'; $file_content = file_get_contents( $file ); header( 'Content-type: application/force-download' ); header( 'Content-Disposition: attachment; filename="my_plugin.zip"' ); echo $file_content; exit; |
You can completely customize and expand this code to do whatever you want. By adding extra arguments being sent to your API, you could have several plugins in this same repository. You can send API key information, host information, and anything else you’d need/want.
Nice write up!
Unfortunately, you’re right, there’s a typo (or plain error) in the code given in the Professional WordPress Plugin Development and the solution you worked out was essentially the same as the one I had arrived at. I spent a fair bit of time before I realised the error in the code!
I’m curious that your simplified download script uses the wp_options table for storing download stats, as I would have expected this script to be outside your WP install.
Anyway, thanks for writing this up!
Hi Ade,
Yeah, WordPress has so many lovely built-in functions, I have a WordPress install for the API system. Combined with the s2member member management plugin for WordPress, it makes a pretty powerful system.
Is this the only change:
if( version_compare( $response->new_version, $transient->checked[$plugin_path], ‘>’ ) )
$transient->response[$plugin_path] = $response;
While that fixes most of it, the only thing it leaves is that circled number on the plugins page, that indicates an upgrade (though it does not say which one it is for).
-Chris
Make sure the function “my_plugin_plugins_api” returns $false and not only false. Because it will break the filter for other hooks to that filter.
Excellent point, updated the post. Thanks!