WordPress upgrades a plugin when an admin click on update-now button or upload a new zip file. Besides that, a WordPress plugin can also be updated automatically if an admin enables auto-update for that plugin. Here’s how plugin auto update works.
WP_Version_Check
When we click on Enable auto-updates for a plugin, WordPress store the plugin’s folder name in auto_update_plugins
option. Later, the plugin (along with other auto update enabled plugin) gets updated on a recurring cron event wp_version_check
. wp_version_check
cron event is triggered in every 12 hour.
/**
* @file wp-includes/update.php
*/
function wp_schedule_update_checks() {
if ( ! wp_next_scheduled( 'wp_version_check' ) && ! wp_installing() ) {
wp_schedule_event( time(), 'twicedaily', 'wp_version_check' );
}
// ...
}
In cases, WordPress also schedule extra event to run this job when WordPress version check request respond with a ttl
property.
/**
* @file wp-includes/update.php
*/
function wp_version_check( $extra_stats = array(), $force_check = false ) {
// ...
$url = 'http://api.wordpress.org/core/version-check/1.7/?' . http_build_query( $query, '', '&' );
// ...
$response = wp_remote_post( $url, $options );
// ...
$body = trim( wp_remote_retrieve_body( $response ) );
$body = json_decode( $body, true );
// ...
if ( ! empty( $body['ttl'] ) ) {
$ttl = (int) $body['ttl'];
if ( $ttl && ( time() + $ttl < wp_next_scheduled( 'wp_version_check' ) ) ) {
// Queue an event to re-run the update check in $ttl seconds.
wp_schedule_single_event( time() + $ttl, 'wp_version_check' );
}
}
// ...
}
wp_version_check
cron event is handled by a function named wp_version_check
. At the end of this function, WordPress triggers an action named wp_maybe_auto_update
which performs the auto update. wp_maybe_auto_update
action is only triggered when wp_version_check
function runs in the cron context. Means, calling wp_version_check
in a plugin will not trigger
action. wp_maybe_auto_update
wp_maybe_auto_update
action is responded by a function named wp_maybe_auto_update.
WP_Maybe_Auto_Update
Here’s the function definition
/**
* @file wp-includes/update.php
*/
function wp_maybe_auto_update() {
include_once ABSPATH . 'wp-admin/includes/admin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
$upgrader = new WP_Automatic_Updater;
$upgrader->run();
}
So the main task is done in WP_Automatic_Updater
class.
The $upgrader->run()
method first checks whether automatic update is enabled.
Automatic update might be disabled in many ways, defining wp-config constant (AUTOMATIC_UPDATER_DISABLED
, DISALLOW_FILE_MODS
), using hook (automatic_updater_disabled
) and if WordPress is installing something (wp_installing
function).
The next thing it checks whether current site is the main site of a network (for multisite installation). It doesn’t process auto update on sub-sites.
The next thing WordPress do is create a lock so that the updater doesn’t run recurrently rather only once. And remove few filters.
/**
* @class WP_Automatic_Updater
*/
public function run() {
if ( $this->is_disabled() ) {
return;
}
if ( ! is_main_network() || ! is_main_site() ) {
return;
}
if ( ! WP_Upgrader::create_lock( 'auto_updater' ) ) {
return;
}
// ...
}
Updating Plugin
Next the process fetch plugin update information, and run each plugin update one by one. Plugin only gets updated if an update is available, auto-update is enabled & minimum php version requirements is met.
/**
* @class WP_Automatic_Updater
*/
public function run() {
// ...
wp_update_plugins(); // Check for plugin updates.
$plugin_updates = get_site_transient( 'update_plugins' );
if ( $plugin_updates && ! empty( $plugin_updates->response ) ) {
foreach ( $plugin_updates->response as $plugin ) {
$this->update( 'plugin', $plugin );
}
// Force refresh of plugin update information.
wp_clean_plugins_cache();
}
// ...
}
The update process uses Automatic_Upgrader_Skin
class as skin, and Plugin_Upgrader
class as upgrader. Skin is responsible handing the messages shown on interface, and upgrader is responsible for handing the file download, verification, replacement, deletion etc process. The next process is upgrading the plugin. $upgrader_item
is the folder name (or single file name) of the plugin.
/**
* @class WP_Automatic_Updater
*/
public function update( $type, $item ) {
$skin = new Automatic_Upgrader_Skin;
// ...
$upgrader = new Plugin_Upgrader( $skin );
$context = WP_PLUGIN_DIR;
// ...
if ( ! $this->should_update( $type, $item, $context ) ) {
return false;
}
$upgrader_item = $item->plugin;
$upgrade_result = $upgrader->upgrade(
$upgrader_item,
array(
'clear_update_cache' => false,
// Always use partial builds if possible for core updates.
'pre_check_md5' => false,
// Only available for core updates.
'attempt_rollback' => true,
// Allow relaxed file ownership in some scenarios.
'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
)
);
}
Now we jump to the Plugin_Upgrader::upgrade
method. This upgrader method performs a check to see if the plugin does have an update. $r
is the plugin update response, $r->package
is the direct link to the new version zip file. Then it runs the upgrade. Following is the code
/**
* @class Plugin_Upgrader
*/
public function upgrade( $plugin, $args = array() ) {
// ...
$current = get_site_transient( 'update_plugins' );
// ...
$r = $current->response[ $plugin ];
// ...
$this->run(
array(
'package' => $r->package,
'destination' => WP_PLUGIN_DIR,
'clear_destination' => true,
'clear_working' => true,
'hook_extra' => array(
'plugin' => $plugin,
'type' => 'plugin',
'action' => 'update',
),
)
);
// ...
);
This run method is defined in WP_Upgrader
class. First it downloads the package, then extract it to wp-content/upgrade
folder, then remove Then check for plugin file.
/**
* @class Wp_Upgrader
*/
public function run( $options ) {
// ...
$download = $this->download_package( $options['package'], true, $options['hook_extra'] );
// ...
$delete_package = ( $download !== $options['package'] );
// ...
$working_dir = $this->unpack_package( $download, $delete_package );
// ...
$result = $this->install_package(
array(
'source' => $working_dir,
'destination' => $options['destination'],
'clear_destination' => $options['clear_destination'],
'abort_if_destination_exists' => $options['abort_if_destination_exists'],
'clear_working' => $options['clear_working'],
'hook_extra' => $options['hook_extra'],
)
);
// ...
}
There’s a interesting thing in this install_package
method. If the new update zip contains a folder in the root and no other files, it extracts the folder content as the plugin folder / files. Means, you can zip a plugin of something/my-plugin/file.php
as and update.
That’s it.