Current File : /home/pacjaorg/public_html/sceju/wp-content/plugins/updraftplus/udaddons/updraftplus-addons.php |
<?php
// @codingStandardsIgnoreStart
/*
Obtain and manage extra features for UpdraftPlus Backup
This plugin communicates with the mothership via encrypted HTTPS and falls back to HTTP if this fails; even over HTTP, nothing of significant value is thereby put at risk - see https://updraftplus.com/faqs/tell-me-about-my-updraftplus-com-account/
However, if your organisation has strong requirements for use of SSL, then add the following line to your wp-config.php to forbid any non-https communications:
define('UPDRAFTPLUS_ADDONS_SSL', true);
This plugin:
- over-rides the update mechanism for the UpdraftPlus plugin, so that we can get them from our site
- shows the user his installed and available add-ons
- also over-rides its own update mechanism
This directory should not be added to the wordpress.org SVN
*/
// @codingStandardsIgnoreEnd
define('UDADDONS2_DIR', dirname(realpath(__FILE__)));
define('UDADDONS2_URL', UPDRAFTPLUS_URL.'/udaddons');
define('UDADDONS2_SLUG', 'updraftplus-addons');
define('UDADDONS2_PAGESLUG', 'updraftplus');
$udaddons2_mothership = (defined('UPDRAFTPLUS_ADDONS_SSL') && !UPDRAFTPLUS_ADDONS_SSL) ? 'http://' : 'https://';
$udaddons2_mothership .= defined('UDADDONS2_TEST_MOTHERSHIP') ? UDADDONS2_TEST_MOTHERSHIP : 'updraftplus.com';
global $updraftplus_addons2; // Need to explicitly globalise the variable or WP-CLI won't recognise it https://github.com/wp-cli/wp-cli/issues/4019#issuecomment-297410839
$updraftplus_addons2 = new UpdraftPlusAddons2('updraftplus', $udaddons2_mothership);
class UpdraftPlusAddons2 {
public $slug;
public $url;
public $debug = false;
public $user_addons;
public $user_support;
public $available_addons;
public $remote_addons;
public $plug_updatechecker;
private $admin_notices = array();
private $saved_site_id;
private $plugin_file;
/**
* Not used anywhere, but it is set.
*
* @var UpdraftPlusAddOns_Options2
*/
private $options;
/**
* Constructor
*
* @param String $slug - the plugin's slug
* @param String $url - the URL of the mothership that updates are checked at
*/
public function __construct($slug, $url) {
$this->slug = $slug;
$this->url = $url;
// Hide unwanted/user-hostile JetPack promotions, which cause genuine directory search results to be de-prioritised by default (if you use any of JetPack's 45 modules, then it promotes things from the others to the top of search results): https://wptavern.com/jetpack-7-1-adds-feature-suggestions-to-plugin-search-results. If using one JetPack module really does mean that you now automatically prefer all the others, then remove these lines, or add higher-priority filters to add them back.
add_filter('jetpack_show_promotions', '__return_false', 20);
add_filter('jetpack_just_in_time_msgs', '__return_false', 20);
// This needs to exact match PluginUpdateChecker's view
$this->plugin_file = plugin_basename($this->slug.'/'.$this->slug.'.php');
add_action('updraftplus_restore_db_pre', array($this, 'updraftplus_restore_db_pre'));
add_action('updraftplus_restored_db_is_migration', array($this, 'updraftplus_restored_db_is_migration'));
add_action('updraftplus_showrawinfo', array($this, 'updraftplus_showrawinfo'));
add_action((is_multisite() && class_exists('UpdraftPlusAddOn_MultiSite')) ? 'network_admin_menu' : 'admin_menu', array($this, 'admin_menu'));
add_action('wp_ajax_udaddons_claimaddon', array($this, 'ajax_udaddons_claimaddon'));
if (class_exists('UpdraftPlusAddons')) return;
// Prevent updates from wordpress.org showing in all circumstances. Run with lower than default priority, to allow later processes to add something.
add_filter('site_transient_update_plugins', array($this, 'site_transient_update_plugins'), 9);
// Over-ride update mechanism for the plugin
if (is_readable(UPDRAFTPLUS_DIR.'/vendor/yahnis-elsts/plugin-update-checker/plugin-update-checker.php')) {
updraft_try_include_file('vendor/yahnis-elsts/plugin-update-checker/plugin-update-checker.php', 'include_once');
add_filter('puc_check_now-'.$this->slug, array($this, 'puc_check_now'), 10, 3);
add_filter('puc_retain_fields-'.$this->slug, array($this, 'puc_retain_fields'));
add_filter('puc_request_info_options-'.$this->slug, array($this, 'puc_request_info_options'));
// Run after the PluginUpdateChecker has done its stuff
add_filter('site_transient_update_plugins', array($this, 'possibly_inject_translations'), 11);
// If https does not work, then try http
$plug_updatechecker = Puc_v4_Factory::buildUpdateChecker($this->url."/plugin-info/", WP_PLUGIN_DIR.'/'.$this->slug.'/'.$this->slug.'.php', $this->slug, 24);
// The null case is seen in HS#36382
if (null === $plug_updatechecker) {
error_log("UpdraftPlus: Puc_v4_Factory::buildUpdateChecker() return a null object");
} else {
$plug_updatechecker->addQueryArgFilter(array($this, 'updater_queryargs_plugin'));
if ($this->debug) $plug_updatechecker->debugMode = true;
$plug_updatechecker->addFilter('request_metadata_http_result', array($this, 'request_metadata_http_result'), 10, 3);
$this->plug_updatechecker = $plug_updatechecker;
}
$this->move_updraftplus_update_cron();
}
}
/**
* This function is used to redistribute update check times; it is a control to help spread server load
*
* @return void
*/
private function move_updraftplus_update_cron() {
$cron_move_date = 1607558400; // 2020-12-10 00:00
$cleanup_date = 1607644800; // 2020-12-11 00:00
$option_name = 'updraftplus_move_update_cron_dec20';
$time_now = time();
$start_of_today = $time_now - ($time_now % 86400);
if ($start_of_today > $cleanup_date + 172800) return;
$moved_cron = get_site_option($option_name);
if ($cron_move_date == $start_of_today && !$moved_cron) {
$timestamp = wp_next_scheduled('puc_cron_check_updates-updraftplus');
$begintime1 = $cron_move_date + 3600 * 9; // 09:00
$endtime1 = $begintime1 + 3600 * 12;
// $begintime2 = 1588953600; // 2020-05-08 16:00
// $endtime2 = $begintime2 + 7 * 3600;
if ($timestamp >= $begintime1 && $timestamp <= $endtime1) {
wp_clear_scheduled_hook('puc_cron_check_updates-updraftplus');
wp_schedule_event($timestamp + 13 * 3600, 'daily', 'puc_cron_check_updates-updraftplus');
$this->update_option($option_name, true);
}
} elseif ($moved_cron && $start_of_today >= $cleanup_date) {
delete_site_option($option_name);
}
}
/**
* WordPress filter, from plugins-updates-checker. We use it to fall back to http:// if https:// fails
*
* @param Mixed $result The result of the first HTTP request
* @param String $url The URL being fetched
* @param Array $options The options for the WP HTTP API
*
* @return Mixed - the result of the WP HTTP API call to use
*/
public function request_metadata_http_result($result, $url, $options) {
$url_non_ssl = preg_replace('/^https:/i', 'http:', $url);
if (is_wp_error($result) && $url != $url_non_ssl && (!defined('UPDRAFTPLUS_ADDONS_SSL') || !UPDRAFTPLUS_ADDONS_SSL)) {
$result = wp_remote_get(
$url_non_ssl,
$options
);
}
return $result;
}
/**
* Inject our translation information into the updates object. Runs upon the WP filter site_transient_update_plugins .
*
* @param Object $updates
*
* @return Object
*/
public function possibly_inject_translations($updates) {
$slug = $this->slug;
$checker = is_object($this->plug_updatechecker) ? get_site_option($this->plug_updatechecker->optionName, null) : null;
if (!isset($checker->update->translations) || !is_array($checker->update->translations)) return $updates;
foreach ($checker->update->translations as $translation) {
// Remove any existing updates indicated for this slug/language - i.e. we want it over-ridden with the result from the server
if (isset($updates->translations) && is_array($updates->translations)) {
foreach ($updates->translations as $k => $translation) {
if ('plugin' == $translation['type'] && $translation['slug'] == $slug) unset($updates->translations[$k]);
}
} else {
$updates->translations = array();
}
// Add our translation onto the array
$translation_array = (array) $translation;
$translation_array['type'] = 'plugin';
$translation_array['slug'] = $slug;
$updates->translations[] = $translation_array;
}
return $updates;
}
public function puc_request_info_options($opts) {
if (is_array($opts)) $opts['timeout'] = 10;
return $opts;
}
/**
* Print out update URL information
*/
public function updraftplus_showrawinfo() {
echo "<p>Updates URL: ".esc_url($this->url)."</p>";
}
public function updraftplus_restore_db_pre() {
$this->saved_site_id = $this->siteid();
}
public function updraftplus_restored_db_is_migration() {
$option = UDADDONS2_SLUG.'_siteid';
if (!empty($this->saved_site_id)) {
global $updraftplus;
$updraftplus->log("Restored pre-migration site ID for this installation");
delete_site_option($option);
add_site_option($option, $this->saved_site_id);
}
}
/**
* Fields to be retained from the remote request
*
* @param Array $fields - list of fields
*
* @return Array - list after filtering
*/
public function puc_retain_fields($fields) {
$retain_these = array('translations', 'extraProperties');
foreach ($retain_these as $retain) {
if (!in_array($retain, $fields)) $fields[] = $retain;
}
return $fields;
}
public function admin_notices() {
foreach ($this->admin_notices as $key => $notice) {
$notice = '<span style="font-size: 115%;">'.$notice.'</span>';
if (is_numeric($key)) {
$this->show_admin_warning($notice);
} else {
$this->show_admin_warning($notice, 'error updraftupdatesnotice updraftupdatesnotice-'.$key);
}
}
}
/**
* Runs upon the WP action admin_menu or network_admin_menu
*/
public function admin_menu() {
global $pagenow, $updraftplus;
// Do we want to display a notice about the upcoming or past expiry of their UpdraftPlus subscription?
if (!empty($this->plug_updatechecker) && !empty($this->plug_updatechecker->optionName) && current_user_can('update_plugins')) {
// (!is_multisite() && 'options-general.php' == $pagenow) || (is_multisite() && 'settings.php' == $pagenow) ||
if ('plugins.php' == $pagenow || 'update-core.php' == $pagenow || (('options-general.php' == $pagenow || 'admin.php' == $pagenow) && !empty($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page'])) {
$do_expiry_check = true;
$dismiss = '';
} elseif (is_admin()) {
$dismissed_until = UpdraftPlus_Options::get_updraft_option('updraftplus_dismissedexpiry', 0);
if ($dismissed_until <= time()) {
$do_expiry_check = true;
$dismiss = '<div style="float:right; position: relative; top:-24px;" class="ud-expiry-dismiss"><a href="#" onclick="jQuery(\'.ud-expiry-dismiss\').parent().slideUp(); jQuery.post(ajaxurl, {action: \'updraft_ajax\', subaction: \'dismissexpiry\', nonce: \''.wp_create_nonce('updraftplus-credentialtest-nonce').'\' })">'.sprintf(__('Dismiss from main dashboard (for %s weeks)', 'updraftplus'), 2).'</a></div>';
}
}
}
$oval = is_object($this->plug_updatechecker) ? get_site_option($this->plug_updatechecker->optionName, null) : null;
$updateskey = 'x-spm-expiry';
$supportkey = 'x-spm-support-expiry';
$metakey = 'x-spm-meta';
$subscription_activekey = 'x-spm-subscription-active';
$meta_info = (is_object($oval) && !empty($oval->update) && is_object($oval->update) && !empty($oval->update->extraProperties[$metakey])) ? (array) json_decode($oval->update->extraProperties[$metakey], true) : array();
$yourversionkey = 'x-spm-yourversion-tested';
if (is_object($oval) && !empty($oval->update) && is_object($oval->update) && !empty($oval->update->extraProperties[$yourversionkey]) && UpdraftPlus_Options::user_can_manage() && (!defined('UPDRAFTPLUS_DISABLECOMPATNOTICE') || true != UPDRAFTPLUS_DISABLECOMPATNOTICE)) {
// Prevent false-positives
if (file_exists(UPDRAFTPLUS_DIR.'/readme.txt') && $fp = fopen(UPDRAFTPLUS_DIR.'/readme.txt', 'r')) {
$file_data = fread($fp, 1024);
if (preg_match("/Tested up to: (\d+(\.\d+)+)/", $file_data, $matches)) {
$readme_says = $matches[1];
}
fclose($fp);
}
$wp_version = $updraftplus->get_wordpress_version();
$compare_wp_version = (preg_match('/^(\d+\.\d+)\..*$/', $wp_version, $wmatches)) ? $wmatches[1] : $wp_version;
$compare_tested_version = $oval->update->extraProperties[$yourversionkey];
if (!empty($readme_says) && version_compare($readme_says, $compare_tested_version, '>')) $compare_tested_version = $readme_says;
if (version_compare($compare_wp_version, $compare_tested_version, '>')) {
$this->admin_notices['yourversiontested'] = '<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('The installed version of UpdraftPlus Backup/Restore has not been tested on your version of WordPress (%s).', 'updraftplus'), $wp_version).' '.sprintf(__('It has been tested up to version %s.', 'updraftplus'), $compare_tested_version).' <a href="https://updraftplus.com/seeing-warning-versions-wordpress-updraftplus-tested/">'.__('You should update UpdraftPlus to make sure that you have a version that has been tested for compatibility.', 'updraftplus').'</a>';
}
}
if (!empty($do_expiry_check) && is_object($oval) && !empty($oval->update) && is_object($oval->update) && !empty($oval->update->extraProperties[$updateskey])) {
if (preg_match('/(^|)expired_?(\d+)?(,|$)/', $oval->update->extraProperties[$updateskey], $matches)) {
if (empty($matches[2])) {
$message = __('Your paid access to UpdraftPlus updates for this site has expired.', 'updraftplus').' '.__('You will no longer receive updates to UpdraftPlus.', 'updraftplus').' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To regain access to updates (including future features and compatibility with future WordPress releases) and support, please renew.', 'updraftplus').'</a>';
if ($updraftplus->have_addons > 14 && !empty($meta_info['indirect'])) {
$message .= ' <br>'.sprintf(__('If you have already renewed, then you need to allocate a licence to this site - %s', 'updraftplus'), '<a href="'.UpdraftPlus_Options::admin_page().'?page=updraftplus&tab=addons">'.__('go here', 'updraftplus').'</a>');
}
$this->admin_notices['updatesexpired'] = $message.$dismiss;
} else {
$this->admin_notices['updatesexpired'] = sprintf(__('Your paid access to UpdraftPlus updates for %s add-ons on this site has expired.', 'updraftplus'), $matches[2]).' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To regain access to updates (including future features and compatibility with future WordPress releases) and support, please renew.', 'updraftplus').'</a>'.$dismiss;
}
}
$subscription_status = apply_filters('udmupdater_subscription_active', isset($oval->update->extraProperties[$subscription_activekey]) ? $oval->update->extraProperties[$subscription_activekey] : false);
if (empty($subscription_status)) {
if (preg_match('/(^|,)soonpartial_(\d+)_(\d+)($|,)/', $oval->update->extraProperties[$updateskey], $matches)) {
$this->admin_notices['updatesexpiringsoon'] = sprintf(__('Your paid access to UpdraftPlus updates for %s of the %s add-ons on this site will soon expire.', 'updraftplus'), $matches[2], $matches[3]).' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To retain your access, and maintain access to updates (including future features and compatibility with future WordPress releases) and support, please renew.', 'updraftplus').'</a>'.$dismiss;
} elseif (preg_match('/(^|,)soon($|,)/', $oval->update->extraProperties[$updateskey])) {
$message = __('Your paid access to UpdraftPlus updates for this site will soon expire.', 'updraftplus').' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To retain your access, and maintain access to updates (including future features and compatibility with future WordPress releases) and support, please renew.', 'updraftplus').'</a>';
if ($updraftplus->have_addons > 14 && !empty($meta_info['indirect'])) {
$message .= ' <br>'.sprintf(__('If you have already renewed, then you need to allocate a licence to this site - %s', 'updraftplus'), '<a href="'.UpdraftPlus_Options::admin_page().'?page=updraftplus&tab=addons">'.__('go here', 'updraftplus').'</a>');
}
$this->admin_notices['updatesexpiringsoon'] = $message.$dismiss;
}
}
} elseif (!empty($do_expiry_check) && is_object($oval) && !empty($oval->update) && is_object($oval->update) && !empty($oval->update->extraProperties[$supportkey])) {
if ('expired' == $oval->update->extraProperties[$supportkey]) {
$this->admin_notices['supportexpired'] = __('Your paid access to UpdraftPlus support has expired.', 'updraftplus').' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To regain your access, please renew.', 'updraftplus').'</a>'.$dismiss;
} elseif ('soon' == $oval->update->extraProperties[$supportkey]) {
$this->admin_notices['supportsoonexpiring'] = __('Your paid access to UpdraftPlus support will soon expire.', 'updraftplus').' <a href="https://updraftplus.com/renewing-updraftplus-purchase/">'.__('To maintain your access to support, please renew.', 'updraftplus').'</a>'.$dismiss;
}
}
add_action('all_admin_notices', array($this, 'admin_notices'));
if (!function_exists('is_plugin_active')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
if (is_plugin_active('updraftplus-addons/updraftplus-addons.php')) {
deactivate_plugins('updraftplus-addons/updraftplus-addons.php');
if (('options-general.php' == $pagenow || 'settings.php' == $pagenow) && !empty($_REQUEST['page']) && 'updraftplus-addons' == $_REQUEST['page']) {
wp_redirect($this->addons_admin_url());
exit;
}
// Do nothing more this time to avoid duplication
return;
} elseif (is_dir(WP_PLUGIN_DIR.'/updraftplus-addons') && current_user_can('delete_plugins')) {
// Exists, but not active - nag them
if ((!is_multisite() && 'options-general.php' == $pagenow) || (is_multisite() && 'settings.php' == $pagenow) || 'plugins.php' == $pagenow) add_action('all_admin_notices', array($this, 'deinstall_udaddons'));
}
if (class_exists('UpdraftPlusAddons')) return;
// Refresh, if specifically requested
if ((('options-general.php' == $pagenow) || (is_multisite() && 'settings.php' == $pagenow)) && !empty($_GET['udm_refresh'])) {
if ($this->plug_updatechecker) $this->plug_updatechecker->checkForUpdates();
}
include_once(UDADDONS2_DIR.'/options.php');
$this->options = new UpdraftPlusAddOns_Options2($this->slug, __('UpdraftPlus Addons', 'updraftplus'), $this->url);
}
public function addons_admin_url() {
return UpdraftPlus_Options::admin_page().'?page='.UDADDONS2_PAGESLUG.'&tab=addons';
}
/**
* Funnelling through here a) allows for future flexibility and b) allows us to migrate elegantly from the previous non-MU-friendly setup
*
* @param string $option
* @return array
*/
public function get_option($option) {
$val = get_site_option($option);
// On multisite, migrate options into the site options
if (false === $val && is_multisite()) {
$blog_id = get_current_blog_id();
if ($blog_id>1) {
$val = get_option($option);
if (false !== $val) {
delete_option($option);
update_site_option($option, $val);
return $val;
}
}
// $val is still false
switch_to_blog(1);
$val = get_option($option);
if (false !== $val) {
delete_option($option);
update_site_option($option, $val);
}
restore_current_blog();
}
return $val;
}
/**
* Remove and update a site option
*
* @param String $option
* @param Mixed $val
*
* @return Boolean - true if the option was successfully set; otherwise false
*/
public function update_option($option, $val) {
delete_site_option($option);
return update_site_option($option, $val);
}
public function deinstall_udaddons() {
$del = '<a href="' . wp_nonce_url('plugins.php?action=delete-selected&checked[]=updraftplus-addons/updraftplus-addons.php&plugin_status=all&paged=1&s=', 'bulk-plugins') . '" title="' . esc_attr__('Delete plugin') . '" class="delete">' . 'delete the UpdraftPlus Addons Manager plugin' . '</a>';
$this->show_admin_warning('You can '.$del.' - it is obsolete (all of its functions, including your add-ons, are now included in the main UpdraftPlus plugin obtained from updraftplus.com).');
}
public function show_admin_warning($message, $class = "updated") {
echo '<div class="updraftmessage '.esc_attr($class).'"><p>'.$message.'</p></div>';// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- needs to be presented in html
}
/**
* Remove any existing updates detected
*
* @param array $updates
* @return array
*/
public function site_transient_update_plugins($updates) {
if (!is_object($updates) || empty($this->plugin_file)) return $updates;
if (isset($updates, $updates->response, $updates->response[$this->plugin_file]))
unset($updates->response[$this->plugin_file]);
return $updates;
}
/**
* We want to lessen the number of automatic checks if an update is already known to be available
*
* @param string $shouldcheck Should check if there are updates
* @param string $lastcheck Last update check
* @param string $checkperiod Period to check
* @return boolean
*/
public function puc_check_now($shouldcheck, $lastcheck, $checkperiod) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use
// Skip checks immediately after a WP upgrade. This action has existed since WP 4.4. Since we're just trying to reduce server load spikes when WP core automatic security upgrades happen, that is adequate.
if (did_action('pre_auto_update')) return false;
global $wp_current_filter;
/*
* Return without any changes if no check was going to happen anyway; or, if it was, if any of these conditions match:
* - We have no checker object
* - There hasn't been a last check
* - We're on the update-core.php page
* - We're not in a cron context
*/
if (true !== $shouldcheck || empty($this->plug_updatechecker) || 0 == $lastcheck || (is_array($wp_current_filter) && in_array('load-update-core.php', $wp_current_filter)) || !defined('DOING_CRON')) return $shouldcheck;
// i.e. return unchanged result (i.e. do check if you were going to) if no update is available
if (null === $this->plug_updatechecker->getUpdate()) return $shouldcheck;
// So, now we know an update is available. In this situation, we reduce checks.
$days_since_check = max(round((time() - $lastcheck)/86400), 1);
if ($days_since_check > 10000) return true;
// Suppress checks on days 2, 4, 5, 7 and then every day except multiples of 7.
if (2 == $days_since_check || 4 == $days_since_check || 5 == $days_since_check || 7 == $days_since_check || ($days_since_check >= 7 && 0 != $days_since_check % 7)) return false;
return true;
}
/**
* Send a licence claim to the licensing server
*
* @param String $key - the add-on being claimed
*
* @return WP_Error|Array - body as returned by wp_remote_post(), after JSON-decoding
*/
private function claim_addon($key) {
$options = $this->get_option(UDADDONS2_SLUG.'_options');
$post_array = array(
'e' => $options['email'],
'sid' => $this->siteid(),
'sn' => base64_encode(get_bloginfo('name')),
'su' => base64_encode(home_url()),
'key' => $key,
'si2' => json_encode($this->get_site_info()),
'response_format' => 'json'
);
$result = wp_remote_post($this->url.'/plugin-info/?udm_action=claimaddon',
array(
'timeout' => 15,
'body' => $post_array
)
);
if (!is_wp_error($result) && isset($result['body']) && null !== ($response = json_decode($result['body'])) && is_object($response) && !empty($response->code) && 'OK' === $response->code) {
// Remove the password from the stored settings, for security. We have confirmed that we can authenticate via token (SID) - which is valid for approx. a week (the user can re-enter the password if he needs to)
unset($options['password']);
$this->update_option(UDADDONS2_SLUG.'_options', $options);
} else {
// Try again with password (old-style protocol)
// The 'password' encoded here is the updraftplus.com password. See here: https://updraftplus.com/faqs/tell-me-about-my-updraftplus-com-account/
if (!empty($options['password'])) {
$post_array['p'] = base64_encode($options['password']);
$result = wp_remote_post($this->url.'/plugin-info/?udm_action=claimaddon',
array(
'timeout' => 15,
'body' => $post_array
)
);
if (!is_wp_error($result) && isset($result['body'])) $response = json_decode($result['body']);
}
}
return $response;
}
/**
* Called by the WP action wp_ajax_udaddons_claimaddon
*/
public function ajax_udaddons_claimaddon() {
$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
if (!wp_verify_nonce($nonce, 'udmanager-nonce') || empty($_POST['key'])) die('Security check');
$key = $_POST['key'];
$result = $this->claim_addon($key);
if (is_wp_error($result)) {
echo esc_html__('Errors occurred:', 'updraftplus').'<br>';
show_message($result);
} elseif (is_object($result)) {
if ((!defined('UPDRAFTPLUS_ADDONS_QUICK_INSTALL') || !UPDRAFTPLUS_ADDONS_QUICK_INSTALL) && !empty($result->data->files)) {
$all_addons = array();
if (is_dir(UPDRAFTPLUS_DIR.'/addons') && $dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) {
while (false !== ($e = readdir($dir_handle))) {
if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e) && preg_match('/\.php$/', $e)) {
$all_addons[] = $e;
}
}
}
foreach ($result->data->files as $file => $data) {
if (false !== strpos($file, '..')) continue;
if (preg_match('#^addons/(.*\.php)$#', $file, $matches)) {
$all_addons[] = $matches[1];
}
}
$all_addons = array_unique($all_addons);
$new_sub_version = ('all' == $key || count($all_addons) > 15 ) ? count($all_addons) + 1 : count($all_addons);
foreach ($result->data->files as $file => $data) {
if (false !== strpos($file, '..')) continue;
if ('updraftplus.php' != $file && !preg_match('#^addons/.*\.php$#', $file)) continue;
if (false == ($decompressed = gzuncompress(base64_decode($data)))) continue;
// Check the basic sanity of the incoming data
$start = substr($decompressed, 0, 128);
if ('updraftplus.php' == $file && false === strpos($start, 'Plugin Name: UpdraftPlus')) continue;
if (preg_match('#^addons/.*\.php$#', $file) && false === strpos($start, 'UpdraftPlus Addon:')) continue;
if ('updraftplus.php' == $file) $decompressed = preg_replace('/\nVersion: \d+\.(\d+\.\d+)/', "\n".'Version: 2.$1.'.$new_sub_version, $decompressed, 1);
if (false == file_put_contents(UPDRAFTPLUS_DIR.'/'.$file, $decompressed)) {
$addons_written = false;
break;
}
$addons_written = true;
}
}
$result->check_updates = true;
if (!empty($addons_written)) $result->addons_written = true;
// If the results included update information, then store that
if (!empty($result->data) && !empty($result->data->plugin_info) && !empty($this->plug_updatechecker)) {
// e.g. Puc_v4p9_Plugin_UpdateChecker
$checker_class = get_class($this->plug_updatechecker);
// Hopefully take off the 'Checker'. The setUpdate() call below wants a compatible version.
$plugin_update_class = substr($checker_class, 0, strlen($checker_class)-7);
if (class_exists($plugin_update_class) && is_callable(array($plugin_update_class, 'fromObject'))) {
// $plugin_update_class::fromObject() is invalid syntax on PHP 5.2
$plugin_update = call_user_func(array($plugin_update_class, 'fromObject'), $result->data->plugin_info);
$update_checker = $this->plug_updatechecker;
$installed_version = $update_checker->getInstalledVersion();
if (null !== $installed_version) {
$state = $update_checker->getUpdateState();
$state->setLastCheckToNow()->setCheckedVersion($installed_version);
$state->setUpdate($plugin_update);
$state->save();
$result->check_updates = false;
}
}
}
// Removed only because it is unnecessary
unset($result->data);
echo json_encode($result);
} else {
echo esc_html(__('Errors occurred:', 'updraftplus').' '.serialize($result));
}
die;
}
/**
* Return a reasonably unique ID for the site
*
* @return String
*/
public function siteid() {
$sid = get_site_option(UDADDONS2_SLUG.'_siteid');
if (!is_string($sid) || empty($sid)) {
$sid = md5(rand().microtime(true).home_url());
update_site_option(UDADDONS2_SLUG.'_siteid', $sid);
}
return $sid;
}
/**
* Return site information
*
* @return Array - which can be json_encode()-ed.
*/
private function get_site_info() {
global $wp_version, $updraftplus;
include(ABSPATH.WPINC.'/version.php');
$site_info = array(
'wp' => $wp_version,
'php' => PHP_VERSION,
'multi' => is_multisite() ? 1 : 0,
'mem' => ini_get('memory_limit'),
'lang' => (string) get_locale()
);
// Since 3.7.0
if (function_exists('wp_get_installed_translations')) {
$all_translations = wp_get_installed_translations('plugins');
if (isset($all_translations[$this->slug])) {
foreach ($all_translations[$this->slug] as $locale => $tinfo) {
$found_existing = false;
if (isset($tinfo['PO-Revision-Date'])) {
$found_existing = strtotime($tinfo['PO-Revision-Date']);
}
$site_info['trans'][$locale]['d'] = $found_existing;
}
}
}
if (is_a($updraftplus, 'UpdraftPlus') && isset($updraftplus->version)) {
$site_info['ud'] = $updraftplus->version;
if (class_exists('UpdraftPlus_Options')) $site_info['service'] = json_encode(UpdraftPlus_Options::get_updraft_option('updraft_service'));
}
return $site_info;
}
/**
* Modifies the query arguments on the updates request
*
* @param Array $args - unfiltered arguments
*
* @return Array - filtered arguments
*/
public function updater_queryargs_plugin($args) {
if (!is_array($args)) return $args;
$options = $this->get_option(UDADDONS2_SLUG.'_options');
$email = isset($options['email']) ? $options['email'] : '';
$args['udm_action'] = 'updateinfo';
$args['sid'] = $this->siteid();
$args['su'] = urlencode(base64_encode(home_url()));
$args['sn'] = urlencode(base64_encode(get_bloginfo('name')));
$args['slug'] = urlencode($this->slug);
$args['e'] = urlencode($email);
if (defined('UPDRAFTPLUS_ADDONS_SSL') && UPDRAFTPLUS_ADDONS_SSL == true) $args['ssl'] = 1;
// Some information on the server calling. This can be used - e.g. if they have an old version of PHP/WordPress, then this may affect what update version they should be offered
$args['si2'] = urlencode(base64_encode(json_encode($this->get_site_info())));
// Unset a couple of values set by version 4.0+ of plugin-updates-checker
unset($args['php']);
unset($args['locale']);
return $args;
}
/**
* This function, if ever changed, should be kept in sync with the same function in udmanager.php
*
* @param String $file
* @return Boolean|Array
*/
private function get_addon_info($file) {
if ($f = fopen($file, 'r')) {
$key = "";
$name = "";
$description = "";
$version = "";
$shopurl = "";
$latestchange = null;
$lines_read = 0;
while ($lines_read<10 && $line = @fgets($f)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
if ("" == $key && preg_match('/Addon: ([^:]+):(.*)$/i', $line, $lmatch)) {
$key = $lmatch[1];
$name = $lmatch[2];
} elseif ("" == $description && preg_match('/Description: (.*)$/i', $line, $lmatch)) {
$description = $lmatch[1];
} elseif ("" == $version && preg_match('/Version: (.*)$/i', $line, $lmatch)) {
$version = $lmatch[1];
} elseif ("" == $shopurl && preg_match('/Shop: (.*)$/i', $line, $lmatch)) {
$shopurl = $lmatch[1];
} elseif ("" == $latestchange && preg_match('/Latest Change: (.*)$/i', $line, $lmatch)) {
$latestchange = $lmatch[1];
}
$lines_read++;
}
fclose($f);
if ($key && $name && $description && $version) {
return array('key' => $key, 'name' => $name, 'description' => $description, 'installedversion' => $version, 'shopurl' => $shopurl, 'latestchange' => $latestchange);
}
}
return false;
}
private function get_default_addons() {
return array(
'noadverts' => array(
'name' => 'Remove adverts',
'description' => 'Removes all adverts from the control panel and emails',
'shopurl' => '/shop/updraftplus-premium/'
),
'all' => array(
'name' => 'All addons',
'description' => 'Access to all UpdraftPlus add-ons',
'shopurl' => '/shop/updraftplus-premium/'
),
'multisite' => array(
'name' => 'WordPress Network (multisite) support',
'description' => 'Adds support for WordPress Network (multisite) installations, allowing secure backup by the super-admin only',
'shopurl' => '/shop/updraftplus-premium/'
),
'fixtime' => array(
'name' => 'Fix Time',
'description' => 'Allows you to specify the exact time at which backups will run',
'shopurl' => '/shop/updraftplus-premium/'
),
'morefiles' => array(
'name' => 'More Files',
'description' => 'Allows you to backup WordPress core, and other files in your web space',
'shopurl' => '/shop/updraftplus-premium/'
),
'sftp' => array(
'name' => 'SFTP and FTPS and SCP',
'description' => 'Allows SFTP and SCP as a cloud backup method, and encrypted FTP',
'shopurl' => '/shop/updraftplus-premium/'
),
'dropbox-folders' => array(
'name' => 'Dropbox Folders',
'description' => 'Allows you to organise your backups into Dropbox sub-folders',
'shopurl' => '/shop/updraftplus-premium/'
),
'morestorage' => array(
'name' => 'Multiple storage destinations',
'description' => 'Allows you to send a single backup to multiple destinations (e.g. Dropbox and Google Drive and Amazon)',
'shopurl' => '/shop/updraftplus-premium/'
),
'webdav' => array(
'name' => 'WebDAV support',
'description' => 'Allows you to use the WebDAV and encrypted WebDAV protocols for remote backups',
'shopurl' => '/shop/updraftplus-premium/'
)
);
}
private function get_local_addons($long_form = true) {
$plugin_dir = WP_PLUGIN_DIR.'/'.$this->slug;
if (!is_dir($plugin_dir.'/addons')) return array();
$local_addons = array();
if ($dir_handle = @opendir($plugin_dir.'/addons')) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
while (false !== ($e = readdir($dir_handle))) {
if (is_file($plugin_dir.'/addons/'.$e) && preg_match('/^(.*)\.php$/i', $e, $matches)) {
$addon = $this->get_addon_info($plugin_dir.'/addons/'.$e);
if (is_array($addon)) {
$key = $addon['key'];
if ($long_form) {
$local_addons[$key] = $addon;
} else {
$local_addons[] = $key;
}
}
}
}
}
return $local_addons;
}
/**
* Get a list of available add-ons
*
* @return Array
*/
public function get_available_addons() {
// Cached in this object?
if (is_array($this->available_addons)) return $this->available_addons;
// The remote list may be cached in a site transient
$potential_array = get_site_transient('upaddons_remote');
if (false == $this->debug && is_array($potential_array) && isset($potential_array['all'])) {
$this->remote_addons = $potential_array;
$remote_addons = $potential_array;
} else {
$url = $this->url.'/plugin-info/?udm_action=listaddons&response_format=json';
$result = wp_remote_get($url, array('timeout' => 15));
if (!is_wp_error($result) & false !== $result) {
$response = json_decode($result['body'], true);
$remote_addons = array();
if (is_array($response) && isset($response['addons'])) {
$addons = $response['addons'];
// One more sanity check
if (isset($addons['all'])) {
$remote_addons = $addons;
// Cache it
$this->remote_addons = $addons;
set_site_transient('upaddons_remote', $addons, 14400);
}
}
} else {
// Populate with default
$remote_addons = $this->get_default_addons();
}
}
// Perhaps we have some installed that have been obsoleted/removed upstream
$installed_addons = $this->get_local_addons();
$all_addons = array();
foreach ($remote_addons as $key => $addon) {
// Can then get over-ridden
$addon['installed'] = false;
$all_addons[$key] = $addon;
}
foreach ($installed_addons as $key => $addon) {
if (isset($all_addons[$key])) {
// The remote info over-writes all else
$newaddon = $all_addons[$key];
$newaddon['installed'] = true;
$newaddon['installedversion'] = $addon['installedversion'];
} else {
$newaddon = $addon;
if (!empty($newaddon['installedversion'])) $newaddon['installed'] = true;
}
$all_addons[$key] = $newaddon;
}
$this->available_addons = $all_addons;
return $all_addons;
}
/**
* This function will get the connection status
*
* @return boolean|WP_Error - returns true if successful
*/
public function connection_status() {
$options = $this->get_option(UDADDONS2_SLUG.'_options');
$try_siteid = false;
$oval = is_object($this->plug_updatechecker) ? get_site_option($this->plug_updatechecker->optionName, null) : null;
// Detect the case where the password has been removed
if (is_object($oval) && !empty($oval->lastCheck) && time()-$oval->lastCheck < 86400*8) {
$try_siteid = $this->siteid();
}
// Username and password set up?
if (empty($options['email']) || (empty($options['password']) && false == $try_siteid)) return new WP_Error('blank_details', __('You need to supply both an email address and a password', 'updraftplus'));
// Hash will change if the account changes (password change is handled by the options filter)
$ehash = substr(md5($options['email']), 0, 23);
$trans = get_site_transient('udaddons_connect_'.$ehash);
// In debug mode, we don't cache
if (true !== $this->debug && empty($_GET['udm_refresh']) && is_array($trans)) {
if (isset($trans['myaddons']) && is_array($trans['myaddons'])) {
$this->user_addons = $trans['myaddons'];
}
if (isset($trans['availableaddons']) && is_array($trans['availableaddons'])) {
$this->remote_addons = $trans['availableaddons'];
}
if (isset($trans['support']) && is_array($trans['support'])) {
$this->user_support = $trans['support'];
}
return true;
}
$password = isset($options['password']) ? $options['password'] : '';
$connect = $this->connect($options['email'], $password);
if (is_wp_error($connect)) return $connect;
if (false === $connect) return new WP_Error('failed_connection', __('We failed to successfully connect to UpdraftPlus.Com', 'updraftplus'));
if (!is_bool($connect)) return new WP_Error('bad_response', __('UpdraftPlus.Com responded, but we did not understand the response', 'updraftplus'));
return true;
}
/**
* Returns either true (in which case the add-ons array is populated), or a WP_Error
*
* @param string $email Email for the remote post array
* @param string $password Password for the remote post array
* @return boolean
*/
private function connect($email, $password) {
global $updraftplus;
// Use previous response, if available
if (is_array($this->user_addons) && count($this->user_addons) > 0) return true;
$url = $this->url.'/plugin-info/?udm_action=connect';
// The 'password' encoded here is the updraftplus.com password. See here: https://updraftplus.com/faqs/tell-me-about-my-updraftplus-com-account/. Note - it may be empty if the user removed their password.
$result = wp_remote_post($url,
array(
'timeout' => 15,
'body' => array(
'e' => $email,
'p' => base64_encode($password),
'sid' => $this->siteid(),
'sn' => base64_encode(get_bloginfo('name')),
'su' => base64_encode(home_url()),
'f' => 2
)
)
);
if (is_wp_error($result) || false === $result) return $result;
$code = wp_remote_retrieve_response_code($result);
if ($code >= 400) {
$message = __('An error response was received; HTTP code:', 'updraftplus').' '.$code;
$headers = wp_remote_retrieve_headers($result);
if (!empty($headers['cf-ray'])) {
$message .= ' CF-Ray: '.$headers['cf-ray'];
}
if (403 == $code) {
$ip_addr = $updraftplus->get_outgoing_ip_address(true);
if (false !== $ip_addr && false !== filter_var($ip_addr, FILTER_VALIDATE_IP)) {
$message .= ' IP: '.htmlspecialchars($ip_addr);
$message .= '<br>'.__('This most likely means that you share a webserver with a hacked website that has been used in previous attacks.', 'updraftplus').'<br> <a href="https://updraftplus.com/unblock-ip-address/" target="_blank">'.__('To remove any block, please go here.', 'updraftplus').'</a> '.__('Your IP address:', 'updraftplus').' '.htmlspecialchars($ip_addr);
}
}
return new WP_Error('error_response', $message, $result);
}
$response = json_decode($result['body'], true);
if (!is_array($response) || !isset($response['updraftpluscom']) || !isset($response['loggedin'])) {
$ser_resp = htmlspecialchars(serialize($response));
if (preg_match('/has banned your IP address \(([\.:0-9a-f]+)\)/', $response, $matches)) {
return new WP_Error('banned_ip', sprintf(__("UpdraftPlus.com has responded with 'Access Denied'.", 'updraftplus').'<br>'.__("It appears that your web server's IP Address (%s) is blocked.", 'updraftplus').' '.__('This most likely means that you share a webserver with a hacked website that has been used in previous attacks.', 'updraftplus').'<br> <a href="https://updraftplus.com/unblock-ip-address/" target="_blank">'.__('To remove any block, please go here.', 'updraftplus').'</a> ', $matches[1]));
} else {
if (null === $ser_resp) {
return new WP_Error('unknown_response', __('No response data was received.', 'updraftplus').' '.__('This usually indicates a network connectivity issue (e.g. an outgoing firewall or overloaded network) between this site and UpdraftPlus.com.', 'updraftplus'));
} else {
return new WP_Error('unknown_response', sprintf(__('UpdraftPlus.Com returned a response which we could not understand (data: %s)', 'updraftplus'), $ser_resp));
}
}
}
switch ($response['loggedin']) {
case 'connected':
if (isset($response['myaddons']) && is_array($response['myaddons'])) {
$this->user_addons = $response['myaddons'];
}
if (isset($response['availableaddons']) && is_array($response['availableaddons'])) {
$this->remote_addons = $response['availableaddons'];
set_site_transient('upaddons_remote', $this->remote_addons, 14400);
}
if (isset($response['support']) && is_array($response['support'])) {
$this->user_support = $response['support'];
}
$ehash = substr(md5($email), 0, 23);
set_site_transient('udaddons_connect_'.$ehash, $response, 7200);
// Now, trigger an update check, since things may have changed
if ($this->plug_updatechecker) $this->plug_updatechecker->checkForUpdates();
break;
case 'authfailed':
$ehash = substr(md5($email), 0, 23);
delete_site_transient('udaddons_connect_'.$ehash);
if (!empty($response['authproblem'])) {
if ('invalidpassword' == $response['authproblem']) {
$authfail_error = new WP_Error('authfailed', __('Your email address was valid, but your password was not recognised by UpdraftPlus.Com.', 'updraftplus').'<a href="?page=updraftplus&tab=addons"> '.__('Go here to re-enter your password.', 'updraftplus').'</a><br>');
$authfail_error->add('authfailed', __('If you have forgotten your password ', 'updraftplus').' <a href="'.$updraftplus->get_url('lost-password').'">'.__('go here to change your password on updraftplus.com.', 'updraftplus').'</a>');
return $authfail_error;
} elseif ('invaliduser' == $response['authproblem']) {
return new WP_Error('authfailed', __('You entered an email address that was not recognised by UpdraftPlus.Com', 'updraftplus'));
}
}
return new WP_Error('authfailed', __('Your email address and password were not recognised by UpdraftPlus.Com', 'updraftplus'));
break;
default:
return new WP_Error('unknown_response', __('UpdraftPlus.Com returned a response, but we could not understand it', 'updraftplus'));
break;
}
return true;
}
}