if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
class UpdraftCentral_Updates_Commands extends UpdraftCentral_Commands {
public function do_updates($updates) {
if (!is_array($updates)) $this->_generic_error_response('invalid_data');
if (!empty($updates['plugins']) && !current_user_can('update_plugins')) return $this->_generic_error_response('updates_permission_denied', 'update_plugins');
if (!empty($updates['themes']) && !current_user_can('update_themes')) return $this->_generic_error_response('updates_permission_denied', 'update_themes');
if (!empty($updates['core']) && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied', 'update_core');
if (!empty($updates['translations']) && !$this->user_can_update_translations()) return $this->_generic_error_response('updates_permission_denied', 'update_translations');
$this->_admin_include('plugin.php', 'update.php', 'file.php', 'template.php');
$this->_frontend_include('update.php');
if (!empty($updates['meta']) && isset($updates['meta']['filesystem_credentials'])) {
parse_str($updates['meta']['filesystem_credentials'], $filesystem_credentials);
if (is_array($filesystem_credentials)) {
foreach ($filesystem_credentials as $key => $value) {
// Put them into $_POST, which is where request_filesystem_credentials() checks for them.
$plugins = empty($updates['plugins']) ? array() : $updates['plugins'];
$plugin_updates = array();
foreach ($plugins as $plugin_info) {
$plugin_updates[] = $this->_update_plugin($plugin_info['plugin'], $plugin_info['slug']);
$themes = empty($updates['themes']) ? array() : $updates['themes'];
$theme_updates = array();
foreach ($themes as $theme_info) {
$theme = $theme_info['theme'];
$theme_updates[] = $this->_update_theme($theme);
$cores = empty($updates['core']) ? array() : $updates['core'];
foreach ($cores as $core) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- We dont use $core but we need the AS in the foreach so it needs to stay
$core_updates[] = $this->_update_core(null);
// Only one (and always we go to the latest version) - i.e. we ignore the passed parameters
$translation_updates = array();
if (!empty($updates['translations'])) {
$translation_updates[] = $this->_update_translation();
return $this->_response(array(
'plugins' => $plugin_updates,
'themes' => $theme_updates,
'translations' => $translation_updates,
* Updates a plugin. A facade method that exposes a private updates
* feature for other modules to consume.
* @param string $plugin Specific plugin to be updated
* @param string $slug Unique key passed for updates
public function update_plugin($plugin, $slug) {
return $this->_update_plugin($plugin, $slug);
* Updates a theme. A facade method that exposes a private updates
* feature for other modules to consume.
* @param string $theme Specific theme to be updated
public function update_theme($theme) {
return $this->_update_theme($theme);
* Gets available updates for a certain entity (e.g. plugin or theme). A facade method that
* exposes a private updates feature for other modules to consume.
* @param string $entity The name of the entity that this request is intended for (e.g. themes or plugins)
public function get_item_updates($entity) {
$updates = $this->maybe_add_third_party_items(get_theme_updates(), 'theme');
$updates = $this->maybe_add_third_party_items(get_plugin_updates(), 'plugin');
* Mostly from wp_ajax_update_plugin() in wp-admin/includes/ajax-actions.php (WP 4.5.2)
* Code-formatting style has been retained from the original, for ease of comparison/updating
* @param string $plugin Specific plugin to be updated
* @param string $slug Unique key passed for updates
private function _update_plugin($plugin, $slug) {
'slug' => sanitize_key($slug),
if (false !== strpos($plugin, '..') || false !== strpos($plugin, ':') || !preg_match('#^[^\/]#i', $plugin)) {
$status['error'] = 'not_found';
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
if (!isset($plugin_data['Name']) || !isset($plugin_data['Author']) || ('' == $plugin_data['Name'] && '' == $plugin_data['Author'])) {
$status['error'] = 'not_found';
if ($plugin_data['Version']) {
$status['oldVersion'] = $plugin_data['Version'];
if (!current_user_can('update_plugins')) {
$status['error'] = 'updates_permission_denied';
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Plugin_Upgrader($skin);
$result = $upgrader->bulk_upgrade(array($plugin));
if (is_array($result) && empty($result[$plugin]) && is_wp_error($skin->result)) {
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_array($result) && !empty($result[$plugin])) {
$plugin_update_data = current($result);
* If the `update_plugins` site transient is empty (e.g. when you update
* two plugins in quick succession before the transient repopulates),
* this may be the return.
* Preferably something can be done to ensure `update_plugins` isn't empty.
* For now, surface some sort of error here.
if (true === $plugin_update_data) {
$status['error'] = 'update_failed';
if (is_wp_error($result[$plugin])) {
$status['error'] = $result[$plugin]->get_error_code();
$status['error_message'] = $result[$plugin]->get_error_message();
$plugin_data = get_plugins('/' . $result[$plugin]['destination_name']);
$plugin_data = reset($plugin_data);
if ($plugin_data['Version']) {
$status['newVersion'] = $plugin_data['Version'];
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (isset($wp_filesystem->errors) && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
// An unhandled error occured
$status['error'] = 'update_failed';
* Adapted from _update_theme (above)
private function _update_core($core) {
// THis is included so we can get $wp_version
include(ABSPATH.WPINC.'/version.php');
$status['oldVersion'] = $wp_version;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
if (!current_user_can('update_core')) {
$status['error'] = 'updates_permission_denied';
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
$locale = get_locale();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$core_update_key = false;
$core_update_latest_version = false;
$get_core_updates = get_core_updates();
// THis is included so we can get $wp_version
@include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
foreach ($get_core_updates as $k => $core_update) {
if (isset($core_update->version) && version_compare($core_update->version, $wp_version, '>') && version_compare($core_update->version, $core_update_latest_version, '>')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
$core_update_latest_version = $core_update->version;
if (false === $core_update_key) {
$status['error'] = 'no_update_found';
$update = $get_core_updates[$core_update_key];
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Core_Upgrader($skin);
$result = $upgrader->upgrade($update);
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
} elseif (preg_match('/^[0-9]/', $result)) {
$status['newVersion'] = $result;
// An unhandled error occured
$status['error'] = 'update_failed';
private function _update_theme($theme) {
if (false !== strpos($theme, '/') || false !== strpos($theme, '\\')) {
$status['error'] = 'not_found';
$theme_version = $this->get_theme_version($theme);
if (false === $theme_version) {
$status['error'] = 'not_found';
$status['oldVersion'] = $theme_version;
if (!current_user_can('update_themes')) {
$status['error'] = 'updates_permission_denied';
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Theme_Upgrader($skin);
$result = $upgrader->bulk_upgrade(array($theme));
if (is_array($result) && empty($result[$theme]) && is_wp_error($skin->result)) {
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_array($result) && !empty($result[$theme])) {
$theme_update_data = current($result);
* If the `update_themes` site transient is empty (e.g. when you update
* two plugins in quick succession before the transient repopulates),
* this may be the return.
* Preferably something can be done to ensure `update_themes` isn't empty.
* For now, surface some sort of error here.
if (true === $theme_update_data) {
$status['error'] = 'update_failed';
$new_theme_version = $this->get_theme_version($theme);
if (false === $new_theme_version) {
$status['error'] = 'update_failed';
$status['newVersion'] = $new_theme_version;
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
// An unhandled error occured
$status['error'] = 'update_failed';
* Updates available translations for this website
private function _update_translation() {
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Language_Pack_Upgrader($skin);
$result = $upgrader->bulk_upgrade();
if (is_array($result) && !empty($result)) {
$status['success'] = true;
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
} elseif (is_bool($result) && $result) {
$status['error'] = 'up_to_date';
// An unhandled error occured
$status['error'] = 'update_failed';
private function get_theme_version($theme) {
if (function_exists('wp_get_theme')) {
$theme = wp_get_theme($theme);
if (is_a($theme, 'WP_Theme')) {
$theme_data = get_theme_data(WP_CONTENT_DIR . '/themes/'.$theme.'/style.css');
if (isset($theme_data['Version'])) {
return $theme_data['Version'];
* Adding third-party plugins/theme for UDC automatic updates, for some updaters which store their information when the transient is set, instead of (like most) when it is fetched
* @param Array $items A collection of plugins or themes for updates
* @param String $type A string indicating which type of collection to process (e.g. 'plugin' or 'theme')
* @return Array An updated collection of plugins or themes for updates
private function maybe_add_third_party_items($items, $type) {
// Here we're preparing a dummy transient object that will be pass to the filter
// and gets populated by those plugins or themes that hooked into the "pre_set_site_transient_*" filter.
// We're setting some default properties so that plugins and themes won't be able to bypass populating them,
// because most of the plugins and themes updater scripts checks whether or not these properties are set and
// non-empty or passed the 12 hour period (where WordPress re-starts the process of checking updates for
// these plugins and themes), otherwise, they bypass populating the update/upgrade info for these items.
$transient = (object) array(
'last_checked' => time() - (13 * 3600), /* Making sure that we passed the 12 hour period check */
'checked' => array('default' => 'none'),
'response' => array('default' => 'none')