Writing a simple WordPress plugin is straightforward. All you need is a trivial PHP file with an WordPress plugin specific header.
If you want to make its source code public and/or to share it with others then hosting the plugin within the WordPress Plugin Directory it would be the easiest way. They provide for it a web space (a SVN repository) where you can upload its changes. Nonetheless the WordPress Plugin Directory is automatically integrated with that repository such that every time you upload a new revision it will be automatically reflected in the Plugin Directory in a matter of minutes. When a new revision is uploaded/found for your plugin the WordPress Admin panel will notify you (the site administrator) that the new version was published and is ready for update. It takes care automatically of grabbing the changes and installing into your WordPress installation.
Note: I assume that you already know that only the free GPLv2+ plugins are allowed/hosted at WordPress.org.
What about those plugins that you want to sell (ie. they are commercial) and thus they cannot be hosted at WordPress.org? How can we provide a way of automatic update for non WordPress hosted plugins?
Well, the idea is to take a look at the WordPress source code and to try to mimic what the WordPress.org does. I would recommend you to look especially at:
- wp-includes/option.php
- wp-admin/includes/plugin-install.php
- wp-admin/includes/template.php
- wp-admin/update-core.php
Basically what we can do is to try to hook into WordPress at the right moment and to serve it the info from "our repository" instead of letting it to try to fetch some (inexistent) info from its Plugin Directory.
Two filters are especially useful:
- pre_set_site_transient_update_plugins
- plugins_api
The pre_set_site_transient_update_plugins filter can be used to feed the WordPress with the necessary info about our update. This filter is called by set_site_transient function within wp-includes/option.php file which send us an stdClass argument containing the following useful info:
- last_checked : when did WordPress checked for the newest versions last time
- checked: an array containing a key=>value pair of checked plugins (key=plugin folder/plugin-main file; value=plugin current version)
- response: an array containing a key=>value pair of new updates found (key=plugin folder/plugin-main file; value=a stdClass object);
- WordPress expects that the value object to have at least the following fields:
- slug: the plugin slug (see your plugin main file)
- plugin: plugin folder/plugin-main file
- new_version: a string containing the new version identifier (normally it should be greater than the installed version, right?)
- url: the plugin URL home page
- package: the URL that the WordPress will call in order to download the ZIP archive of your plugin's new version
- upgrade_notice (optionally): a string that will appear on the plugin list immediately after your plugin info
- WordPress expects that the value object to have at least the following fields:
- translations: an array of translations available for update
- no_update: an array like (3) above which contains those plugins which do not have a new version
So what we need to do to add our plugin to the list above is to insert a new item in the array mentioned at the (3) above. In order to hook into that filter we need to inform the WordPress about our filter function:
add_filter ( 'pre_set_site_transient_update_plugins', 'on_update_plugin');
Our function looks like this:
/** * Filter the value of a specific site transient before it is set * * @param mixed $value * Value of site transient. Expected to not be SQL-escaped * @return boolean|mixed $transient When false then WP update will found no updates at all otherwise it relies on the $transient content. */ function on_update_plugin ($transient){ if (empty ( $transient->checked )){ return $transient; } $plugin='plugin-folder/plugin-main-file.php'; $new_version='7.0'; $plugin_info = get_site_transient('update_plugins'); $current_version = $plugin_info->checked[$plugin]; // make sure we do nothing when the current version seems to be greater than the "new" one if (version_compare ( $current_version, $new_version, '>' )) { return $transient; } // create our response $obj = new \stdClass (); $obj->plugin = $plugin; $obj->slug = 'my-plugin-slug'; $obj->new_version = $new_version; $obj->package = 'http://mynixworld.info/api/get-plugin-zip'; // here we inject our response to the given $transient $transient->response [$obj->plugin] = $obj; return $transient; }
So when WordPress will check what plugins have a new version then it will run our function too. Our function will inject an object in the $response array mentioned at (3) above. If I would return the unmodified $transient then it is equivalent of not injecting anything in the $response. On the other hand if I would return false instead of the modified $transient then it is equivalent of reseting all these info mentioned at (1) .. (5) above so WordPress will not show anything not only for your plugin but for no other plugins too. So you don't mess with the Zohan!
Now, as you can see in the figure above each plugin update has a "View version details" link. When you click on that link the WordPress will show something like below:
If you want to take advantage of this feature of WordPress then we should hook somehow into the WordPress default functions, right? The right function for this is the plugins_api from the wp-admin/includes/plugin-install.php file.This function allows us to override the WordPress.org Plugin Install API entirely. So all we have to do is to hook into the plugins_api filter like below:
add_filter ( 'plugins_api', 'on_plugins_api', 10, 3 );
Our function looks like this:
/** * Add our self-hosted description to the filter * This is called for all plugins that have a newer version * * @param bool|object $result * The result object. Default false. * @param array $action * The type of information being requested from the Plugin Install API. * @param object $args * Plugin API arguments. * @return bool|object Please ensure that an object is returned. * */ public function on_plugins_api($result, $action, $args) { $plugin_slug='my-plugin-slug'; $plugin='plugin-folder/plugin-main-file.php'; if ($args->slug != $plugin_slug){ return $result; } $plugin_info = get_site_transient ( 'update_plugins' ); $args->version = $plugin_info->checked [$plugin]; // Create our object which should includes everything // see wp-admin/includes/plugin-install.php $obj = new \stdClass (); $obj->homepage = 'http://mynixworld.info/shop/product/wp-mybackup/'; $obj->version = '7.0'; $obj->new_version = '7.0'; $obj->author = 'Eugen'; $obj->slug = $plugin_slug; $obj->name = 'WP MyBackup'; $obj->plugin_name = $plugin_slug; $obj->description = 'This is the plugin description'; $obj->requires = '3.0'; $obj->tested = '4.3.1'; $obj->last_updated = date ( 'Y-m-d' ); $obj->download_link = 'http://mynixworld.info/api/get-plugin-zip'; $obj->downloaded = 15000; $obj->active_installs = 4000; $obj->donate_link = 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ABCDEFGHIJKL'; $obj->contributors = array ( 'contrib_username1' => 'contrib profile1', 'contrib_username2' => 'contrib profile2' ); // see wp-admin/includes/template.php $obj->rating = 50; $obj->ratings = array ( 5 => 100, 4 => 200 ); $obj->num_ratings = 2000; $obj->sections = array ( 'description' => 'A', 'changelog' => '<h4>7.0</h4><ul><li>Fix - super cool fix</li><li>Tweak - super hot tweak</li></ul>', 'another_section' => 'C' ); $obj->upgrade_notice = 'minor upgrade'; $obj->license = 'my-custom-field'; $obj->tags = array ( 'backup' => 'backup restore' ); $obj->banners = array ( 'low' => 'https://ps.w.org/wp-mybackup/assets/banner-772x250.png?rev=1244519', 'high' => '' ); return $obj; }
That's all folks!
Of course, we can optimize a little bit the code, we can even encapsulate it within a class such that we can reuse it at a later time with some other projects. I just wanted to keep it as simple as possible such that you may understand only what is really important. The other tweaks can be done later, when the basics are understood and work!
@edit: please keep in mind that the on_plugins_api function in the example above should return at least the following fields:
- name
- sections['description']
- last_updated
- version
It will look like the image below but at least it'll work:
Now, if you think that this article was interesting don't forget to rate it. It shows me that you care and thus I will continue write about these things.
Eugen Mihailescu
Latest posts by Eugen Mihailescu (see all)
- Dual monitor setup in Xfce - January 9, 2019
- Gentoo AMD Ryzen stabilizator - April 29, 2018
- Symfony Compile Error Failed opening required Proxies - January 22, 2018