diff --git a/wordpress/wp-content/plugins/updraftplus/admin.php b/wordpress/wp-content/plugins/updraftplus/admin.php deleted file mode 100644 index 9a88aaa445c8914255cdafa870c48d4cfedbf50d..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/admin.php +++ /dev/null @@ -1,6164 +0,0 @@ - array(), 'onedrive' => array(), 'googledrive' => array(), 'googlecloud' => array()); - - private $php_versions = array('5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'); - - private $storage_service_without_settings; - - private $storage_service_with_partial_settings; - - /** - * Constructor - */ - public function __construct() { - $this->admin_init(); - } - - /** - * Get the path to the UI templates directory - * - * @return String - a filesystem directory path - */ - public function get_templates_dir() { - return apply_filters('updraftplus_templates_dir', UpdraftPlus_Manipulation_Functions::wp_normalize_path(UPDRAFTPLUS_DIR.'/templates')); - } - - private function register_template_directories() { - - $template_directories = array(); - - $templates_dir = $this->get_templates_dir(); - - if ($dh = opendir($templates_dir)) { - while (($file = readdir($dh)) !== false) { - if ('.' == $file || '..' == $file) continue; - if (is_dir($templates_dir.'/'.$file)) { - $template_directories[$file] = $templates_dir.'/'.$file; - } - } - closedir($dh); - } - - // This is the optimal hook for most extensions to hook into - $this->template_directories = apply_filters('updraftplus_template_directories', $template_directories); - - } - - /** - * Output, or return, the results of running a template (from the 'templates' directory, unless a filter over-rides it). Templates are run with $updraftplus, $updraftplus_admin and $wpdb set. - * - * @param String $path - path to the template - * @param Boolean $return_instead_of_echo - by default, the template is echo-ed; set this to instead return it - * @param Array $extract_these - variables to inject into the template's run context - * - * @return Void|String - */ - public function include_template($path, $return_instead_of_echo = false, $extract_these = array()) { - if ($return_instead_of_echo) ob_start(); - - if (preg_match('#^([^/]+)/(.*)$#', $path, $matches)) { - $prefix = $matches[1]; - $suffix = $matches[2]; - if (isset($this->template_directories[$prefix])) { - $template_file = $this->template_directories[$prefix].'/'.$suffix; - } - } - - if (!isset($template_file)) $template_file = UPDRAFTPLUS_DIR.'/templates/'.$path; - - $template_file = apply_filters('updraftplus_template', $template_file, $path); - - do_action('updraftplus_before_template', $path, $template_file, $return_instead_of_echo, $extract_these); - - if (!file_exists($template_file)) { - error_log("UpdraftPlus: template not found: $template_file"); - echo __('Error:', 'updraftplus').' '.__('template not found', 'updraftplus')." ($path)"; - } else { - extract($extract_these); - global $updraftplus, $wpdb;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $updraftplus_admin = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - include $template_file; - } - - do_action('updraftplus_after_template', $path, $template_file, $return_instead_of_echo, $extract_these); - - if ($return_instead_of_echo) return ob_get_clean(); - } - - /** - * Add actions for any needed dashboard notices for remote storage services - * - * @param String|Array $services - a list of services, or single service - */ - private function setup_all_admin_notices_global($services) { - - global $updraftplus; - - if ('googledrive' === $services || (is_array($services) && in_array('googledrive', $services))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('googledrive'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "Google Drive (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $instance_id => $storage_options) { - if ((defined('UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP') && UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP) || !empty($storage_options['clientid'])) { - if (!empty($storage_options['clientid'])) { - $clientid = $storage_options['clientid']; - $token = empty($storage_options['token']) ? '' : $storage_options['token']; - } - if (!empty($clientid) && '' == $token) { - if (!in_array($instance_id, $this->auth_instance_ids['googledrive'])) $this->auth_instance_ids['googledrive'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googledrive')); - } - unset($clientid); - unset($token); - } else { - if (empty($storage_options['user_id'])) { - if (!in_array($instance_id, $this->auth_instance_ids['googledrive'])) $this->auth_instance_ids['googledrive'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googledrive')); - } - } - } - } - } - if ('googlecloud' === $services || (is_array($services) && in_array('googlecloud', $services))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('googlecloud'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "Google Cloud (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $instance_id => $storage_options) { - $clientid = $storage_options['clientid']; - $token = (empty($storage_options['token'])) ? '' : $storage_options['token']; - - if (!empty($clientid) && empty($token)) { - if (!in_array($instance_id, $this->auth_instance_ids['googlecloud'])) $this->auth_instance_ids['googlecloud'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud')); - } - } - } - } - - if ('dropbox' === $services || (is_array($services) && in_array('dropbox', $services))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('dropbox'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "Dropbox (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $instance_id => $storage_options) { - if (empty($storage_options['tk_access_token'])) { - if (!in_array($instance_id, $this->auth_instance_ids['dropbox'])) $this->auth_instance_ids['dropbox'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_dropbox'))) add_action('all_admin_notices', array($this, 'show_admin_warning_dropbox')); - } - } - } - } - - if ('onedrive' === $services || (is_array($services) && in_array('onedrive', $services))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('onedrive'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "OneDrive (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $instance_id => $storage_options) { - if ((defined('UPDRAFTPLUS_CUSTOM_ONEDRIVE_APP') && UPDRAFTPLUS_CUSTOM_ONEDRIVE_APP)) { - if (!empty($storage_options['clientid']) && !empty($storage_options['secret']) && empty($storage_options['refresh_token'])) { - if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive')); - } elseif (empty($storage_options['refresh_token'])) { - if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive')); - } - } else { - if (empty($storage_options['refresh_token'])) { - if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id; - if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive')); - } - } - } - } - } - - if ('updraftvault' === $services || (is_array($services) && in_array('updraftvault', $services))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "UpdraftVault (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $instance_id => $storage_options) { - if (empty($storage_options['token']) && empty($storage_options['email'])) { - add_action('all_admin_notices', array($this, 'show_admin_warning_updraftvault')); - } - } - } - } - - if ($this->disk_space_check(1048576*35) === false) add_action('all_admin_notices', array($this, 'show_admin_warning_diskspace')); - - $all_services = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids($updraftplus->get_canonical_service_list()); - - $this->storage_service_without_settings = array(); - $this->storage_service_with_partial_settings = array(); - - foreach ($all_services as $method => $sinfo) { - if (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) continue; - foreach ($sinfo['instance_settings'] as $opt) { - if (!$sinfo['object']->options_exist($opt)) { - if (isset($opt['CSRF'])) { - $this->storage_service_with_partial_settings[$method] = $updraftplus->backup_methods[$method]; - } else { - $this->storage_service_without_settings[] = $updraftplus->backup_methods[$method]; - } - } - } - } - - if (!empty($this->storage_service_with_partial_settings)) { - add_action('all_admin_notices', array($this, 'show_admin_warning_if_remote_storage_with_partial_setttings')); - } - - if (!empty($this->storage_service_without_settings)) { - add_action('all_admin_notices', array($this, 'show_admin_warning_if_remote_storage_settting_are_empty')); - } - - if ($updraftplus->is_restricted_hosting('only_one_backup_per_month')) { - add_action('all_admin_notices', array($this, 'show_admin_warning_one_backup_per_month')); - } - } - - private function setup_all_admin_notices_udonly($service, $override = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use - global $updraftplus; - - if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { - @ini_set('display_errors', 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (defined('E_DEPRECATED')) { - @error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, PHPCompatibility.Constants.NewConstants.e_deprecatedFound -- Ok to ignore - } else { - @error_reporting(E_ALL & ~E_NOTICE);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - add_action('all_admin_notices', array($this, 'show_admin_debug_warning')); - } - - if (null === UpdraftPlus_Options::get_updraft_option('updraft_interval')) { - add_action('all_admin_notices', array($this, 'show_admin_nosettings_warning')); - $this->no_settings_warning = true; - } - - // Avoid false positives, by attempting to raise the limit (as happens when we actually do a backup) - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $max_execution_time = (int) @ini_get('max_execution_time');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if ($max_execution_time>0 && $max_execution_time<20) { - add_action('all_admin_notices', array($this, 'show_admin_warning_execution_time')); - } - - // LiteSpeed has a generic problem with terminating cron jobs - if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false) { - if (!is_file(ABSPATH.'.htaccess') || !preg_match('/noabort/i', file_get_contents(ABSPATH.'.htaccess'))) { - add_action('all_admin_notices', array($this, 'show_admin_warning_litespeed')); - } - } - - if (version_compare($updraftplus->get_wordpress_version(), '3.2', '<')) add_action('all_admin_notices', array($this, 'show_admin_warning_wordpressversion')); - - // DreamObjects west cluster shutdown warning - if ('dreamobjects' === $service || (is_array($service) && in_array('dreamobjects', $service))) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('dreamobjects'); - - if (is_wp_error($settings)) { - if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = ''; - $this->storage_module_option_errors .= "DreamObjects (".$settings->get_error_code()."): ".$settings->get_error_message(); - add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options')); - $updraftplus->log_wp_error($settings, true, true); - } elseif (!empty($settings['settings'])) { - foreach ($settings['settings'] as $storage_options) { - if ('objects-us-west-1.dream.io' == $storage_options['endpoint']) { - add_action('all_admin_notices', array($this, 'show_admin_warning_dreamobjects')); - } - } - } - } - - // If the plugin was not able to connect to a UDC account due to lack of licences - if (isset($_GET['udc_connect']) && 0 == $_GET['udc_connect']) { - add_action('all_admin_notices', array($this, 'show_admin_warning_udc_couldnt_connect')); - } - } - - /** - * Used to output the information for the next scheduled backup. - * moved to function for the ajax saves - */ - public function next_scheduled_backups_output() { - // UNIX timestamp - $next_scheduled_backup = wp_next_scheduled('updraft_backup'); - if ($next_scheduled_backup) { - // Convert to GMT - $next_scheduled_backup_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup); - // Convert to blog time zone - $next_scheduled_backup = get_date_from_gmt($next_scheduled_backup_gmt, 'D, F j, Y H:i'); - // $next_scheduled_backup = date_i18n('D, F j, Y H:i', $next_scheduled_backup); - } else { - $next_scheduled_backup = __('Nothing currently scheduled', 'updraftplus'); - $files_not_scheduled = true; - } - - $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database'); - if (UpdraftPlus_Options::get_updraft_option('updraft_interval_database', UpdraftPlus_Options::get_updraft_option('updraft_interval')) == UpdraftPlus_Options::get_updraft_option('updraft_interval')) { - if (isset($files_not_scheduled)) { - $next_scheduled_backup_database = $next_scheduled_backup; - $database_not_scheduled = true; - } else { - $next_scheduled_backup_database = __("At the same time as the files backup", 'updraftplus'); - $next_scheduled_backup_database_same_time = true; - } - } else { - if ($next_scheduled_backup_database) { - // Convert to GMT - $next_scheduled_backup_database_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup_database); - // Convert to blog time zone - $next_scheduled_backup_database = get_date_from_gmt($next_scheduled_backup_database_gmt, 'D, F j, Y H:i'); - // $next_scheduled_backup_database = date_i18n('D, F j, Y H:i', $next_scheduled_backup_database); - } else { - $next_scheduled_backup_database = __('Nothing currently scheduled', 'updraftplus'); - $database_not_scheduled = true; - } - } - - if (isset($files_not_scheduled) && isset($database_not_scheduled)) { - ?> - - - : - - - - - : - - - - '.$next_scheduled_backup.''; - } else { - echo ''.$next_scheduled_backup.''; - } - - if ($return_instead_of_echo) return ob_get_clean(); - } - - /** - * Used to output the information for the next scheduled database backup. - * moved to function for the ajax saves - * - * @param Boolean $return_instead_of_echo Whether to return or echo the results. N.B. More than just the results to echo will be returned - * @return Void|String If $return_instead_of_echo parameter is true, It returns html string - */ - public function next_scheduled_database_backups_output($return_instead_of_echo = false) { - if ($return_instead_of_echo) ob_start(); - - $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database'); - if ($next_scheduled_backup_database) { - // Convert to GMT - $next_scheduled_backup_database_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup_database); - // Convert to blog time zone - $next_scheduled_backup_database = get_date_from_gmt($next_scheduled_backup_database_gmt, 'D, F j, Y H:i'); - $database_not_scheduled = false; - } else { - $next_scheduled_backup_database = __('Nothing currently scheduled', 'updraftplus'); - $database_not_scheduled = true; - } - - if ($database_not_scheduled) { - echo ''.$next_scheduled_backup_database.''; - } else { - echo ''.$next_scheduled_backup_database.''; - } - - if ($return_instead_of_echo) return ob_get_clean(); - } - - /** - * Run upon the WP admin_init action - */ - private function admin_init() { - - add_action('admin_init', array($this, 'maybe_download_backup_from_email')); - - add_action('core_upgrade_preamble', array($this, 'core_upgrade_preamble')); - add_action('admin_action_upgrade-plugin', array($this, 'admin_action_upgrade_pluginortheme')); - add_action('admin_action_upgrade-theme', array($this, 'admin_action_upgrade_pluginortheme')); - - add_action('admin_head', array($this, 'admin_head')); - add_filter((is_multisite() ? 'network_admin_' : '').'plugin_action_links', array($this, 'plugin_action_links'), 10, 2); - add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup')); - add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler')); - add_action('wp_ajax_updraft_ajaxrestore', array($this, 'updraft_ajaxrestore')); - add_action('wp_ajax_nopriv_updraft_ajaxrestore', array($this, 'updraft_ajaxrestore')); - add_action('wp_ajax_updraft_ajaxrestore_continue', array($this, 'updraft_ajaxrestore')); - add_action('wp_ajax_nopriv_updraft_ajaxrestore_continue', array($this, 'updraft_ajaxrestore')); - - add_action('wp_ajax_plupload_action', array($this, 'plupload_action')); - add_action('wp_ajax_plupload_action2', array($this, 'plupload_action2')); - - add_action('wp_before_admin_bar_render', array($this, 'wp_before_admin_bar_render')); - - // Add a new Ajax action for saving settings - add_action('wp_ajax_updraft_savesettings', array($this, 'updraft_ajax_savesettings')); - - // Ajax for settings import and export - add_action('wp_ajax_updraft_importsettings', array($this, 'updraft_ajax_importsettings')); - - add_filter('heartbeat_received', array($this, 'process_status_in_heartbeat'), 10, 2); - - // UpdraftPlus templates - $this->register_template_directories(); - - global $updraftplus, $pagenow; - add_filter('updraftplus_dirlist_others', array($updraftplus, 'backup_others_dirlist')); - add_filter('updraftplus_dirlist_uploads', array($updraftplus, 'backup_uploads_dirlist')); - - // First, the checks that are on all (admin) pages: - - $service = UpdraftPlus_Options::get_updraft_option('updraft_service'); - - if (UpdraftPlus_Options::user_can_manage()) { - - $this->print_restore_in_progress_box_if_needed(); - - // Main dashboard page advert - // Since our nonce is printed, make sure they have sufficient credentials - if ('index.php' == $pagenow && current_user_can('update_plugins') && (!file_exists(UPDRAFTPLUS_DIR.'/udaddons') || (defined('UPDRAFTPLUS_FORCE_DASHNOTICE') && UPDRAFTPLUS_FORCE_DASHNOTICE))) { - - $dismissed_until = UpdraftPlus_Options::get_updraft_option('updraftplus_dismisseddashnotice', 0); - - $backup_dir = $updraftplus->backups_dir_location(); - // N.B. Not an exact proxy for the installed time; they may have tweaked the expert option to move the directory - $installed = @filemtime($backup_dir.'/index.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $installed_for = time() - $installed; - - if (($installed && time() > $dismissed_until && $installed_for > 28*86400 && !defined('UPDRAFTPLUS_NOADS_B')) || (defined('UPDRAFTPLUS_FORCE_DASHNOTICE') && UPDRAFTPLUS_FORCE_DASHNOTICE)) { - add_action('all_admin_notices', array($this, 'show_admin_notice_upgradead')); - } - } - - // Moved out for use with Ajax saving - $this->setup_all_admin_notices_global($service); - } - - if (!class_exists('Updraft_Dashboard_News')) include_once(UPDRAFTPLUS_DIR.'/includes/class-updraft-dashboard-news.php'); - - $news_translations = array( - 'product_title' => 'UpdraftPlus', - 'item_prefix' => __('UpdraftPlus', 'updraftplus'), - 'item_description' => __('UpdraftPlus News', 'updraftplus'), - 'dismiss_tooltip' => __('Dismiss all UpdraftPlus news', 'updraftplus'), - 'dismiss_confirm' => __('Are you sure you want to dismiss all UpdraftPlus news forever?', 'updraftplus'), - ); - - add_filter('woocommerce_in_plugin_update_message', array($this, 'woocommerce_in_plugin_update_message')); - - new Updraft_Dashboard_News('https://feeds.feedburner.com/updraftplus/', 'https://updraftplus.com/news/', $news_translations); - - // New-install admin tour - if ((!defined('UPDRAFTPLUS_ENABLE_TOUR') || UPDRAFTPLUS_ENABLE_TOUR) && (!defined('UPDRAFTPLUS_THIS_IS_CLONE') || !UPDRAFTPLUS_THIS_IS_CLONE)) { - include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-tour.php'); - } - - if ('index.php' == $GLOBALS['pagenow'] && UpdraftPlus_Options::user_can_manage()) { - add_action('admin_print_footer_scripts', array($this, 'admin_index_print_footer_scripts')); - } - - // Next, the actions that only come on the UpdraftPlus page - if (UpdraftPlus_Options::admin_page() != $pagenow || empty($_REQUEST['page']) || 'updraftplus' != $_REQUEST['page']) return; - $this->setup_all_admin_notices_udonly($service); - - global $updraftplus_checkout_embed; - if (!class_exists('Updraft_Checkout_Embed')) include_once UPDRAFTPLUS_DIR.'/includes/checkout-embed/class-udp-checkout-embed.php'; - - // Create an empty list (usefull for testing, thanks to the filter bellow) - $checkout_embed_products = array(); - - // get products from JSON file. - $checkout_embed_product_file = UPDRAFTPLUS_DIR.'/includes/checkout-embed/products.json'; - if (file_exists($checkout_embed_product_file)) { - $checkout_embed_products = json_decode(file_get_contents($checkout_embed_product_file)); - } - - $checkout_embed_products = apply_filters('updraftplus_checkout_embed_products', $checkout_embed_products); - - if (!empty($checkout_embed_products)) { - $updraftplus_checkout_embed = new Updraft_Checkout_Embed( - 'updraftplus', // plugin name - UpdraftPlus_Options::admin_page_url().'?page=updraftplus', // return url - $checkout_embed_products, // products list - UPDRAFTPLUS_URL.'/includes' // base_url - ); - } - - add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'), 99999); - - $udp_saved_version = UpdraftPlus_Options::get_updraft_option('updraftplus_version'); - if (!$udp_saved_version || $udp_saved_version != $updraftplus->version) { - if (!$udp_saved_version) { - // udp was newly installed, or upgraded from an older version - do_action('updraftplus_newly_installed', $updraftplus->version); - } else { - // udp was updated or downgraded - do_action('updraftplus_version_changed', UpdraftPlus_Options::get_updraft_option('updraftplus_version'), $updraftplus->version); - } - UpdraftPlus_Options::update_updraft_option('updraftplus_version', $updraftplus->version); - } - - if (isset($_POST['action']) && 'updraft_wipesettings' == $_POST['action'] && isset($_POST['nonce']) && UpdraftPlus_Options::user_can_manage()) { - if (wp_verify_nonce($_POST['nonce'], 'updraftplus-wipe-setting-nonce')) $this->wipe_settings(); - } - } - - /** - * Runs upon the WP action woocommerce_in_plugin_update_message - * - * @param String $msg - the message that WooCommerce will print - * - * @return String - filtered value - */ - public function woocommerce_in_plugin_update_message($msg) { - if (time() < UpdraftPlus_Options::get_updraft_option('dismissed_clone_wc_notices_until', 0)) return $msg; - return '

'.__('You can test upgrading your site on an instant copy using UpdraftClone credits', 'updraftplus').' - '.__('go here to learn more', 'updraftplus').' - '. __('dismiss notice', 'updraftplus') .'
'.$msg; - } - - /** - * Runs upon the WP action admin_print_footer_scripts if an entitled user is on the main WP dashboard page - */ - public function admin_index_print_footer_scripts() { - if (time() < UpdraftPlus_Options::get_updraft_option('dismissed_clone_php_notices_until', 0)) return; - ?> - - admin_enqueue_scripts(); - ?> - -
-

-		
- -
- -
- -
- -
- -

- - - - render_active_jobs_and_log_table(true, false); ?> - -
- -
- prepare_restore(); - die(); - } - - /** - * Runs upon the WP action wp_before_admin_bar_render - */ - public function wp_before_admin_bar_render() { - global $wp_admin_bar; - - if (!UpdraftPlus_Options::user_can_manage()) return; - - if (defined('UPDRAFTPLUS_ADMINBAR_DISABLE') && UPDRAFTPLUS_ADMINBAR_DISABLE) return; - - if (false == apply_filters('updraftplus_settings_page_render', true)) return; - - $option_location = UpdraftPlus_Options::admin_page_url(); - - $args = array( - 'id' => 'updraft_admin_node', - 'title' => apply_filters('updraftplus_admin_node_title', 'UpdraftPlus') - ); - $wp_admin_bar->add_node($args); - - $args = array( - 'id' => 'updraft_admin_node_status', - 'title' => str_ireplace('Back Up', 'Backup', __('Backup', 'updraftplus')).' / '.__('Restore', 'updraftplus'), - 'parent' => 'updraft_admin_node', - 'href' => $option_location.'?page=updraftplus&tab=backups' - ); - $wp_admin_bar->add_node($args); - - $args = array( - 'id' => 'updraft_admin_node_migrate', - 'title' => __('Migrate / Clone', 'updraftplus'), - 'parent' => 'updraft_admin_node', - 'href' => $option_location.'?page=updraftplus&tab=migrate' - ); - $wp_admin_bar->add_node($args); - - $args = array( - 'id' => 'updraft_admin_node_settings', - 'title' => __('Settings', 'updraftplus'), - 'parent' => 'updraft_admin_node', - 'href' => $option_location.'?page=updraftplus&tab=settings' - ); - $wp_admin_bar->add_node($args); - - $args = array( - 'id' => 'updraft_admin_node_expert_content', - 'title' => __('Advanced Tools', 'updraftplus'), - 'parent' => 'updraft_admin_node', - 'href' => $option_location.'?page=updraftplus&tab=expert' - ); - $wp_admin_bar->add_node($args); - - $args = array( - 'id' => 'updraft_admin_node_addons', - 'title' => __('Extensions', 'updraftplus'), - 'parent' => 'updraft_admin_node', - 'href' => $option_location.'?page=updraftplus&tab=addons' - ); - $wp_admin_bar->add_node($args); - - global $updraftplus; - if (!$updraftplus->have_addons) { - $args = array( - 'id' => 'updraft_admin_node_premium', - 'title' => 'UpdraftPlus Premium', - 'parent' => 'updraft_admin_node', - 'href' => apply_filters('updraftplus_com_link', 'https://updraftplus.com/shop/updraftplus-premium/') - ); - $wp_admin_bar->add_node($args); - } - } - - /** - * Output HTML for a dashboard notice highlighting the benefits of upgrading to Premium - */ - public function show_admin_notice_upgradead() { - $this->include_template('wp-admin/notices/thanks-for-using-main-dash.php'); - } - - /** - * Enqueue sufficient versions of jQuery and our own scripts - */ - private function ensure_sufficient_jquery_and_enqueue() { - global $updraftplus; - - $enqueue_version = $updraftplus->use_unminified_scripts() ? $updraftplus->version.'.'.time() : $updraftplus->version; - $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min'; - $updraft_min_or_not = $updraftplus->get_updraftplus_file_version(); - - - if (version_compare($updraftplus->get_wordpress_version(), '3.3', '<')) { - // Require a newer jQuery (3.2.1 has 1.6.1, so we go for something not too much newer). We use .on() in a way that is incompatible with < 1.7 - wp_deregister_script('jquery'); - $jquery_enqueue_version = $updraftplus->use_unminified_scripts() ? '1.7.2'.'.'.time() : '1.7.2'; - wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery'.$min_or_not.'.js', false, $jquery_enqueue_version, false); - wp_enqueue_script('jquery'); - // No plupload until 3.3 - wp_enqueue_script('updraft-admin-common', UPDRAFTPLUS_URL.'/includes/updraft-admin-common'.$updraft_min_or_not.'.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-core', 'jquery-ui-accordion'), $enqueue_version, true); - } else { - wp_enqueue_script('updraft-admin-common', UPDRAFTPLUS_URL.'/includes/updraft-admin-common'.$updraft_min_or_not.'.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-core', 'jquery-ui-accordion', 'plupload-all'), $enqueue_version); - } - - } - - /** - * This is also called directly from the auto-backup add-on - */ - public function admin_enqueue_scripts() { - - global $updraftplus, $wp_locale, $updraftplus_checkout_embed; - - $enqueue_version = $updraftplus->use_unminified_scripts() ? $updraftplus->version.'.'.time() : $updraftplus->version; - $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min'; - $updraft_min_or_not = $updraftplus->get_updraftplus_file_version(); - - // Defeat other plugins/themes which dump their jQuery UI CSS onto our settings page - wp_deregister_style('jquery-ui'); - $jquery_ui_version = version_compare($updraftplus->get_wordpress_version(), '5.6', '>=') ? '1.12.1' : '1.11.4'; - $jquery_ui_css_enqueue_version = $updraftplus->use_unminified_scripts() ? $jquery_ui_version.'.0'.'.'.time() : $jquery_ui_version.'.0'; - wp_enqueue_style('jquery-ui', UPDRAFTPLUS_URL."/includes/jquery-ui.custom-v$jquery_ui_version$updraft_min_or_not.css", array(), $jquery_ui_css_enqueue_version); - - wp_enqueue_style('updraft-admin-css', UPDRAFTPLUS_URL.'/css/updraftplus-admin'.$updraft_min_or_not.'.css', array(), $enqueue_version); - // add_filter('style_loader_tag', array($this, 'style_loader_tag'), 10, 2); - - $this->ensure_sufficient_jquery_and_enqueue(); - $jquery_blockui_enqueue_version = $updraftplus->use_unminified_scripts() ? '2.71.0'.'.'.time() : '2.71.0'; - wp_enqueue_script('jquery-blockui', UPDRAFTPLUS_URL.'/includes/blockui/jquery.blockUI'.$updraft_min_or_not.'.js', array('jquery'), $jquery_blockui_enqueue_version); - - wp_enqueue_script('jquery-labelauty', UPDRAFTPLUS_URL.'/includes/labelauty/jquery-labelauty'.$updraft_min_or_not.'.js', array('jquery'), $enqueue_version); - wp_enqueue_style('jquery-labelauty', UPDRAFTPLUS_URL.'/includes/labelauty/jquery-labelauty'.$updraft_min_or_not.'.css', array(), $enqueue_version); - $serialize_js_enqueue_version = $updraftplus->use_unminified_scripts() ? '2.8.1'.'.'.time() : '2.8.1'; - wp_enqueue_script('jquery.serializeJSON', UPDRAFTPLUS_URL.'/includes/jquery.serializeJSON/jquery.serializejson'.$min_or_not.'.js', array('jquery'), $serialize_js_enqueue_version); - $handlebars_js_enqueue_version = $updraftplus->use_unminified_scripts() ? '4.1.2'.'.'.time() : '4.1.2'; - wp_enqueue_script('handlebars', UPDRAFTPLUS_URL.'/includes/handlebars/handlebars'.$min_or_not.'.js', array(), $handlebars_js_enqueue_version); - $this->enqueue_jstree(); - - $jqueryui_dialog_extended_version = $updraftplus->use_unminified_scripts() ? '1.0.3'.'.'.time() : '1.0.3'; - wp_enqueue_script('jquery-ui.dialog.extended', UPDRAFTPLUS_URL.'/includes/jquery-ui.dialog.extended/jquery-ui.dialog.extended'.$min_or_not.'.js', array('jquery', 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-dialog'), $jqueryui_dialog_extended_version); - - do_action('updraftplus_admin_enqueue_scripts'); - - $day_selector = ''; - for ($day_index = 0; $day_index <= 6; $day_index++) { - // $selected = ($opt == $day_index) ? 'selected="selected"' : ''; - $selected = ''; - $day_selector .= "\n\t'; - } - - $mday_selector = ''; - for ($mday_index = 1; $mday_index <= 28; $mday_index++) { - // $selected = ($opt == $mday_index) ? 'selected="selected"' : ''; - $selected = ''; - $mday_selector .= "\n\t'; - } - $backup_methods = $updraftplus->backup_methods; - $remote_storage_options_and_templates = UpdraftPlus_Storage_Methods_Interface::get_remote_storage_options_and_templates(); - $main_tabs = $this->get_main_tabs_array(); - - $checkout_embed_5gb_trial_attribute = ''; - - if (is_a($updraftplus_checkout_embed, 'Updraft_Checkout_Embed')) { - $checkout_embed_5gb_trial_attribute = $updraftplus_checkout_embed->get_product('updraftplus-vault-storage-5-gb') ? 'data-embed-checkout="'.apply_filters('updraftplus_com_link', $updraftplus_checkout_embed->get_product('updraftplus-vault-storage-5-gb', UpdraftPlus_Options::admin_page_url().'?page=updraftplus&tab=settings')).'"' : ''; - } - - $hosting_company = $updraftplus->get_hosting_info(); - - wp_localize_script('updraft-admin-common', 'updraftlion', array( - 'tab' => empty($_GET['tab']) ? 'backups' : $_GET['tab'], - 'sendonlyonwarnings' => __('Send a report only when there are warnings/errors', 'updraftplus'), - 'wholebackup' => __('When the Email storage method is enabled, also send the backup', 'updraftplus'), - 'emailsizelimits' => esc_attr(sprintf(__('Be aware that mail servers tend to have size limits; typically around %s Mb; backups larger than any limits will likely not arrive.', 'updraftplus'), '10-20')), - 'rescanning' => __('Rescanning (looking for backups that you have uploaded manually into the internal backup store)...', 'updraftplus'), - 'dbbackup' => __('Only email the database backup', 'updraftplus'), - 'rescanningremote' => __('Rescanning remote and local storage for backup sets...', 'updraftplus'), - 'enteremailhere' => esc_attr(__('To send to more than one address, separate each address with a comma.', 'updraftplus')), - 'excludedeverything' => __('If you exclude both the database and the files, then you have excluded everything!', 'updraftplus'), - 'nofileschosen' => __('You have chosen to backup files, but no file entities have been selected', 'updraftplus'), - 'notableschosen' => __('You have chosen to backup a database, but no tables have been selected', 'updraftplus'), - 'nocloudserviceschosen' => __('You have chosen to send this backup to remote storage, but no remote storage locations have been selected', 'updraftplus'), - 'restore_proceeding' => __('The restore operation has begun. Do not close your browser until it reports itself as having finished.', 'updraftplus'), - 'unexpectedresponse' => __('Unexpected response:', 'updraftplus'), - 'servererrorcode' => __('The web server returned an error code (try again, or check your web server logs)', 'updraftplus'), - 'newuserpass' => __("The new user's RackSpace console password is (this will not be shown again):", 'updraftplus'), - 'trying' => __('Trying...', 'updraftplus'), - 'fetching' => __('Fetching...', 'updraftplus'), - 'calculating' => __('calculating...', 'updraftplus'), - 'begunlooking' => __('Begun looking for this entity', 'updraftplus'), - 'stilldownloading' => __('Some files are still downloading or being processed - please wait.', 'updraftplus'), - 'processing' => __('Processing files - please wait...', 'updraftplus'), - 'emptyresponse' => __('Error: the server sent an empty response.', 'updraftplus'), - 'warnings' => __('Warnings:', 'updraftplus'), - 'errors' => __('Errors:', 'updraftplus'), - 'jsonnotunderstood' => __('Error: the server sent us a response which we did not understand.', 'updraftplus'), - 'errordata' => __('Error data:', 'updraftplus'), - 'error' => __('Error:', 'updraftplus'), - 'errornocolon' => __('Error', 'updraftplus'), - 'existing_backups' => __('Existing backups', 'updraftplus'), - 'fileready' => __('File ready.', 'updraftplus'), - 'actions' => __('Actions', 'updraftplus'), - 'deletefromserver' => __('Delete from your web server', 'updraftplus'), - 'downloadtocomputer' => __('Download to your computer', 'updraftplus'), - 'browse_contents' => __('Browse contents', 'updraftplus'), - 'notunderstood' => __('Download error: the server sent us a response which we did not understand.', 'updraftplus'), - 'requeststart' => __('Requesting start of backup...', 'updraftplus'), - 'phpinfo' => __('PHP information', 'updraftplus'), - 'delete_old_dirs' => __('Delete Old Directories', 'updraftplus'), - 'raw' => __('Raw backup history', 'updraftplus'), - 'notarchive' => __('This file does not appear to be an UpdraftPlus backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'updraftplus').' '.__('However, UpdraftPlus archives are standard zip/SQL files - so if you are sure that your file has the right format, then you can rename it to match that pattern.', 'updraftplus'), - 'notarchive2' => '

'.__('This file does not appear to be an UpdraftPlus backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'updraftplus').'

'.apply_filters('updraftplus_if_foreign_then_premium_message', '

'.__('If this is a backup created by a different backup plugin, then UpdraftPlus Premium may be able to help you.', 'updraftplus').'

'), - 'makesure' => __('(make sure that you were trying to upload a zip file previously created by UpdraftPlus)', 'updraftplus'), - 'uploaderror' => __('Upload error:', 'updraftplus'), - 'notdba' => __('This file does not appear to be an UpdraftPlus encrypted database archive (such files are .gz.crypt files which have a name like: backup_(time)_(site name)_(code)_db.crypt.gz).', 'updraftplus'), - 'uploaderr' => __('Upload error', 'updraftplus'), - 'followlink' => __('Follow this link to attempt decryption and download the database file to your computer.', 'updraftplus'), - 'thiskey' => __('This decryption key will be attempted:', 'updraftplus'), - 'unknownresp' => __('Unknown server response:', 'updraftplus'), - 'ukrespstatus' => __('Unknown server response status:', 'updraftplus'), - 'uploaded' => __('The file was uploaded.', 'updraftplus'), - // One of the translators has erroneously changed "Backup" into "Back up" (which means, "reverse" !) - 'backupnow' => str_ireplace('Back Up', 'Backup', __('Backup Now', 'updraftplus')), - 'cancel' => __('Cancel', 'updraftplus'), - 'deletebutton' => __('Delete', 'updraftplus'), - 'createbutton' => __('Create', 'updraftplus'), - 'uploadbutton' => __('Upload', 'updraftplus'), - 'youdidnotselectany' => __('You did not select any components to restore. Please select at least one, and then try again.', 'updraftplus'), - 'proceedwithupdate' => __('Proceed with update', 'updraftplus'), - 'close' => __('Close', 'updraftplus'), - 'restore' => __('Restore', 'updraftplus'), - 'downloadlogfile' => __('Download log file', 'updraftplus'), - 'automaticbackupbeforeupdate' => __('Automatic backup before update', 'updraftplus'), - 'unsavedsettings' => __('You have made changes to your settings, and not saved.', 'updraftplus'), - 'saving' => __('Saving...', 'updraftplus'), - 'connect' => __('Connect', 'updraftplus'), - 'connecting' => __('Connecting...', 'updraftplus'), - 'disconnect' => __('Disconnect', 'updraftplus'), - 'disconnecting' => __('Disconnecting...', 'updraftplus'), - 'counting' => __('Counting...', 'updraftplus'), - 'updatequotacount' => __('Update quota count', 'updraftplus'), - 'addingsite' => __('Adding...', 'updraftplus'), - 'addsite' => __('Add site', 'updraftplus'), - // 'resetting' => __('Resetting...', 'updraftplus'), - 'creating_please_allow' => __('Creating...', 'updraftplus').(function_exists('openssl_encrypt') ? '' : ' ('.__('your PHP install lacks the openssl module; as a result, this can take minutes; if nothing has happened by then, then you should either try a smaller key size, or ask your web hosting company how to enable this PHP module on your setup.', 'updraftplus').')'), - 'sendtosite' => __('Send to site:', 'updraftplus'), - 'checkrpcsetup' => sprintf(__('You should check that the remote site is online, not firewalled, does not have security modules that may be blocking access, has UpdraftPlus version %s or later active and that the keys have been entered correctly.', 'updraftplus'), '2.10.3'), - 'pleasenamekey' => __('Please give this key a name (e.g. indicate the site it is for):', 'updraftplus'), - 'key' => __('Key', 'updraftplus'), - 'nokeynamegiven' => sprintf(__("Failure: No %s was given.", 'updraftplus'), __('key name', 'updraftplus')), - 'deleting' => __('Deleting...', 'updraftplus'), - 'enter_mothership_url' => __('Please enter a valid URL', 'updraftplus'), - 'delete_response_not_understood' => __("We requested to delete the file, but could not understand the server's response", 'updraftplus'), - 'testingconnection' => __('Testing connection...', 'updraftplus'), - 'send' => __('Send', 'updraftplus'), - 'migratemodalheight' => class_exists('UpdraftPlus_Addons_Migrator') ? 555 : 300, - 'migratemodalwidth' => class_exists('UpdraftPlus_Addons_Migrator') ? 770 : 500, - 'download' => _x('Download', '(verb)', 'updraftplus'), - 'browse_download_link' => apply_filters('updraftplus_browse_download_link', ''.__("With UpdraftPlus Premium, you can directly download individual files from here.", "updraftplus").''), - 'unsavedsettingsbackup' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('You should save your changes to ensure that they are used for making your backup.', 'updraftplus'), - 'unsaved_settings_export' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('Your export file will be of your displayed settings, not your saved ones.', 'updraftplus'), - 'dayselector' => $day_selector, - 'mdayselector' => $mday_selector, - 'day' => __('day', 'updraftplus'), - 'inthemonth' => __('in the month', 'updraftplus'), - 'days' => __('day(s)', 'updraftplus'), - 'hours' => __('hour(s)', 'updraftplus'), - 'weeks' => __('week(s)', 'updraftplus'), - 'forbackupsolderthan' => __('For backups older than', 'updraftplus'), - 'ud_url' => UPDRAFTPLUS_URL, - 'processing' => __('Processing...', 'updraftplus'), - 'loading' => __('Loading...', 'updraftplus'), - 'pleasefillinrequired' => __('Please fill in the required information.', 'updraftplus'), - 'test_settings' => __('Test %s Settings', 'updraftplus'), - 'testing_settings' => __('Testing %s Settings...', 'updraftplus'), - 'settings_test_result' => __('%s settings test result:', 'updraftplus'), - 'nothing_yet_logged' => __('Nothing yet logged', 'updraftplus'), - 'import_select_file' => __('You have not yet selected a file to import.', 'updraftplus'), - 'import_invalid_json_file' => __('Error: The chosen file is corrupt. Please choose a valid UpdraftPlus export file.', 'updraftplus'), - 'updraft_settings_url' => UpdraftPlus_Options::admin_page_url().'?page=updraftplus', - 'network_site_url' => network_site_url(), - 'importing' => __('Importing...', 'updraftplus'), - 'importing_data_from' => __('This will import data from:', 'updraftplus'), - 'exported_on' => __('Which was exported on:', 'updraftplus'), - 'continue_import' => __('Do you want to carry out the import?', 'updraftplus'), - 'complete' => __('Complete', 'updraftplus'), - 'backup_complete' => __('The backup has finished running', 'updraftplus'), - 'backup_aborted' => __('The backup was aborted', 'updraftplus'), - 'remote_delete_limit' => defined('UPDRAFTPLUS_REMOTE_DELETE_LIMIT') ? UPDRAFTPLUS_REMOTE_DELETE_LIMIT : 15, - 'remote_files_deleted' => __('remote files deleted', 'updraftplus'), - 'http_code' => __('HTTP code:', 'updraftplus'), - 'makesure2' => __('The file failed to upload. Please check the following:', 'updraftplus')."\n\n - ".__('Any settings in your .htaccess or web.config file that affects the maximum upload or post size.', 'updraftplus')."\n - ".__('The available memory on the server.', 'updraftplus')."\n - ".__('That you are attempting to upload a zip file previously created by UpdraftPlus.', 'updraftplus')."\n\n".__('Further information may be found in the browser JavaScript console, and the server PHP error logs.', 'updraftplus'), - 'zip_file_contents' => __('Browsing zip file', 'updraftplus'), - 'zip_file_contents_info' => __('Select a file to view information about it', 'updraftplus'), - 'search' => __('Search', 'updraftplus'), - 'download_timeout' => __('Unable to download file. This could be caused by a timeout. It would be best to download the zip to your computer.', 'updraftplus'), - 'loading_log_file' => __('Loading log file', 'updraftplus'), - 'updraftplus_version' => $updraftplus->version, - 'updraftcentral_wizard_empty_url' => __('Please enter the URL where your UpdraftCentral dashboard is hosted.'), - 'updraftcentral_wizard_invalid_url' => __('Please enter a valid URL e.g http://example.com', 'updraftplus'), - 'export_settings_file_name' => 'updraftplus-settings-'.sanitize_title(get_bloginfo('name')).'.json', - // For remote storage handlebarsjs template - 'remote_storage_options' => $remote_storage_options_and_templates['options'], - 'remote_storage_templates' => $remote_storage_options_and_templates['templates'], - 'remote_storage_methods' => $backup_methods, - 'instance_enabled' => __('Currently enabled', 'updraftplus'), - 'instance_disabled' => __('Currently disabled', 'updraftplus'), - 'local_upload_started' => __('Local backup upload has started; please check the log file to see the upload progress', 'updraftplus'), - 'local_upload_error' => __('You must select at least one remote storage destination to upload this backup set to.', 'updraftplus'), - 'already_uploaded' => __('(already uploaded)', 'updraftplus'), - 'onedrive_folder_url_warning' => __('Please specify the Microsoft OneDrive folder name, not the URL.', 'updraftplus'), - 'updraftcentral_cloud' => __('UpdraftCentral Cloud', 'updraftplus'), - 'udc_cloud_connected' => __('Connected. Requesting UpdraftCentral Key.', 'updraftplus'), - 'udc_cloud_key_created' => __('Key created. Adding site to UpdraftCentral Cloud.', 'updraftplus'), - 'login_successful' => __('Login successful.', 'updraftplus').' '.__('Please follow this link to open %s in a new window.', 'updraftplus'), - 'login_successful_short' => __('Login successful; reloading information.', 'updraftplus'), - 'registration_successful' => __('Registration successful.', 'updraftplus').' '.__('Please follow this link to open %s in a new window.', 'updraftplus'), - 'username_password_required' => __('Both email and password fields are required.', 'updraftplus'), - 'valid_email_required' => __('An email is required and needs to be in a valid format.', 'updraftplus'), - 'trouble_connecting' => __('Trouble connecting? Try using an alternative method in the advanced security options.', 'updraftplus'), - 'checking_tfa_code' => __('Verifying one-time password...', 'updraftplus'), - 'perhaps_login' => __('Perhaps you would want to login instead.', 'updraftplus'), - 'generating_key' => __('Please wait while the system generates and registers an encryption key for your website with UpdraftCentral Cloud.', 'updraftplus'), - 'updraftcentral_cloud_redirect' => __('Please wait while you are redirected to UpdraftCentral Cloud.', 'updraftplus'), - 'data_consent_required' => __('You need to read and accept the UpdraftCentral Cloud data and privacy policies before you can proceed.', 'updraftplus'), - 'close_wizard' => __('You can also close this wizard.', 'updraftplus'), - 'control_udc_connections' => __('For future control of all your UpdraftCentral connections, go to the "Advanced Tools" tab.', 'updraftplus'), - 'main_tabs_keys' => array_keys($main_tabs), - 'clone_version_warning' => __('Warning: you have selected a lower version than your currently installed version. This may fail if you have components that are incompatible with earlier versions.', 'updraftplus'), - 'clone_backup_complete' => __('The clone has been provisioned, and its data has been sent to it. Once the clone has finished deploying it, you will receive an email.', 'updraftplus'), - 'clone_backup_aborted' => __('The preparation of the clone data has been aborted.', 'updraftplus'), - 'current_clean_url' => UpdraftPlus::get_current_clean_url(), - 'exclude_rule_remove_conformation_msg' => __('Are you sure you want to remove this exclusion rule?', 'updraftplus'), - 'exclude_select_file_or_folder_msg' => __('Please select a file/folder which you would like to exclude', 'updraftplus'), - 'exclude_type_ext_msg' => __('Please enter a file extension, like zip', 'updraftplus'), - 'exclude_ext_error_msg' => __('Please enter a valid file extension', 'updraftplus'), - 'exclude_type_prefix_msg' => __('Please enter characters that begin the filename which you would like to exclude', 'updraftplus'), - 'exclude_prefix_error_msg' => __('Please enter a valid file name prefix', 'updraftplus'), - 'duplicate_exclude_rule_error_msg' => __('The exclusion rule which you are trying to add already exists', 'updraftplus'), - 'clone_key_required' => __('UpdraftClone key is required.', 'updraftplus'), - 'files_new_backup' => __('Include your files in the backup', 'updraftplus'), - 'files_incremental_backup' => __('File backup options', 'updraftplus'), - 'ajax_restore_invalid_response' => __('HTML was detected in the response. You may have a security module on your webserver blocking the restoration operation.', 'updraftplus'), - 'emptyrestorepath' => __('You have not selected a restore path for your chosen backups', 'updraftplus'), - 'updraftvault_info' => '

'.__('Try UpdraftVault!', 'updraftplus').'

' - .'

'.__('UpdraftVault is our remote storage which works seamlessly with UpdraftPlus.', 'updraftplus') - .' '.__('Find out more here.', 'updraftplus').'' - .'

' - .'

'.__('Try it - 1 month for $1!', 'updraftplus').'

', - 'login_udc_no_licences_short' => __('No UpdraftCentral licences were available. Continuing to connect to account.'), - 'credentials' => __('credentials', 'updraftplus'), - 'username' => __('Username', 'updraftplus'), - 'password' => __('Password', 'updraftplus'), - 'last_activity' => __('last activity: %d seconds ago', 'updraftplus'), - 'no_recent_activity' => __('no recent activity; will offer resumption after: %d seconds', 'updraftplus'), - 'restore_files_progress' => __('Restoring %s1 files out of %s2', 'updraftplus'), - 'restore_db_table_progress' => __('Restoring table: %s', 'updraftplus'), - 'restore_db_stored_routine_progress' => __('Restoring stored routine: %s', 'updraftplus'), - 'finished' => __('Finished', 'updraftplus'), - 'begun' => __('Begun', 'updraftplus'), - 'maybe_downloading_entities' => __('Downloading backup files if needed', 'updraftplus'), - 'preparing_backup_files' => __('Preparing backup files', 'updraftplus'), - 'ajax_restore_contact_failed' => __('Attempts by the browser to contact the website failed.', 'updraftplus'), - 'ajax_restore_error' => __('Restore error:', 'updraftplus'), - 'ajax_restore_404_detected' => '

'. __('Warning:', 'updraftplus') . '

' . __('Attempts by the browser to access some pages have returned a "not found (404)" error. This could mean that your .htaccess file has incorrect contents, is missing, or that your webserver is missing an equivalent mechanism.', 'updraftplus'). '

'.__('Missing pages:', 'updraftplus').'

'.__('Follow this link for more information', 'updraftplus').'.
', - 'delete_error_log_prompt' => __('Please check the error log for more details', 'updraftplus'), - 'existing_backups_limit' => defined('UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT') ? UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT : 100, - 'remote_scan_warning' => __('Warning: if you continue, you will add all backups stored in the configured remote storage directory (whichever site they were created by).'), - 'hosting_restriction_one_backup_permonth' => __("You have reached the monthly limit for the number of backups you can create at this time.", 'updraftplus').' '.__('Your hosting provider only allows you to take one backup per month.', 'updraftplus').' '.sprintf(__("Please contact your hosting company (%s) if you require further support.", 'updraftplus'), $hosting_company['name']), - 'hosting_restriction_one_incremental_perday' => __("You have reached the daily limit for the number of incremental backups you can create at this time.", 'updraftplus').' '.__("Your hosting provider only allows you to take one incremental backup per day.", 'updraftplus').' '.sprintf(__("Please contact your hosting company (%s) if you require further support.", 'updraftplus'), $hosting_company['name']), - 'hosting_restriction' => $updraftplus->is_hosting_backup_limit_reached(), - 'conditional_logic' => array( - 'day_of_the_week_options' => $updraftplus->list_days_of_the_week(), - 'logic_options' => array( - array( - 'label' => __('on every backup', 'updraftplus'), - 'value' => '', - ), - array( - 'label' => __('if any of the following conditions are matched:', 'updraftplus'), - 'value' => 'any', - ), - array( - 'label' => __('if all of the following conditions are matched:', 'updraftplus'), - 'value' => 'all', - ), - ), - 'operand_options' => array( - array( - 'label' => __('Day of the week', 'updraftplus'), - 'value' => 'day_of_the_week', - ), - array( - 'label' => __('Day of the month', 'updraftplus'), - 'value' => 'day_of_the_month', - ), - ), - 'operator_options' => array( - array( - 'label' => __('is', 'updraftplus'), - 'value' => 'is', - ), - array( - 'label' => __('is not', 'updraftplus'), - 'value' => 'is_not', - ), - ) - ), - )); - } - - /** - * Despite the name, this fires irrespective of what capabilities the user has (even none - so be careful) - */ - public function core_upgrade_preamble() { - // They need to be able to perform backups, and to perform updates - if (!UpdraftPlus_Options::user_can_manage() || (!current_user_can('update_core') && !current_user_can('update_plugins') && !current_user_can('update_themes'))) return; - - if (!class_exists('UpdraftPlus_Addon_Autobackup')) { - if (defined('UPDRAFTPLUS_NOADS_B')) return; - } - - ?> - do_notice('autobackup', 'autobackup', true)); - } else { - echo apply_filters('updraftplus_autobackup_blurb', ''); - } - ?> - - 'html5,flash,silverlight,html4', - 'browse_button' => 'plupload-browse-button', - 'container' => 'plupload-upload-ui', - 'drop_element' => 'drag-drop-area', - 'file_data_name' => 'async-upload', - 'multiple_queues' => true, - 'max_file_size' => '100Gb', - 'chunk_size' => $chunk_size.'b', - 'url' => admin_url('admin-ajax.php', 'relative'), - 'multipart' => true, - 'multi_selection' => true, - 'urlstream_upload' => true, - // additional post data to send to our ajax hook - 'multipart_params' => array( - '_ajax_nonce' => wp_create_nonce('updraft-uploader'), - 'action' => 'plupload_action' - ) - ); - - // WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663 - if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.swf')) { - $plupload_init['flash_swf_url'] = includes_url('js/plupload/Moxie.swf'); - } else { - $plupload_init['flash_swf_url'] = includes_url('js/plupload/plupload.flash.swf'); - } - - if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.xap')) { - $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/Moxie.xap'); - } else { - $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/plupload.silverlight.swf'); - } - - ?> - backups_dir_location(); - $disk_free_space = function_exists('disk_free_space') ? @disk_free_space($updraft_dir) : false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (false == $disk_free_space) return -1; - return ($disk_free_space > $space) ? true : false; - } - - /** - * Adds the settings link under the plugin on the plugin screen. - * - * @param Array $links Set of links for the plugin, before being filtered - * @param String $file File name (relative to the plugin directory) - * @return Array filtered results - */ - public function plugin_action_links($links, $file) { - if (is_array($links) && 'updraftplus/updraftplus.php' == $file) { - $settings_link = ''.__("Settings", "updraftplus").''; - array_unshift($links, $settings_link); - $settings_link = ''.__("Add-Ons / Pro Support", "updraftplus").''; - array_unshift($links, $settings_link); - } - return $links; - } - - public function admin_action_upgrade_pluginortheme() { - if (isset($_GET['action']) && ('upgrade-plugin' == $_GET['action'] || 'upgrade-theme' == $_GET['action']) && !class_exists('UpdraftPlus_Addon_Autobackup') && !defined('UPDRAFTPLUS_NOADS_B')) { - - if ('upgrade-plugin' == $_GET['action']) { - if (!current_user_can('update_plugins')) return; - } else { - if (!current_user_can('update_themes')) return; - } - - $dismissed_until = UpdraftPlus_Options::get_updraft_option('updraftplus_dismissedautobackup', 0); - if ($dismissed_until > time()) return; - - if ('upgrade-plugin' == $_GET['action']) { - $title = __('Update Plugin');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - $parent_file = 'plugins.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - $submenu_file = 'plugins.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - } else { - $title = __('Update Theme');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - $parent_file = 'themes.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - $submenu_file = 'themes.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php - } - - include_once(ABSPATH.'wp-admin/admin-header.php'); - - if (!class_exists('UpdraftPlus_Notices')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-notices.php'); - global $updraftplus_notices; - $updraftplus_notices->do_notice('autobackup', 'autobackup'); - } - } - - /** - * Show an administrative warning message, which can appear only on the UpdraftPlus plugin page - * - * @param String $message the HTML for the message (already escaped) - * @param String $class CSS class to use for the div - */ - public function show_plugin_page_admin_warning($message, $class = 'updated') { - - global $pagenow, $plugin_page; - - if (UpdraftPlus_Options::admin_page() !== $pagenow || 'updraftplus' !== $plugin_page) return; - - $this->show_admin_warning($message, $class); - } - - /** - * Paint a div for a dashboard warning - * - * @param String $message - the HTML for the message (already escaped) - * @param String $class - CSS class to use for the div - */ - public function show_admin_warning($message, $class = 'updated') { - echo '
'."

$message

"; - } - - public function show_admin_warning_multiple_storage_options() { - $this->show_admin_warning('UpdraftPlus: '.__('An error occurred when fetching storage module options: ', 'updraftplus').htmlspecialchars($this->storage_module_option_errors), 'error'); - } - - public function show_admin_warning_unwritable() { - // One of the translators has erroneously changed "Backup" into "Back up" (which means, "reverse" !) - $unwritable_mess = htmlspecialchars(str_ireplace('Back Up', 'Backup', __("The 'Backup Now' button is disabled as your backup directory is not writable (go to the 'Settings' tab and find the relevant option).", 'updraftplus'))); - $this->show_admin_warning($unwritable_mess, "error"); - } - - public function show_admin_nosettings_warning() { - $this->show_admin_warning(''.__('Welcome to UpdraftPlus!', 'updraftplus').' '.str_ireplace('Back Up', 'Backup', __('To make a backup, just press the Backup Now button.', 'updraftplus')).' '.__('To change any of the default settings of what is backed up, to configure scheduled backups, to send your backups to remote storage (recommended), and more, go to the settings tab.', 'updraftplus').'', 'updated notice is-dismissible'); - } - - public function show_admin_warning_execution_time() { - $this->show_admin_warning(''.__('Warning', 'updraftplus').': '.sprintf(__('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid backup failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', 'updraftplus'), (int) @ini_get('max_execution_time'), 90));// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - public function show_admin_warning_disabledcron() { - $ret = '

'; - $ret .= ''.__('Warning', 'updraftplus').': '.__('The scheduler is disabled in your WordPress install, via the DISABLE_WP_CRON setting. No backups can run (even "Backup Now") unless either you have set up a facility to call the scheduler manually, or until it is enabled.', 'updraftplus').' '.__('Go here for more information.', 'updraftplus').''; - $ret .= '

'; - return $ret; - } - - public function show_admin_warning_diskspace() { - $this->show_admin_warning(''.__('Warning', 'updraftplus').': '.sprintf(__('You have less than %s of free disk space on the disk which UpdraftPlus is configured to use to create backups. UpdraftPlus could well run out of space. Contact your the operator of your server (e.g. your web hosting company) to resolve this issue.', 'updraftplus'), '35 MB')); - } - - public function show_admin_warning_wordpressversion() { - $this->show_admin_warning(''.__('Warning', 'updraftplus').': '.sprintf(__('UpdraftPlus does not officially support versions of WordPress before %s. It may work for you, but if it does not, then please be aware that no support is available until you upgrade WordPress.', 'updraftplus'), '3.2')); - } - - public function show_admin_warning_litespeed() { - $this->show_admin_warning(''.__('Warning', 'updraftplus').': '.sprintf(__('Your website is hosted using the %s web server.', 'updraftplus'), 'LiteSpeed').' '.__('Please consult this FAQ if you have problems backing up.', 'updraftplus').''); - } - - public function show_admin_debug_warning() { - $this->show_admin_warning(''.__('Notice', 'updraftplus').': '.__('UpdraftPlus\'s debug mode is on. You may see debugging notices on this page not just from UpdraftPlus, but from any other plugin installed. Please try to make sure that the notice you are seeing is from UpdraftPlus before you raise a support request.', 'updraftplus').''); - } - - public function show_admin_warning_overdue_crons($howmany) { - $ret = '

'; - $ret .= ''.__('Warning', 'updraftplus').': '.sprintf(__('WordPress has a number (%d) of scheduled tasks which are overdue. Unless this is a development site, this probably means that the scheduler in your WordPress install is not working.', 'updraftplus'), $howmany).' '.__('Read this page for a guide to possible causes and how to fix it.', 'updraftplus').''; - $ret .= '

'; - return $ret; - } - - /** - * Output authorisation links for any un-authorised Dropbox settings instances - */ - public function show_admin_warning_dropbox() { - $this->get_method_auth_link('dropbox'); - } - - /** - * Output authorisation links for any un-authorised OneDrive settings instances - */ - public function show_admin_warning_onedrive() { - $this->get_method_auth_link('onedrive'); - } - - public function show_admin_warning_updraftvault() { - $this->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.sprintf(__('%s has been chosen for remote storage, but you are not currently connected.', 'updraftplus'), 'UpdraftPlus Vault').' '.__('Go to the remote storage settings in order to connect.', 'updraftplus'), 'updated'); - } - - /** - * Output authorisation links for any un-authorised Google Drive settings instances - */ - public function show_admin_warning_googledrive() { - $this->get_method_auth_link('googledrive'); - } - - /** - * Output authorisation links for any un-authorised Google Cloud settings instances - */ - public function show_admin_warning_googlecloud() { - $this->get_method_auth_link('googlecloud'); - } - - /** - * Show DreamObjects cluster migration warning - */ - public function show_admin_warning_dreamobjects() { - $this->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.sprintf(__('The %s endpoint is scheduled to shut down on the 1st October 2018. You will need to switch to a different end-point and migrate your data before that date. %sPlease see this article for more information%s'), 'objects-us-west-1.dream.io', '', ''), 'updated'); - } - - /** - * Show notice if the account connection attempted to register with UDC Cloud but could not due to lack of licences - */ - public function show_admin_warning_udc_couldnt_connect() { - $this->show_admin_warning(''.__('Notice', 'updraftplus').': '.sprintf(__('Connection to your %1$s account was successful. However, we were not able to register this site with %2$s, as there are no available %2$s licences on the account.', 'updraftplus'), 'UpdraftPlus.com', 'UpdraftCentral Cloud'), 'updated'); - } - - /** - * This method will setup the storage object and get the authentication link ready to be output with the notice - * - * @param String $method - the remote storage method - */ - public function get_method_auth_link($method) { - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($method)); - - $object = $storage_objects_and_ids[$method]['object']; - - foreach ($this->auth_instance_ids[$method] as $instance_id) { - - $object->set_instance_id($instance_id); - - $this->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.$object->get_authentication_link(false, false), 'updated updraft_authenticate_'.$method); - } - } - - /** - * Start a download of a backup. This method is called via the AJAX action updraft_download_backup. May die instead of returning depending upon the mode in which it is called. - */ - public function updraft_download_backup() { - try { - if (empty($_REQUEST['_wpnonce']) || !wp_verify_nonce($_REQUEST['_wpnonce'], 'updraftplus_download')) die; - - if (empty($_REQUEST['timestamp']) || !is_numeric($_REQUEST['timestamp']) || empty($_REQUEST['type'])) exit; - - $findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex']; - $stage = empty($_REQUEST['stage']) ? '' : $_REQUEST['stage']; - $file_path = empty($_REQUEST['filepath']) ? '' : $_REQUEST['filepath']; - - // This call may not actually return, depending upon what mode it is called in - $result = $this->do_updraft_download_backup($findexes, $_REQUEST['type'], $_REQUEST['timestamp'], $stage, false, $file_path); - - // In theory, if a response was already sent, then Connection: close has been issued, and a Content-Length. However, in https://updraftplus.com/forums/topic/pclzip_err_bad_format-10-invalid-archive-structure/ a browser ignores both of these, and then picks up the second output and complains. - if (empty($result['already_closed'])) echo json_encode($result); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during download backup. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during download backup. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } - die(); - } - - /** - * Ensure that a specified backup is present, downloading if necessary (or delete it, if the parameters so indicate). N.B. This function may die(), depending on the request being made in $stage - * - * @param Array $findexes - the index number of the backup archive requested - * @param String $type - the entity type (e.g. 'plugins') being requested - * @param Integer $timestamp - identifier for the backup being requested (UNIX epoch time) - * @param Mixed $stage - the stage; valid values include (have not audited for other possibilities) at least 'delete' and 2. - * @param Callable|Boolean $close_connection_callable - function used to close the connection to the caller; an array of data to return is passed. If false, then UpdraftPlus::close_browser_connection is called with a JSON version of the data. - * @param String $file_path - an over-ride for where to download the file to (basename only) - * - * @return Array - sumary of the results. May also just die. - */ - public function do_updraft_download_backup($findexes, $type, $timestamp, $stage, $close_connection_callable = false, $file_path = '') { - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - global $updraftplus; - - if (!is_array($findexes)) $findexes = array($findexes); - - $connection_closed = false; - - // Check that it is a known entity type; if not, die - if ('db' != substr($type, 0, 2)) { - $backupable_entities = $updraftplus->get_backupable_file_entities(true); - foreach ($backupable_entities as $t => $info) { - if ($type == $t) $type_match = true; - } - if (empty($type_match)) return array('result' => 'error', 'code' => 'no_such_type'); - } - - $debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode'); - - // Retrieve the information from our backup history - $backup_history = UpdraftPlus_Backup_History::get_history(); - - foreach ($findexes as $findex) { - // This is a bit ugly; these variables get placed back into $_POST (where they may possibly have come from), so that UpdraftPlus::log() can detect exactly where to log the download status. - $_POST['findex'] = $findex; - $_POST['type'] = $type; - $_POST['timestamp'] = $timestamp; - - // We already know that no possible entities have an MD5 clash (even after 2 characters) - // Also, there's nothing enforcing a requirement that nonces are hexadecimal - $job_nonce = dechex($timestamp).$findex.substr(md5($type), 0, 3); - - // You need a nonce before you can set job data. And we certainly don't yet have one. - $updraftplus->backup_time_nonce($job_nonce); - - // Set the job type before logging, as there can be different logging destinations - $updraftplus->jobdata_set('job_type', 'download'); - $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms); - - // Base name - $file = $backup_history[$timestamp][$type]; - - // Deal with multi-archive sets - if (is_array($file)) $file = $file[$findex]; - - if (false !== strpos($file_path, '..')) { - error_log("UpdraftPlus_Admin::do_updraft_download_backup : invalid file_path: $file_path"); - return array('result' => __('Error: invalid path', 'updraftplus')); - } - - if (!empty($file_path)) $file = $file_path; - - // Where it should end up being downloaded to - $fullpath = $updraftplus->backups_dir_location().'/'.$file; - - if (!empty($file_path) && strpos(realpath($fullpath), realpath($updraftplus->backups_dir_location())) === false) { - error_log("UpdraftPlus_Admin::do_updraft_download_backup : invalid fullpath: $fullpath"); - return array('result' => __('Error: invalid path', 'updraftplus')); - } - - if (2 == $stage) { - $updraftplus->spool_file($fullpath); - // We only want to remove if it was a temp file from the zip browser - if (!empty($file_path)) @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // Do not return - we do not want the caller to add any output - die; - } - - if ('delete' == $stage) { - @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $updraftplus->log("The file has been deleted ($file)"); - return array('result' => 'deleted'); - } - - // TODO: FIXME: Failed downloads may leave log files forever (though they are small) - if ($debug_mode) $updraftplus->logfile_open($updraftplus->nonce); - - set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); - - $updraftplus->log("Requested to obtain file: timestamp=$timestamp, type=$type, index=$findex"); - - $itext = empty($findex) ? '' : $findex; - $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0; - - $services = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false; - - $services = $updraftplus->get_canonical_service_list($services); - - $updraftplus->jobdata_set('service', $services); - - // Fetch it from the cloud, if we have not already got it - - $needs_downloading = false; - - if (!file_exists($fullpath) && empty($services)) { - $updraftplus->log('This file does not exist locally, and there is no remote storage for this file.'); - } elseif (!file_exists($fullpath)) { - // If the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud. - $needs_downloading = true; - $updraftplus->log('File does not yet exist locally - needs downloading'); - } elseif ($known_size > 0 && filesize($fullpath) < $known_size) { - $updraftplus->log("The file was found locally (".filesize($fullpath).") but did not match the size in the backup history ($known_size) - will resume downloading"); - $needs_downloading = true; - } elseif ($known_size > 0 && filesize($fullpath) > $known_size) { - $updraftplus->log("The file was found locally (".filesize($fullpath).") but the size is larger than what is recorded in the backup history ($known_size) - will try to continue but if errors are encountered then check that the backup is correct"); - } elseif ($known_size > 0) { - $updraftplus->log('The file was found locally and matched the recorded size from the backup history ('.round($known_size/1024, 1).' KB)'); - } else { - $updraftplus->log('No file size was found recorded in the backup history. We will assume the local one is complete.'); - $known_size = filesize($fullpath); - } - - // The AJAX responder that updates on progress wants to see this - $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath"); - - if ($needs_downloading) { - - // Update the "last modified" time to dissuade any other instances from thinking that no downloaders are active - @touch($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $msg = array( - 'result' => 'needs_download', - 'request' => array( - 'type' => $type, - 'timestamp' => $timestamp, - 'findex' => $findex - ) - ); - - if ($close_connection_callable && is_callable($close_connection_callable) && !$connection_closed) { - $connection_closed = true; - call_user_func($close_connection_callable, $msg); - } elseif (!$connection_closed) { - $connection_closed = true; - $updraftplus->close_browser_connection(json_encode($msg)); - } - UpdraftPlus_Storage_Methods_Interface::get_remote_file($services, $file, $timestamp); - } - - // Now, be ready to spool the thing to the browser - if (is_file($fullpath) && is_readable($fullpath) && $needs_downloading) { - - // That message is then picked up by the AJAX listener - $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'downloaded:'.filesize($fullpath).":$fullpath"); - - $result = 'downloaded'; - - } elseif ($needs_downloading) { - - $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'failed'); - $updraftplus->jobdata_set('dlerrors_'.$timestamp.'_'.$type.'_'.$findex, $updraftplus->errors); - $updraftplus->log('Remote fetch failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.'); - - $result = 'download_failed'; - } else { - $result = 'no_local_file'; - } - - restore_error_handler(); - - if ($debug_mode) @fclose($updraftplus->logfile_handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (!$debug_mode) @unlink($updraftplus->logfile_name);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - // The browser connection was possibly already closed, but not necessarily - return array('result' => $result, 'already_closed' => $connection_closed); - } - - /** - * This is used as a callback - * - * @param Mixed $msg The data to be JSON encoded and sent back - */ - public function _updraftplus_background_operation_started($msg) { - global $updraftplus; - // The extra spaces are because of a bug seen on one server in handling of non-ASCII characters; see HS#11739 - $updraftplus->close_browser_connection(json_encode($msg).' '); - } - - public function updraft_ajax_handler() { - - $nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce']; - - if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check'); - - $subaction = $_REQUEST['subaction']; - // Mitigation in case the nonce leaked to an unauthorised user - if ('dismissautobackup' == $subaction) { - if (!current_user_can('update_plugins') && !current_user_can('update_themes')) return; - } elseif ('dismissexpiry' == $subaction || 'dismissdashnotice' == $subaction) { - if (!current_user_can('update_plugins')) return; - } else { - if (!UpdraftPlus_Options::user_can_manage()) return; - } - - // All others use _POST - $data_in_get = array('get_log', 'get_fragment'); - - // UpdraftPlus_WPAdmin_Commands extends UpdraftPlus_Commands - i.e. all commands are in there - if (!class_exists('UpdraftPlus_WPAdmin_Commands')) include_once(UPDRAFTPLUS_DIR.'/includes/class-wpadmin-commands.php'); - $commands = new UpdraftPlus_WPAdmin_Commands($this); - - if (method_exists($commands, $subaction)) { - - $data = in_array($subaction, $data_in_get) ? $_GET : $_POST; - - // Undo WP's slashing of GET/POST data - $data = UpdraftPlus_Manipulation_Functions::wp_unslash($data); - - // TODO: Once all commands come through here and through updraft_send_command(), the data should always come from this attribute (once updraft_send_command() is modified appropriately). - if (isset($data['action_data'])) $data = $data['action_data']; - try { - $results = call_user_func(array($commands, $subaction), $data); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during '.$subaction.' subaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - die; - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during '.$subaction.' subaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - die; - } - if (is_wp_error($results)) { - $results = array( - 'result' => false, - 'error_code' => $results->get_error_code(), - 'error_message' => $results->get_error_message(), - 'error_data' => $results->get_error_data(), - ); - } - - if (is_string($results)) { - // A handful of legacy methods, and some which are directly the source for iframes, for which JSON is not appropriate. - echo $results; - } else { - echo json_encode($results); - } - die; - } - - // Below are all the commands not ported over into class-commands.php or class-wpadmin-commands.php - - if ('activejobs_list' == $subaction) { - try { - // N.B. Also called from autobackup.php - // TODO: This should go into UpdraftPlus_Commands, once the add-ons have been ported to use updraft_send_command() - echo json_encode($this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($_GET))); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } - - } elseif ('httpget' == $subaction) { - try { - // httpget - $curl = empty($_REQUEST['curl']) ? false : true; - echo $this->http_get(UpdraftPlus_Manipulation_Functions::wp_unslash($_REQUEST['uri']), $curl); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during http get. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during http get. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } - - } elseif ('doaction' == $subaction && !empty($_REQUEST['subsubaction']) && 'updraft_' == substr($_REQUEST['subsubaction'], 0, 8)) { - $subsubaction = $_REQUEST['subsubaction']; - try { - // These generally echo and die - they will need further work to port to one of the command classes. Some may already have equivalents in UpdraftPlus_Commands, if they are used from UpdraftCentral. - do_action(UpdraftPlus_Manipulation_Functions::wp_unslash($subsubaction), $_REQUEST); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during doaction subaction with '.$subsubaction.' subsubaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - die; - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during doaction subaction with '.$subsubaction.' subsubaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - die; - } - } - - die; - - } - - /** - * Run a credentials test for the indicated remote storage module - * - * @param Array $test_settings The test parameters, including the method itself indicated in the key 'method' - * @param Boolean $return_instead_of_echo Whether to return or echo the results. N.B. More than just the results to echo will be returned - * @return Array|Void - the results, if they are being returned (rather than echoed). Keys: 'output' (the output), 'data' (other data) - */ - public function do_credentials_test($test_settings, $return_instead_of_echo = false) { - - $method = (!empty($test_settings['method']) && preg_match("/^[a-z0-9]+$/", $test_settings['method'])) ? $test_settings['method'] : ""; - - $objname = "UpdraftPlus_BackupModule_$method"; - - $this->logged = array(); - // TODO: Add action for WP HTTP SSL stuff - set_error_handler(array($this, 'get_php_errors'), E_ALL & ~E_STRICT); - - if (!class_exists($objname)) include_once(UPDRAFTPLUS_DIR."/methods/$method.php"); - - $ret = ''; - $data = null; - - // TODO: Add action for WP HTTP SSL stuff - if (method_exists($objname, "credentials_test")) { - $obj = new $objname; - if ($return_instead_of_echo) ob_start(); - $data = $obj->credentials_test($test_settings); - if ($return_instead_of_echo) $ret .= ob_get_clean(); - } - - if (count($this->logged) >0) { - $ret .= "\n\n".__('Messages:', 'updraftplus')."\n"; - foreach ($this->logged as $err) { - $ret .= "* $err\n"; - } - if (!$return_instead_of_echo) echo $ret; - } - restore_error_handler(); - - if ($return_instead_of_echo) return array('output' => $ret, 'data' => $data); - - } - - /** - * Delete a backup set, whilst respecting limits on how much to delete in one go - * - * @uses remove_backup_set_cleanup() - * @param Array $opts - deletion options; with keys backup_timestamp, delete_remote, [remote_delete_limit] - * @return Array - as from remove_backup_set_cleanup() - */ - public function delete_set($opts) { - - global $updraftplus; - - $backups = UpdraftPlus_Backup_History::get_history(); - $timestamps = (string) $opts['backup_timestamp']; - - $remote_delete_limit = (isset($opts['remote_delete_limit']) && $opts['remote_delete_limit'] > 0) ? (int) $opts['remote_delete_limit'] : PHP_INT_MAX; - - $timestamps = explode(',', $timestamps); - $deleted_timestamps = ''; - $delete_remote = empty($opts['delete_remote']) ? false : true; - - // You need a nonce before you can set job data. And we certainly don't yet have one. - $updraftplus->backup_time_nonce(); - // Set the job type before logging, as there can be different logging destinations - $updraftplus->jobdata_set('job_type', 'delete'); - $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms); - - if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { - $updraftplus->logfile_open($updraftplus->nonce); - set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); - } - - $updraft_dir = $updraftplus->backups_dir_location(); - $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); - - $local_deleted = 0; - $remote_deleted = 0; - $sets_removed = 0; - - $deletion_errors = array(); - - foreach ($timestamps as $i => $timestamp) { - - if (!isset($backups[$timestamp])) { - return array('result' => 'error', 'message' => __('Backup set not found', 'updraftplus')); - } - - $nonce = isset($backups[$timestamp]['nonce']) ? $backups[$timestamp]['nonce'] : ''; - - $delete_from_service = array(); - - if ($delete_remote) { - // Locate backup set - if (isset($backups[$timestamp]['service'])) { - // Convert to an array so that there is no uncertainty about how to process it - $services = is_string($backups[$timestamp]['service']) ? array($backups[$timestamp]['service']) : $backups[$timestamp]['service']; - if (is_array($services)) { - foreach ($services as $service) { - if ($service && 'none' != $service && 'email' != $service) $delete_from_service[] = $service; - } - } - } - } - - $files_to_delete = array(); - foreach ($backupable_entities as $key => $ent) { - if (isset($backups[$timestamp][$key])) { - $files_to_delete[$key] = $backups[$timestamp][$key]; - } - } - // Delete DB - foreach ($backups[$timestamp] as $key => $value) { - if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) { - $files_to_delete[$key] = $backups[$timestamp][$key]; - } - } - - // Also delete the log - if ($nonce && !UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { - $files_to_delete['log'] = "log.$nonce.txt"; - } - - $updraftplus->register_wp_http_option_hooks(); - - foreach ($files_to_delete as $key => $files) { - - if (is_string($files)) { - $was_string = true; - $files = array($files); - } else { - $was_string = false; - } - - foreach ($files as $file) { - if (is_file($updraft_dir.'/'.$file) && @unlink($updraft_dir.'/'.$file)) $local_deleted++;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - if ('log' != $key && count($delete_from_service) > 0) { - - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids($delete_from_service); - - foreach ($delete_from_service as $service) { - - if ('email' == $service || 'none' == $service || !$service) continue; - - $deleted = -1; - - $remote_obj = $storage_objects_and_ids[$service]['object']; - - $instance_settings = $storage_objects_and_ids[$service]['instance_settings']; - $this->backups_instance_ids = empty($backups[$timestamp]['service_instance_ids'][$service]) ? array() : $backups[$timestamp]['service_instance_ids'][$service]; - - if (empty($instance_settings)) continue; - - uksort($instance_settings, array($this, 'instance_ids_sort')); - - foreach ($instance_settings as $instance_id => $options) { - - $remote_obj->set_options($options, false, $instance_id); - - if ($remote_obj->supports_feature('multi_delete')) { - if ($remote_deleted == $remote_delete_limit) { - $timestamps_list = implode(',', $timestamps); - - return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors); - } - - $deleted = $remote_obj->delete($files); - - if (true === $deleted) { - $remote_deleted = $remote_deleted + count($files); - - unset($backups[$timestamp][$key]); - - // If we don't save the array back, then the above section will fire again for the same files - and the remote storage will be requested to delete already-deleted files, which then means no time is actually saved by the browser-backend loop method. - UpdraftPlus_Backup_History::save_history($backups); - } else { - // Handle abstracted error codes/return fail status. Including handle array/objects returned - if (is_object($deleted) || is_array($deleted)) $deleted = false; - - if (!array_key_exists($instance_id, $deletion_errors)) { - $deletion_errors[$instance_id] = array('error_code' => $deleted, 'service' => $service); - } - } - - continue; - } - foreach ($files as $index => $file) { - if ($remote_deleted == $remote_delete_limit) { - $timestamps_list = implode(',', $timestamps); - - return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors); - } - - $deleted = $remote_obj->delete($file); - - if (true === $deleted) { - $remote_deleted++; - } else { - // Handle abstracted error codes/return fail status. Including handle array/objects returned - if (is_object($deleted) || is_array($deleted)) $deleted = false; - - if (!array_key_exists($instance_id, $deletion_errors)) { - $deletion_errors[$instance_id] = array('error_code' => $deleted, 'service' => $service); - } - } - - $itext = $index ? (string) $index : ''; - if ($was_string) { - unset($backups[$timestamp][$key]); - if ('db' == strtolower(substr($key, 0, 2))) unset($backups[$timestamp][$key][$index.'-size']); - } else { - unset($backups[$timestamp][$key][$index]); - unset($backups[$timestamp][$key.$itext.'-size']); - if (empty($backups[$timestamp][$key])) unset($backups[$timestamp][$key]); - } - if (isset($backups[$timestamp]['checksums']) && is_array($backups[$timestamp]['checksums'])) { - foreach (array_keys($backups[$timestamp]['checksums']) as $algo) { - unset($backups[$timestamp]['checksums'][$algo][$key.$index]); - } - } - - // If we don't save the array back, then the above section will fire again for the same files - and the remote storage will be requested to delete already-deleted files, which then means no time is actually saved by the browser-backend loop method. - UpdraftPlus_Backup_History::save_history($backups); - } - } - } - } - } - - unset($backups[$timestamp]); - unset($timestamps[$i]); - if ('' != $deleted_timestamps) $deleted_timestamps .= ','; - $deleted_timestamps .= $timestamp; - UpdraftPlus_Backup_History::save_history($backups); - $sets_removed++; - } - - $timestamps_list = implode(',', $timestamps); - - return $this->remove_backup_set_cleanup(true, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors); - - } - - /** - * This function sorts the array of instance ids currently saved so that any instance id that is in both the saved settings and the backup history move to the top of the array, as these are likely to work. Then values that don't appear in the backup history move to the bottom. - * - * @param String $a - the first instance id - * @param String $b - the second instance id - * @return Integer - returns an integer to indicate what position the $b value should be moved in - */ - public function instance_ids_sort($a, $b) { - if (in_array($a, $this->backups_instance_ids)) { - if (in_array($b, $this->backups_instance_ids)) return 0; - return -1; - } - return in_array($b, $this->backups_instance_ids) ? 1 : 0; - } - - /** - * Called by self::delete_set() to finish up before returning (whether the complete deletion is finished or not) - * - * @param Boolean $delete_complete - whether the whole set is now gone (i.e. last round) - * @param Array $backups - the backup history - * @param Integer $local_deleted - how many backup archives were deleted from local storage - * @param Integer $remote_deleted - how many backup archives were deleted from remote storage - * @param Integer $sets_removed - how many complete sets were removed - * @param String $timestamps - a csv of remaining timestamps - * @param String $deleted_timestamps - a csv of deleted timestamps - * @param Array $deletion_errors - an array of abstracted deletion errors, consisting of [error_code, service, instance]. For user notification purposes only, main error logging occurs at service. - * - * @return Array - information on the status, suitable for returning to the UI - */ - public function remove_backup_set_cleanup($delete_complete, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps, $deleted_timestamps, $deletion_errors = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $deletion_errors was used below but the code has been commented out. Can both be removed? - - global $updraftplus; - - $updraftplus->register_wp_http_option_hooks(false); - - UpdraftPlus_Backup_History::save_history($backups); - - $updraftplus->log("Local files deleted: $local_deleted. Remote files deleted: $remote_deleted"); - - /* - Disable until next release - $error_messages = array(); - $storage_details = array(); - - foreach ($deletion_errors as $instance => $entry) { - $service = $entry['service']; - - if (!array_key_exists($service, $storage_details)) { - // As errors from multiple instances of a service can be present, store the service storage object for possible use later - $new_service = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($service)); - $storage_details = array_merge($storage_details, $new_service); - } - - $intance_label = !empty($storage_details[$service]['instance_settings'][$instance]['instance_label']) ? $storage_details[$service]['instance_settings'][$instance]['instance_label'] : $service; - - switch ($entry['error_code']) { - case 'authentication_fail': - $error_messages[] = sprintf(__("The authentication failed for '%s'.", 'updraftplus').' '.__('Please check your credentials.', 'updraftplus'), $intance_label); - break; - case 'service_unavailable': - $error_messages[] = sprintf(__("We were unable to access '%s'.", 'updraftplus').' '.__('Service unavailable.', 'updraftplus'), $intance_label); - break; - case 'container_access_error': - $error_messages[] = sprintf(__("We were unable to access the folder/container for '%s'.", 'updraftplus').' '.__('Please check your permissions.', 'updraftplus'), $intance_label); - break; - case 'file_access_error': - $error_messages[] = sprintf(__("We were unable to access a file on '%s'.", 'updraftplus').' '.__('Please check your permissions.', 'updraftplus'), $intance_label); - break; - case 'file_delete_error': - $error_messages[] = sprintf(__("We were unable to delete a file on '%s'.", 'updraftplus').' '.__('The file may no longer exist or you may not have permission to delete.', 'updraftplus'), $intance_label); - break; - default: - $error_messages[] = sprintf(__("An error occurred while attempting to delete from '%s'.", 'updraftplus'), $intance_label); - break; - } - } - */ - - // $error_message_string = implode("\n", $error_messages); - $error_message_string = ''; - - if ($delete_complete) { - $set_message = __('Backup sets removed:', 'updraftplus'); - $local_message = __('Local files deleted:', 'updraftplus'); - $remote_message = __('Remote files deleted:', 'updraftplus'); - - if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { - restore_error_handler(); - } - - return array('result' => 'success', 'set_message' => $set_message, 'local_message' => $local_message, 'remote_message' => $remote_message, 'backup_sets' => $sets_removed, 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted, 'error_messages' => $error_message_string); - } else { - - return array('result' => 'continue', 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted, 'backup_sets' => $sets_removed, 'timestamps' => $timestamps, 'deleted_timestamps' => $deleted_timestamps, 'error_messages' => $error_message_string); - } - } - - /** - * Get the history status HTML and other information - * - * @param Boolean $rescan - whether to rescan local storage first - * @param Boolean $remotescan - whether to rescan remote storage first - * @param Boolean $debug - whether to return debugging information also - * @param Integer $backup_count - a count of the total backups we want to display on the front end for use by UpdraftPlus_Backup_History::existing_backup_table() - * - * @return Array - the information requested - */ - public function get_history_status($rescan, $remotescan, $debug = false, $backup_count = 0) { - - global $updraftplus; - - if ($rescan) $messages = UpdraftPlus_Backup_History::rebuild($remotescan, false, $debug); - $backup_history = UpdraftPlus_Backup_History::get_history(); - $output = UpdraftPlus_Backup_History::existing_backup_table($backup_history, $backup_count); - - $data = array(); - - if (!empty($messages) && is_array($messages)) { - $noutput = ''; - foreach ($messages as $msg) { - if (empty($msg['code']) || 'file-listing' != $msg['code']) { - $noutput .= '
  • '.(empty($msg['desc']) ? '' : $msg['desc'].': ').''.$msg['message'].'
  • '; - } - if (!empty($msg['data'])) { - $key = $msg['method'].'-'.$msg['service_instance_id']; - $data[$key] = $msg['data']; - } - } - if ($noutput) { - $output = '
    '.$output; - } - } - - $logs_exist = (false !== strpos($output, 'downloadlog')); - if (!$logs_exist) { - list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - if ($mod_time) $logs_exist = true; - } - - return apply_filters('updraftplus_get_history_status_result', array( - 'n' => __('Existing backups', 'updraftplus').' '.count($backup_history).'', - 't' => $output, // table - 'data' => $data, - 'cksum' => md5($output), - 'logs_exist' => $logs_exist, - 'web_server_disk_space' => UpdraftPlus_Filesystem_Functions::web_server_disk_space(true), - )); - } - - /** - * Stop an active backup job - * - * @param String $job_id - job ID of the job to stop - * - * @return Array - information on the outcome of the attempt - */ - public function activejobs_delete($job_id) { - - if (preg_match("/^[0-9a-f]{12}$/", $job_id)) { - - global $updraftplus; - $cron = get_option('cron', array()); - $found_it = false; - - $jobdata = $updraftplus->jobdata_getarray($job_id); - - if (!empty($jobdata['clone_job']) && !empty($jobdata['clone_id']) && !empty($jobdata['secret_token'])) { - $clone_id = $jobdata['clone_id']; - $secret_token = $jobdata['secret_token']; - $updraftplus->get_updraftplus_clone()->clone_failed_delete(array('clone_id' => $clone_id, 'secret_token' => $secret_token)); - } - - $updraft_dir = $updraftplus->backups_dir_location(); - if (file_exists($updraft_dir.'/log.'.$job_id.'.txt')) touch($updraft_dir.'/deleteflag-'.$job_id.'.txt'); - - foreach ($cron as $time => $job) { - if (isset($job['updraft_backup_resume'])) { - foreach ($job['updraft_backup_resume'] as $hook => $info) { - if (isset($info['args'][1]) && $info['args'][1] == $job_id) { - $args = $cron[$time]['updraft_backup_resume'][$hook]['args']; - wp_unschedule_event($time, 'updraft_backup_resume', $args); - if (!$found_it) return array('ok' => 'Y', 'c' => 'deleted', 'm' => __('Job deleted', 'updraftplus')); - $found_it = true; - } - } - } - } - } - - if (!$found_it) return array('ok' => 'N', 'c' => 'not_found', 'm' => __('Could not find that job - perhaps it has already finished?', 'updraftplus')); - - } - - /** - * Input: an array of items - * Each item is in the format: ,,(,) - * The 'base' is not for us: we just pass it straight back - * - * @param array $downloaders Array of Items to download - * @return array - */ - public function get_download_statuses($downloaders) { - global $updraftplus; - $download_status = array(); - foreach ($downloaders as $downloader) { - // prefix, timestamp, entity, index - if (preg_match('/^([^,]+),(\d+),([-a-z]+|db[0-9]+),(\d+)$/', $downloader, $matches)) { - $findex = (empty($matches[4])) ? '0' : $matches[4]; - $updraftplus->nonce = dechex($matches[2]).$findex.substr(md5($matches[3]), 0, 3); - $updraftplus->jobdata_reset(); - $status = $this->download_status($matches[2], $matches[3], $matches[4]); - if (is_array($status)) { - $status['base'] = $matches[1]; - $status['timestamp'] = $matches[2]; - $status['what'] = $matches[3]; - $status['findex'] = $findex; - $download_status[] = $status; - } - } - } - return $download_status; - } - - /** - * Get, as HTML output, a list of active jobs - * - * @param Array $request - details on the request being made (e.g. extra info to include) - * - * @return String - */ - public function get_activejobs_list($request) { - - global $updraftplus; - - $download_status = empty($request['downloaders']) ? array() : $this->get_download_statuses(explode(':', $request['downloaders'])); - - if (!empty($request['oneshot'])) { - $job_id = get_site_option('updraft_oneshotnonce', false); - // print_active_job() for one-shot jobs that aren't in cron - $active_jobs = (false === $job_id) ? '' : $this->print_active_job($job_id, true); - } elseif (!empty($request['thisjobonly'])) { - // print_active_jobs() is for resumable jobs where we want the cron info to be included in the output - $active_jobs = $this->print_active_jobs($request['thisjobonly']); - } else { - $active_jobs = $this->print_active_jobs(); - } - $logupdate_array = array(); - if (!empty($request['log_fetch'])) { - if (isset($request['log_nonce'])) { - $log_nonce = $request['log_nonce']; - $log_pointer = isset($request['log_pointer']) ? absint($request['log_pointer']) : 0; - $logupdate_array = $this->fetch_log($log_nonce, $log_pointer); - } - } - $res = array( - // We allow the front-end to decide what to do if there's nothing logged - we used to (up to 1.11.29) send a pre-defined message - 'l' => htmlspecialchars(UpdraftPlus_Options::get_updraft_lastmessage()), - 'j' => $active_jobs, - 'ds' => $download_status, - 'u' => $logupdate_array, - 'automatic_updates' => $updraftplus->is_automatic_updating_enabled() - ); - - $res['hosting_restriction'] = $updraftplus->is_hosting_backup_limit_reached(); - - return $res; - } - - /** - * Start a new backup - * - * @param Array $request - * @param Boolean|Callable $close_connection_callable - */ - public function request_backupnow($request, $close_connection_callable = false) { - global $updraftplus; - - $abort_before_booting = false; - $backupnow_nocloud = !empty($request['backupnow_nocloud']); - - $request['incremental'] = !empty($request['incremental']); - - $entities = !empty($request['onlythisfileentity']) ? explode(',', $request['onlythisfileentity']) : array(); - - $remote_storage_instances = array(); - - // if only_these_cloud_services is not an array then all connected remote storage locations are being backed up to and we don't need to do this - if (!empty($request['only_these_cloud_services']) && is_array($request['only_these_cloud_services'])) { - $remote_storage_locations = $request['only_these_cloud_services']; - - foreach ($remote_storage_locations as $key => $value) { - /* - This name key inside the value array is the remote storage method name prefixed by 31 characters (updraft_include_remote_service_) so we need to remove them to get the actual name, then the value key inside the value array has the instance id. - */ - $remote_storage_instances[substr($value['name'], 31)][$key] = $value['value']; - } - } - - $incremental = $request['incremental'] ? apply_filters('updraftplus_prepare_incremental_run', false, $entities) : false; - - // The call to backup_time_nonce() allows us to know the nonce in advance, and return it - $nonce = $updraftplus->backup_time_nonce(); - - $msg = array( - 'nonce' => $nonce, - 'm' => apply_filters('updraftplus_backupnow_start_message', ''.__('Start backup', 'updraftplus').': '.htmlspecialchars(__('OK. You should soon see activity in the "Last log message" field below.', 'updraftplus')), $nonce) - ); - - if (!empty($request['backup_nonce']) && 'current' != $request['backup_nonce']) $msg['nonce'] = $request['backup_nonce']; - - if (!empty($request['incremental']) && !$incremental) { - $msg = array( - 'error' => __('No suitable backup set (that already contains a full backup of all the requested file component types) was found, to add increments to. Aborting this backup.', 'updraftplus') - ); - $abort_before_booting = true; - } - - if ($close_connection_callable && is_callable($close_connection_callable)) { - call_user_func($close_connection_callable, $msg); - } else { - $updraftplus->close_browser_connection(json_encode($msg)); - } - - if ($abort_before_booting) die; - - $options = array('nocloud' => $backupnow_nocloud, 'use_nonce' => $nonce); - if (!empty($request['onlythisfileentity']) && is_string($request['onlythisfileentity'])) { - // Something to see in the 'last log' field when it first appears, before the backup actually starts - $updraftplus->log(__('Start backup', 'updraftplus')); - $options['restrict_files_to_override'] = explode(',', $request['onlythisfileentity']); - } - - if ($request['incremental'] && !$incremental) { - $updraftplus->log('An incremental backup was requested but no suitable backup found to add increments to; will proceed with a new backup'); - $request['incremental'] = false; - } - - if (!empty($request['extradata'])) $options['extradata'] = $request['extradata']; - - if (!empty($remote_storage_instances)) $options['remote_storage_instances'] = $remote_storage_instances; - - $options['always_keep'] = !empty($request['always_keep']); - - $event = empty($request['backupnow_nofiles']) ? (empty($request['backupnow_nodb']) ? 'updraft_backupnow_backup_all' : 'updraft_backupnow_backup') : 'updraft_backupnow_backup_database'; - - do_action($event, apply_filters('updraft_backupnow_options', $options, $request)); - } - - /** - * Get the contents of a log file - * - * @param String $backup_nonce - the backup id; or empty, for the most recently modified - * @param Integer $log_pointer - the byte count to fetch from - * @param String $output_format - the format to return in; allowed as 'html' (which will escape HTML entities in what is returned) and 'raw' - * - * @return String - */ - public function fetch_log($backup_nonce = '', $log_pointer = 0, $output_format = 'html') { - global $updraftplus; - - if (empty($backup_nonce)) { - list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - } else { - $nonce = $backup_nonce; - } - - if (!preg_match('/^[0-9a-f]+$/', $nonce)) die('Security check'); - - $log_content = ''; - $new_pointer = $log_pointer; - - if (!empty($nonce)) { - $updraft_dir = $updraftplus->backups_dir_location(); - - $potential_log_file = $updraft_dir."/log.".$nonce.".txt"; - - if (is_readable($potential_log_file)) { - - $templog_array = array(); - $log_file = fopen($potential_log_file, "r"); - if ($log_pointer > 0) fseek($log_file, $log_pointer); - - while (($buffer = fgets($log_file, 4096)) !== false) { - $templog_array[] = $buffer; - } - if (!feof($log_file)) { - $templog_array[] = __('Error: unexpected file read fail', 'updraftplus'); - } - - $new_pointer = ftell($log_file); - $log_content = implode("", $templog_array); - - - } else { - $log_content .= __('The log file could not be read.', 'updraftplus'); - } - - } else { - $log_content .= __('The log file could not be read.', 'updraftplus'); - } - - if ('html' == $output_format) $log_content = htmlspecialchars($log_content); - - $ret_array = array( - 'log' => $log_content, - 'nonce' => $nonce, - 'pointer' => $new_pointer - ); - - return $ret_array; - } - - /** - * Get a count for the number of overdue cron jobs - * - * @return Integer - how many cron jobs are overdue - */ - public function howmany_overdue_crons() { - $how_many_overdue = 0; - if (function_exists('_get_cron_array') || (is_file(ABSPATH.WPINC.'/cron.php') && include_once(ABSPATH.WPINC.'/cron.php') && function_exists('_get_cron_array'))) { - $crons = _get_cron_array(); - if (is_array($crons)) { - $timenow = time(); - foreach ($crons as $jt => $job) { - if ($jt < $timenow) $how_many_overdue++; - } - } - } - return $how_many_overdue; - } - - public function get_php_errors($errno, $errstr, $errfile, $errline) { - global $updraftplus; - if (0 == error_reporting()) return true; - $logline = $updraftplus->php_error_to_logline($errno, $errstr, $errfile, $errline); - if (false !== $logline) $this->logged[] = $logline; - // Don't pass it up the chain (since it's going to be output to the user always) - return true; - } - - private function download_status($timestamp, $type, $findex) { - global $updraftplus; - $response = array('m' => $updraftplus->jobdata_get('dlmessage_'.$timestamp.'_'.$type.'_'.$findex).'
    '); - if ($file = $updraftplus->jobdata_get('dlfile_'.$timestamp.'_'.$type.'_'.$findex)) { - if ('failed' == $file) { - $response['e'] = __('Download failed', 'updraftplus').'
    '; - $response['failed'] = true; - $errs = $updraftplus->jobdata_get('dlerrors_'.$timestamp.'_'.$type.'_'.$findex); - if (is_array($errs) && !empty($errs)) { - $response['e'] .= '
      '; - foreach ($errs as $err) { - if (is_array($err)) { - $response['e'] .= '
    • '.htmlspecialchars($err['message']).'
    • '; - } else { - $response['e'] .= '
    • '.htmlspecialchars($err).'
    • '; - } - } - $response['e'] .= '
    '; - } - } elseif (preg_match('/^downloaded:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) { - $response['p'] = 100; - $response['f'] = $matches[2]; - $response['s'] = (int) $matches[1]; - $response['t'] = (int) $matches[1]; - $response['m'] = __('File ready.', 'updraftplus'); - if ('db' != substr($type, 0, 2)) $response['can_show_contents'] = true; - } elseif (preg_match('/^downloading:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) { - // Convert to bytes - $response['f'] = $matches[2]; - $total_size = (int) max($matches[1], 1); - $cur_size = filesize($matches[2]); - $response['s'] = $cur_size; - $file_age = time() - filemtime($matches[2]); - if ($file_age > 20) $response['a'] = time() - filemtime($matches[2]); - $response['t'] = $total_size; - $response['m'] .= __("Download in progress", 'updraftplus').' ('.round($cur_size/1024).' / '.round(($total_size/1024)).' KB)'; - $response['p'] = round(100*$cur_size/$total_size); - } else { - $response['m'] .= __('No local copy present.', 'updraftplus'); - $response['p'] = 0; - $response['s'] = 0; - $response['t'] = 1; - } - } - return $response; - } - - /** - * Used with the WP filter upload_dir to adjust where uploads go to when uploading a backup - * - * @param Array $uploads - pre-filter array - * - * @return Array - filtered array - */ - public function upload_dir($uploads) { - global $updraftplus; - $updraft_dir = $updraftplus->backups_dir_location(); - if (is_writable($updraft_dir)) $uploads['path'] = $updraft_dir; - return $uploads; - } - - /** - * We do actually want to over-write - * - * @param String $dir Directory - * @param String $name Name - * @param String $ext File extension - * - * @return String - */ - public function unique_filename_callback($dir, $name, $ext) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use - return $name.$ext; - } - - public function sanitize_file_name($filename) { - // WordPress 3.4.2 on multisite (at least) adds in an unwanted underscore - return preg_replace('/-db(.*)\.gz_\.crypt$/', '-db$1.gz.crypt', $filename); - } - - /** - * Runs upon the WordPress action plupload_action - */ - public function plupload_action() { - - global $updraftplus; - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if (!UpdraftPlus_Options::user_can_manage()) return; - check_ajax_referer('updraft-uploader'); - - $updraft_dir = $updraftplus->backups_dir_location(); - if (!@UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - echo json_encode(array('e' => sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $updraft_dir).' '.__('You will find more information about this in the Settings section.', 'updraftplus'))); - exit; - } - - add_filter('upload_dir', array($this, 'upload_dir')); - add_filter('sanitize_file_name', array($this, 'sanitize_file_name')); - // handle file upload - - $farray = array('test_form' => true, 'action' => 'plupload_action'); - - $farray['test_type'] = false; - $farray['ext'] = 'x-gzip'; - $farray['type'] = 'application/octet-stream'; - - if (!isset($_POST['chunks'])) { - $farray['unique_filename_callback'] = array($this, 'unique_filename_callback'); - } - - $status = wp_handle_upload( - $_FILES['async-upload'], - $farray - ); - remove_filter('upload_dir', array($this, 'upload_dir')); - remove_filter('sanitize_file_name', array($this, 'sanitize_file_name')); - - if (isset($status['error'])) { - echo json_encode(array('e' => $status['error'])); - exit; - } - - // If this was the chunk, then we should instead be concatenating onto the final file - if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) { - - $final_file = basename($_POST['name']); - - if (!rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp')) { - @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('This file could not be uploaded', 'updraftplus')))); - exit; - } - - $status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp'; - - } - - $response = array(); - if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1) && isset($final_file)) { - if (!preg_match('/^log\.[a-f0-9]{12}\.txt/i', $final_file) && !preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $final_file, $matches)) { - $accept = apply_filters('updraftplus_accept_archivename', array()); - if (is_array($accept)) { - foreach ($accept as $acc) { - if (preg_match('/'.$acc['pattern'].'/i', $final_file)) { - $response['dm'] = sprintf(__('This backup was created by %s, and can be imported.', 'updraftplus'), $acc['desc']); - } - } - } - if (empty($response['dm'])) { - if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('Bad filename format - this does not look like a file created by UpdraftPlus', 'updraftplus')))); - exit; - } - } else { - $backupable_entities = $updraftplus->get_backupable_file_entities(true); - $type = isset($matches[3]) ? $matches[3] : ''; - if (!preg_match('/^log\.[a-f0-9]{12}\.txt/', $final_file) && 'db' != $type && !isset($backupable_entities[$type])) { - if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), sprintf(__('This looks like a file created by UpdraftPlus, but this install does not know about this type of object: %s. Perhaps you need to install an add-on?', 'updraftplus'), htmlspecialchars($type))))); - exit; - } - } - - // Final chunk? If so, then stich it all back together - if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && !empty($final_file)) { - if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) { - for ($i = 0; $i < $_POST['chunks']; $i++) { - $rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp'; - if ($rh = fopen($rf, 'rb+')) { - - // April 1st 2020 - Due to a bug during uploads to Dropbox some backups had string "null" appended to the end which caused warnings, this removes the string "null" from these backups - fseek($rh, -4, SEEK_END); - $data = fgets($rh, 5); - - if ("null" === $data) { - ftruncate($rh, filesize($rf) - 4); - } - - fseek($rh, 0, SEEK_SET); - - while ($line = fread($rh, 262144)) { - fwrite($wh, $line); - } - fclose($rh); - @unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - } - fclose($wh); - $status['file'] = $updraft_dir.'/'.$final_file; - if ('.tar' == substr($final_file, -4, 4)) { - if (file_exists($status['file'].'.gz')) unlink($status['file'].'.gz'); - if (file_exists($status['file'].'.bz2')) unlink($status['file'].'.bz2'); - } elseif ('.tar.gz' == substr($final_file, -7, 7)) { - if (file_exists(substr($status['file'], 0, strlen($status['file'])-3))) unlink(substr($status['file'], 0, strlen($status['file'])-3)); - if (file_exists(substr($status['file'], 0, strlen($status['file'])-3).'.bz2')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.bz2'); - } elseif ('.tar.bz2' == substr($final_file, -8, 8)) { - if (file_exists(substr($status['file'], 0, strlen($status['file'])-4))) unlink(substr($status['file'], 0, strlen($status['file'])-4)); - if (file_exists(substr($status['file'], 0, strlen($status['file'])-4).'.gz')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.gz'); - } - } - } - - } - - // send the uploaded file url in response - $response['m'] = $status['url']; - echo json_encode($response); - exit; - } - - /** - * Database decrypter - runs upon the WP action plupload_action2 - */ - public function plupload_action2() { - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - global $updraftplus; - - if (!UpdraftPlus_Options::user_can_manage()) return; - check_ajax_referer('updraft-uploader'); - - $updraft_dir = $updraftplus->backups_dir_location(); - if (!is_writable($updraft_dir)) exit; - - add_filter('upload_dir', array($this, 'upload_dir')); - add_filter('sanitize_file_name', array($this, 'sanitize_file_name')); - // handle file upload - - $farray = array('test_form' => true, 'action' => 'plupload_action2'); - - $farray['test_type'] = false; - $farray['ext'] = 'crypt'; - $farray['type'] = 'application/octet-stream'; - - if (isset($_POST['chunks'])) { - // $farray['ext'] = 'zip'; - // $farray['type'] = 'application/zip'; - } else { - $farray['unique_filename_callback'] = array($this, 'unique_filename_callback'); - } - - $status = wp_handle_upload( - $_FILES['async-upload'], - $farray - ); - remove_filter('upload_dir', array($this, 'upload_dir')); - remove_filter('sanitize_file_name', array($this, 'sanitize_file_name')); - - if (isset($status['error'])) die('ERROR: '.$status['error']); - - // If this was the chunk, then we should instead be concatenating onto the final file - if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) { - $final_file = basename($_POST['name']); - rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp'); - $status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp'; - } - - if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) { - if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?\.(gz\.crypt)$/i', $final_file)) { - - @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - echo 'ERROR:'.__('Bad filename format - this does not look like an encrypted database file created by UpdraftPlus', 'updraftplus'); - exit; - } - - // Final chunk? If so, then stich it all back together - if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && isset($final_file)) { - if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) { - for ($i=0; $i<$_POST['chunks']; $i++) { - $rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp'; - if ($rh = fopen($rf, 'rb')) { - while ($line = fread($rh, 32768)) { - fwrite($wh, $line); - } - fclose($rh); - @unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - } - fclose($wh); - } - } - - } - - // send the uploaded file url in response - if (isset($final_file)) echo 'OK:'.$final_file; - exit; - } - - /** - * Include the settings header template - */ - public function settings_header() { - $this->include_template('wp-admin/settings/header.php'); - } - - /** - * Include the settings footer template - */ - public function settings_footer() { - $this->include_template('wp-admin/settings/footer.php'); - } - - /** - * Output the settings page content. Will also run a restore if $_REQUEST so indicates. - */ - public function settings_output() { - - if (false == ($render = apply_filters('updraftplus_settings_page_render', true))) { - do_action('updraftplus_settings_page_render_abort', $render); - return; - } - - do_action('updraftplus_settings_page_init'); - - global $updraftplus; - - /** - * We use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credential for the WP_Filesystem. to do this WP outputs a form, but we don't pass our parameters via that. So the values are passed back in as GET parameters. - */ - if (isset($_REQUEST['action']) && (('updraft_restore' == $_REQUEST['action'] && isset($_REQUEST['backup_timestamp'])) || ('updraft_restore_continue' == $_REQUEST['action'] && !empty($_REQUEST['job_id'])))) { - $this->prepare_restore(); - return; - } - - if (isset($_REQUEST['action']) && 'updraft_delete_old_dirs' == $_REQUEST['action']) { - $nonce = empty($_REQUEST['updraft_delete_old_dirs_nonce']) ? '' : $_REQUEST['updraft_delete_old_dirs_nonce']; - if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check'); - $this->delete_old_dirs_go(); - return; - } - - if (!empty($_REQUEST['action']) && 'updraftplus_broadcastaction' == $_REQUEST['action'] && !empty($_REQUEST['subaction'])) { - $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce']; - if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check'); - do_action($_REQUEST['subaction']); - return; - } - - if (isset($_GET['error'])) { - // This is used by Microsoft OneDrive authorisation failures (May 15). I am not sure what may have been using the 'error' GET parameter otherwise - but it is harmless. - if (!empty($_GET['error_description'])) { - $this->show_admin_warning(htmlspecialchars($_GET['error_description']).' ('.htmlspecialchars($_GET['error']).')', 'error'); - } else { - $this->show_admin_warning(htmlspecialchars($_GET['error']), 'error'); - } - } - - if (isset($_GET['message'])) $this->show_admin_warning(htmlspecialchars($_GET['message'])); - - if (isset($_GET['action']) && 'updraft_create_backup_dir' == $_GET['action'] && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'create_backup_dir')) { - $created = $this->create_backup_dir(); - if (is_wp_error($created)) { - echo '

    '.__('Backup directory could not be created', 'updraftplus').'...
    '; - echo '

      '; - foreach ($created->get_error_messages() as $msg) { - echo '
    • '.htmlspecialchars($msg).'
    • '; - } - echo '

    '; - } elseif (false !== $created) { - echo '

    '.__('Backup directory successfully created.', 'updraftplus').'


    '; - } - echo ''.__('Actions', 'updraftplus').': '.__('Return to UpdraftPlus configuration', 'updraftplus').''; - return; - } - - echo ''; - - // This opens a div - $this->settings_header(); - ?> - -
    -

    - - - - - - " target="_blank"> - -

    -
    - - show_admin_warning("" . __("OptimizePress 2.0 encodes its contents, so search/replace does not work.", "updraftplus") . ' ' . __("To fix this problem go here.", "updraftplus") . "", "notice notice-warning"); - } - $success_advert = (isset($_GET['pval']) && 0 == $_GET['pval'] && !$updraftplus->have_addons) ? '

    '.__('For even more features and personal support, check out ', 'updraftplus').'UpdraftPlus Premium.

    ' : ""; - - echo "
    ".__('Your backup has been restored.', 'updraftplus').'
    '; - // Unnecessary - will be advised of this below - // if (2 == $_GET['updraft_restore_success']) echo ' '.__('Your old (themes, uploads, plugins, whatever) directories have been retained with "-old" appended to their name. Remove them when you are satisfied that the backup worked properly.'); - echo $success_advert; - $include_deleteform_div = false; - - } - - if ($this->scan_old_dirs(true)) $this->print_delete_old_dirs_form(true, $include_deleteform_div); - - // Close the div opened by the earlier section - if (isset($_GET['updraft_restore_success'])) echo '
    '; - - if (empty($success_advert) && empty($this->no_settings_warning)) { - - if (!class_exists('UpdraftPlus_Notices')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-notices.php'); - global $updraftplus_notices; - - $backup_history = UpdraftPlus_Backup_History::get_history(); - $review_dismiss = UpdraftPlus_Options::get_updraft_option('dismissed_review_notice', 0); - $backup_dir = $updraftplus->backups_dir_location(); - // N.B. Not an exact proxy for the installed time; they may have tweaked the expert option to move the directory - $installed = @filemtime($backup_dir.'/index.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $installed_for = time() - $installed; - - $advert = false; - if (!empty($backup_history) && $installed && time() > $review_dismiss && $installed_for > 28*86400 && $installed_for < 84*86400) { - $advert = 'rate'; - } - - $updraftplus_notices->do_notice($advert); - } - - if (!$updraftplus->memory_check(64)) { - // HS8390 - A case where UpdraftPlus::memory_check_current() returns -1 - $memory_check_current = $updraftplus->memory_check_current(); - if ($memory_check_current > 0) { - ?> -
    memory_check_current(); ?> MB
    - errors)) { - echo '
    '; - $updraftplus->list_errors(); - echo '
    '; - } - - $backup_history = UpdraftPlus_Backup_History::get_history(); - if (empty($backup_history)) { - UpdraftPlus_Backup_History::rebuild(); - $backup_history = UpdraftPlus_Backup_History::get_history(); - } - - $tabflag = 'backups'; - $main_tabs = $this->get_main_tabs_array(); - - if (isset($_REQUEST['tab'])) { - $request_tab = sanitize_text_field($_REQUEST['tab']); - $valid_tabflags = array_keys($main_tabs); - if (in_array($request_tab, $valid_tabflags)) { - $tabflag = $request_tab; - } else { - $tabflag = 'backups'; - } - } - - $this->include_template('wp-admin/settings/tab-bar.php', false, array('main_tabs' => $main_tabs, 'backup_history' => $backup_history, 'tabflag' => $tabflag)); - ?> - -
    -
    
    -		
    - - include_template('wp-admin/settings/delete-and-restore-modals.php'); - ?> - -
    style=""> - $is_opera); - $this->include_template('wp-admin/settings/tab-backups.php', false, array('backup_history' => $backup_history, 'options' => $tmp_opts)); - $this->include_template('wp-admin/settings/upload-backups-modal.php'); - ?> -
    - -
    style=""> - include_template('wp-admin/settings/migrator-no-migrator.php'); - } - ?> -
    - -
    style=""> -

    - - settings_formcontents(); ?> - - -
    - get_option(UDADDONS2_SLUG.'_options'); - - if (!empty($options['email'])) { - $email = htmlspecialchars($options['email']); - } - } - } - - // Check the vault's email if we fail to get the "email" from the "Premium / Extensions" tab - if (empty($email)) { - $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault'); - if (!is_wp_error($settings)) { - if (!empty($settings['settings'])) { - foreach ($settings['settings'] as $storage_options) { - if (!empty($storage_options['email'])) { - $email = $storage_options['email']; - break; - } - } - } - } - } - - // Checking any possible email we could find from the "updraft_email" option in case the - // above two checks failed. - if (empty($email)) { - $possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email')); - if (!empty($possible_emails)) { - // If we get an array from the 'just_one_email' result then we're going - // to pull the very first entry and make use of that on the succeeding process. - if (is_array($possible_emails)) $possible_emails = array_shift($possible_emails); - - if (is_string($possible_emails)) { - $emails = explode(',', $possible_emails); - $email = trim($emails[0]); - } - } - } - - $this->include_template('wp-admin/settings/updraftcentral-connect.php', false, array('email' => $email)); - ?> -
    - -
    - -
    style=""> - settings_advanced_tools(); ?> -
    - -
    style=""> - - include_template('wp-admin/settings/tab-addons.php', true, array('tabflag' => $tabflag)); - - echo apply_filters('updraftplus_addonstab_content', $tab_addons); - - ?> - -
    - - settings_footer(); - } - - /** - * Get main tabs array - * - * @return Array Array which have key as a tab key and value as tab label - */ - private function get_main_tabs_array() { - return apply_filters( - 'updraftplus_main_tabs', - array( - 'backups' => __('Backup / Restore', 'updraftplus'), - 'migrate' => __('Migrate / Clone', 'updraftplus'), - 'settings' => __('Settings', 'updraftplus'), - 'expert' => __('Advanced Tools', 'updraftplus'), - 'addons' => __('Premium / Extensions', 'updraftplus'), - ) - ); - } - - /** - * Potentially register an action for showing restore progress - */ - private function print_restore_in_progress_box_if_needed() { - global $updraftplus; - $check_restore_progress = $updraftplus->check_restore_progress(); - // Check to see if the restore is still in progress - if (is_array($check_restore_progress) && true == $check_restore_progress['status']) { - - $restore_jobdata = $check_restore_progress['restore_jobdata']; - $restore_jobdata['jobid'] = $check_restore_progress['restore_in_progress']; - $this->restore_in_progress_jobdata = $restore_jobdata; - - add_action('all_admin_notices', array($this, 'show_admin_restore_in_progress_notice')); - } - } - - /** - * This function is called via the command class, it will get the resume restore notice to be shown when a restore is taking place over AJAX - * - * @param string $job_id - the id of the job - * - * @return WP_Error|string - can return a string containing html or a WP_Error - */ - public function get_restore_resume_notice($job_id) { - global $updraftplus; - - if (empty($job_id)) return new WP_Error('missing_parameter', 'Missing parameters.'); - - $restore_jobdata = $updraftplus->jobdata_getarray($job_id); - - if (!is_array($restore_jobdata) && empty($restore_jobdata)) return new WP_Error('missing_jobdata', 'Job data not found.'); - - $restore_jobdata['jobid'] = $job_id; - $this->restore_in_progress_jobdata = $restore_jobdata; - - $html = $this->show_admin_restore_in_progress_notice(true, true); - - if (empty($html)) return new WP_Error('job_aborted', 'Job aborted.'); - - return $html; - } - - /** - * If added, then runs upon the WP action all_admin_notices, or can be called via get_restore_resume_notice() for when a restore is running over AJAX - * - * @param Boolean $return_instead_of_echo - indicates if we want to add the tfa UI - * @param Boolean $exclude_js - indicates if we want to exclude the js in the returned html - * - * @return void|string - can return a string containing html or echo the html to page - */ - public function show_admin_restore_in_progress_notice($return_instead_of_echo = false, $exclude_js = false) { - - if (isset($_REQUEST['action']) && 'updraft_restore_abort' === $_REQUEST['action'] && !empty($_REQUEST['job_id'])) { - delete_site_option('updraft_restore_in_progress'); - return; - } - - $restore_jobdata = $this->restore_in_progress_jobdata; - $seconds_ago = time() - (int) $restore_jobdata['job_time_ms']; - $minutes_ago = floor($seconds_ago/60); - $seconds_ago = $seconds_ago - $minutes_ago*60; - $time_ago = sprintf(__("%s minutes, %s seconds", 'updraftplus'), $minutes_ago, $seconds_ago); - - $html = '
    '; - $html .= 'UpdraftPlus: '.__('Unfinished restoration', 'updraftplus').'
    '; - $html .= '

    '.sprintf(__('You have an unfinished restoration operation, begun %s ago.', 'updraftplus'), $time_ago).'

    '; - $html .= '
    '; - $html .= wp_nonce_field('updraftplus-credentialtest-nonce'); - $html .= ''; - $html .= ''; - $html .= ''; - - if ($exclude_js) { - $html .= ''; - } else { - $html .= ''; - } - $html .= ''; - - $html .= '
    '; - - if ($return_instead_of_echo) return $html; - - echo $html; - } - - /** - * This method will build the UpdraftPlus.com login form and echo it to the page. - * - * @param String $option_page - the option page this form is being output to - * @param Boolean $tfa - indicates if we want to add the tfa UI - * @param Boolean $include_form_container - indicates if we want the form container - * @param Array $further_options - other options (see below for the possibilities + defaults) - * - * @return void - */ - public function build_credentials_form($option_page, $tfa = false, $include_form_container = true, $further_options = array()) { - - global $updraftplus; - - $further_options = wp_parse_args($further_options, array( - 'under_username' => __("Not yet got an account (it's free)? Go get one!", 'updraftplus'), - 'under_username_link' => $updraftplus->get_url('my-account') - )); - - if ($include_form_container) { - $enter_credentials_begin = UpdraftPlus_Options::options_form_begin('', false, array(), 'updraftplus_com_login'); - if (is_multisite()) $enter_credentials_begin .= ''; - } else { - $enter_credentials_begin = ''; - - echo $enter_credentials_begin; - - $options = apply_filters('updraftplus_com_login_options', array("email" => "", "password" => "")); - - if ($include_form_container) { - // We have to duplicate settings_fields() in order to set our referer - // settings_fields(UDADDONS2_SLUG.'_options'); - - $option_group = $option_page.'_options'; - echo ""; - echo ''; - - // wp_nonce_field("$option_group-options"); - - // This one is used on multisite - echo ''; - - $name = "_wpnonce"; - $action = esc_attr($option_group."-options"); - $nonce_field = ''; - - echo $nonce_field; - - $referer = esc_attr(UpdraftPlus_Manipulation_Functions::wp_unslash($_SERVER['REQUEST_URI'])); - - // This one is used on single site installs - if (false === strpos($referer, '?')) { - $referer .= '?tab=addons'; - } else { - $referer .= '&tab=addons'; - } - - echo ''; - // End of duplication of settings-fields() - } - ?> - -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - - -

    - -

    - - - -
    - - -
    - - include_template('wp-admin/settings/backupnow-modal.php', true); - } - - /** - * Also used by the auto-backups add-on - * - * @param Boolean $wide_format Whether to return data in a wide format - * @param Boolean $print_active_jobs Whether to include currently active jobs - * @return String - the HTML output - */ - public function render_active_jobs_and_log_table($wide_format = false, $print_active_jobs = true) { - global $updraftplus; - ?> -
    - print_active_jobs() : '';?> -
    "> -
    - -
    -
    -
    - -
    :
    -
    - most_recently_modified_log_link(); ?> -
    - -
    - : -
    - most_recently_modified_log_link(); ?> -
    - -
    - get_updraftplus_rssfeed(); - if (is_a($feed, 'SimplePie')) { - echo ''.__('Latest UpdraftPlus.com news:', 'updraftplus').''; - echo ''; - } - } - ?> -
    - last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - - ?> - class="updraft-log-link" onclick="event.preventDefault(); updraft_popuplog('');"> - include_template('wp-admin/settings/downloading-and-restoring.php', $return_result, array('backup_history' => $backup_history, 'options' => $options)); - } - - /** - * Renders take backup content - */ - public function take_backup_content() { - global $updraftplus; - $updraft_dir = $updraftplus->backups_dir_location(); - $backup_disabled = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir) ? '' : 'disabled="disabled"'; - $this->include_template('wp-admin/settings/take-backup.php', false, array('backup_disabled' => $backup_disabled)); - } - - /** - * Output a table row using the updraft_debugrow class - * - * @param String $head - header cell contents - * @param String $content - content cell contents - */ - public function settings_debugrow($head, $content) { - echo "$head$content"; - } - - public function settings_advanced_tools($return_instead_of_echo = false, $pass_through = array()) { - return $this->include_template('wp-admin/advanced/advanced-tools.php', $return_instead_of_echo, $pass_through); - } - - /** - * Paint the HTML for the form for deleting old directories - * - * @param Boolean $include_blurb - whether to include explanatory text - * @param Boolean $include_div - whether to wrap inside a div tag - */ - public function print_delete_old_dirs_form($include_blurb = true, $include_div = true) { - if ($include_blurb) { - if ($include_div) { - echo '
    '; - } - echo '

    '.__('Your WordPress install has old directories from its state before you restored/migrated (technical information: these are suffixed with -old). You should press this button to delete them as soon as you have verified that the restoration worked.', 'updraftplus').'

    '; - } - ?> -
    - - - -
    - '; - } - - /** - * Return cron status information about a specified in-progress job - * - * @param Boolean|String $job_id - the job to get information about; or, if not specified, all jobs - * - * @return Array|Boolean - the requested information, or false if it was not found. Format differs depending on whether info on all jobs, or a single job, was requested. - */ - public function get_cron($job_id = false) { - - $cron = get_option('cron'); - if (!is_array($cron)) $cron = array(); - if (false === $job_id) return $cron; - - foreach ($cron as $time => $job) { - if (!isset($job['updraft_backup_resume'])) continue; - foreach ($job['updraft_backup_resume'] as $info) { - if (isset($info['args'][1]) && $job_id == $info['args'][1]) { - global $updraftplus; - $jobdata = $updraftplus->jobdata_getarray($job_id); - return is_array($jobdata) ? array($time, $jobdata) : false; - } - } - } - } - - /** - * Gets HTML describing the active jobs - * - * @param Boolean $this_job_only A value for $this_job_only also causes something non-empty to always be returned (to allow detection of the job having started on the front-end) - * - * @return String - the HTML - */ - private function print_active_jobs($this_job_only = false) { - $cron = $this->get_cron(); - $ret = ''; - - foreach ($cron as $time => $job) { - if (isset($job['updraft_backup_resume'])) { - foreach ($job['updraft_backup_resume'] as $info) { - if (isset($info['args'][1])) { - $job_id = $info['args'][1]; - if (false === $this_job_only || $job_id == $this_job_only) { - $ret .= $this->print_active_job($job_id, false, $time, $info['args'][0]); - } - } - } - } - } - // A value for $this_job_only implies that output is required - if (false !== $this_job_only && !$ret) { - $ret = $this->print_active_job($this_job_only); - if ('' == $ret) { - global $updraftplus; - $log_file = $updraftplus->get_logfile_name($this_job_only); - // if the file exists, the backup was booted. Check if the information about completion is found in the log, or if it was modified at least 2 minutes ago. - if (file_exists($log_file) && ($updraftplus->found_backup_complete_in_logfile($this_job_only) || (time() - filemtime($log_file)) > 120)) { - // The presence of the exact ID matters to the front-end - indicates that the backup job has at least begun - $ret = '
    '.__('The backup has finished running', 'updraftplus').' - '.__('View Log', 'updraftplus').'
    '; - } - } - } - - return $ret; - } - - /** - * Print the HTML for a particular job - * - * @param String $job_id - the job identifier/nonce - * @param Boolean $is_oneshot - whether this backup should be 'one shot', i.e. no resumptions - * @param Boolean|Integer $time - * @param Integer $next_resumption - * - * @return String - */ - private function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) { - - $ret = ''; - - global $updraftplus; - $jobdata = $updraftplus->jobdata_getarray($job_id); - - if (false == apply_filters('updraftplus_print_active_job_continue', true, $is_oneshot, $next_resumption, $jobdata)) return ''; - - if (!isset($jobdata['backup_time'])) return ''; - - $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); - - $began_at = isset($jobdata['backup_time']) ? get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_time']), 'D, F j, Y H:i') : '?'; - - $backup_label = !empty($jobdata['label']) ? $jobdata['label'] : ''; - - $remote_sent = (!empty($jobdata['service']) && ((is_array($jobdata['service']) && in_array('remotesend', $jobdata['service'])) || 'remotesend' === $jobdata['service'])) ? true : false; - - $jobstatus = empty($jobdata['jobstatus']) ? 'unknown' : $jobdata['jobstatus']; - $stage = 0; - switch ($jobstatus) { - // Stage 0 - case 'begun': - $curstage = __('Backup begun', 'updraftplus'); - break; - // Stage 1 - case 'filescreating': - $stage = 1; - $curstage = __('Creating file backup zips', 'updraftplus'); - if (!empty($jobdata['filecreating_substatus']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['description'])) { - - $sdescrip = preg_replace('/ \(.*\)$/', '', $backupable_entities[$jobdata['filecreating_substatus']['e']]['description']); - if (strlen($sdescrip) > 20 && isset($jobdata['filecreating_substatus']['e']) && is_array($jobdata['filecreating_substatus']['e']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription'])) $sdescrip = $backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription']; - $curstage .= ' ('.$sdescrip.')'; - if (isset($jobdata['filecreating_substatus']['i']) && isset($jobdata['filecreating_substatus']['t'])) { - $stage = min(2, 1 + ($jobdata['filecreating_substatus']['i']/max($jobdata['filecreating_substatus']['t'], 1))); - } - } - break; - case 'filescreated': - $stage = 2; - $curstage = __('Created file backup zips', 'updraftplus'); - break; - // Stage 4 - case 'clonepolling': - $stage = 4; - $curstage = __('Clone server being provisioned and booted (can take several minutes)', 'updraftplus'); - break; - case 'clouduploading': - $stage = 4; - $curstage = __('Uploading files to remote storage', 'updraftplus'); - if ($remote_sent) $curstage = __('Sending files to remote site', 'updraftplus'); - if (isset($jobdata['uploading_substatus']['t']) && isset($jobdata['uploading_substatus']['i'])) { - $t = max((int) $jobdata['uploading_substatus']['t'], 1); - $i = min($jobdata['uploading_substatus']['i']/$t, 1); - $p = min($jobdata['uploading_substatus']['p'], 1); - $pd = $i + $p/$t; - $stage = 4 + $pd; - $curstage .= ' '.sprintf(__('(%s%%, file %s of %s)', 'updraftplus'), floor(100*$pd), $jobdata['uploading_substatus']['i']+1, $t); - } - break; - case 'pruning': - $stage = 5; - $curstage = __('Pruning old backup sets', 'updraftplus'); - break; - case 'resumingforerrors': - $stage = -1; - $curstage = __('Waiting until scheduled time to retry because of errors', 'updraftplus'); - break; - // Stage 6 - case 'finished': - $stage = 6; - $curstage = __('Backup finished', 'updraftplus'); - break; - default: - // Database creation and encryption occupies the space from 2 to 4. Databases are created then encrypted, then the next database is created/encrypted, etc. - if ('dbcreated' == substr($jobstatus, 0, 9)) { - $jobstatus = 'dbcreated'; - $whichdb = substr($jobstatus, 9); - if (!is_numeric($whichdb)) $whichdb = 0; - $howmanydbs = max((empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']), 1); - $perdbspace = 2/$howmanydbs; - - $stage = min(4, 2 + ($whichdb+2)*$perdbspace); - - $curstage = __('Created database backup', 'updraftplus'); - - } elseif ('dbcreating' == substr($jobstatus, 0, 10)) { - $whichdb = substr($jobstatus, 10); - if (!is_numeric($whichdb)) $whichdb = 0; - $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']); - $perdbspace = 2/$howmanydbs; - $jobstatus = 'dbcreating'; - - $stage = min(4, 2 + $whichdb*$perdbspace); - - $curstage = __('Creating database backup', 'updraftplus'); - if (!empty($jobdata['dbcreating_substatus']['t'])) { - $curstage .= ' ('.sprintf(__('table: %s', 'updraftplus'), $jobdata['dbcreating_substatus']['t']).')'; - if (!empty($jobdata['dbcreating_substatus']['i']) && !empty($jobdata['dbcreating_substatus']['a'])) { - $substage = max(0.001, ($jobdata['dbcreating_substatus']['i'] / max($jobdata['dbcreating_substatus']['a'], 1))); - $stage += $substage * $perdbspace * 0.5; - } - } - } elseif ('dbencrypting' == substr($jobstatus, 0, 12)) { - $whichdb = substr($jobstatus, 12); - if (!is_numeric($whichdb)) $whichdb = 0; - $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']); - $perdbspace = 2/$howmanydbs; - $stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace*0.5); - $jobstatus = 'dbencrypting'; - $curstage = __('Encrypting database', 'updraftplus'); - } elseif ('dbencrypted' == substr($jobstatus, 0, 11)) { - $whichdb = substr($jobstatus, 11); - if (!is_numeric($whichdb)) $whichdb = 0; - $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']); - $jobstatus = 'dbencrypted'; - $perdbspace = 2/$howmanydbs; - $stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace); - $curstage = __('Encrypted database', 'updraftplus'); - } else { - $curstage = __('Unknown', 'updraftplus'); - } - } - - $runs_started = empty($jobdata['runs_started']) ? array() : $jobdata['runs_started']; - $time_passed = empty($jobdata['run_times']) ? array() : $jobdata['run_times']; - $last_checkin_ago = -1; - if (is_array($time_passed)) { - foreach ($time_passed as $run => $passed) { - if (isset($runs_started[$run])) { - $time_ago = microtime(true) - ($runs_started[$run] + $time_passed[$run]); - if ($time_ago < $last_checkin_ago || -1 == $last_checkin_ago) $last_checkin_ago = $time_ago; - } - } - } - - $next_res_after = (int) $time-time(); - $next_res_txt = ($is_oneshot) ? '' : sprintf(__("next resumption: %d (after %ss)", 'updraftplus'), $next_resumption, $next_res_after). ' '; - $last_activity_txt = ($last_checkin_ago >= 0) ? sprintf(__('last activity: %ss ago', 'updraftplus'), floor($last_checkin_ago)).' ' : ''; - - if (($last_checkin_ago < 50 && $next_res_after>30) || $is_oneshot) { - $show_inline_info = $last_activity_txt; - $title_info = $next_res_txt; - } else { - $show_inline_info = $next_res_txt; - $title_info = $last_activity_txt; - } - - $ret .= '
    '; - - $ret .= '
    '.(!empty($backup_label) ? esc_html($backup_label) : $began_at). - '
    '; - - $ret .= '
    '; - // Existence of the 'updraft-jobid-(id)' id is checked for in other places, so do not modify this - $ret .= '
    '; - - if ($clone_url) $ret .= '
    '; - - $ret .= apply_filters('updraft_printjob_beforewarnings', '', $jobdata, $job_id); - - if (!empty($jobdata['warnings']) && is_array($jobdata['warnings'])) { - $ret .= '
      '; - foreach ($jobdata['warnings'] as $warning) { - $ret .= '
    • '.sprintf(__('Warning: %s', 'updraftplus'), make_clickable(htmlspecialchars($warning))).'
    • '; - } - $ret .= '
    '; - } - - $ret .= '
    '; - // $ret .= ''.htmlspecialchars($curstage).''; - $ret .= htmlspecialchars($curstage); - // we need to add this data-progress attribute in order to be able to update the progress bar in UDC - - $ret .= '
    '; - $ret .= '
    '; - - $ret .= '
    '; - - $ret .= $show_inline_info; - if (!empty($show_inline_info)) $ret .= ' - '; - - $file_nonce = empty($jobdata['file_nonce']) ? $job_id : $jobdata['file_nonce']; - - $ret .= ''.__('show log', 'updraftplus').''; - if (!$is_oneshot) $ret .=' - '.__('stop', 'updraftplus').''; - $ret .= '
    '; - - $ret .= '
    '; - - return $ret; - - } - - private function delete_old_dirs_go($show_return = true) { - echo $show_return ? '

    UpdraftPlus - '.__('Remove old directories', 'updraftplus').'

    ' : '

    '.__('Remove old directories', 'updraftplus').'

    '; - - if ($this->delete_old_dirs()) { - echo '

    '.__('Old directories successfully removed.', 'updraftplus').'


    '; - } else { - echo '

    ',__('Old directory removal failed for some reason. You may want to do this manually.', 'updraftplus').'


    '; - } - if ($show_return) echo ''.__('Actions', 'updraftplus').': '.__('Return to UpdraftPlus configuration', 'updraftplus').''; - } - - /** - * Deletes the -old directories that are created when a backup is restored. - * - * @return Boolean. Can also exit (something we ought to probably review) - */ - private function delete_old_dirs() { - global $wp_filesystem, $updraftplus; - $credentials = request_filesystem_credentials(wp_nonce_url(UpdraftPlus_Options::admin_page_url()."?page=updraftplus&action=updraft_delete_old_dirs", 'updraftplus-credentialtest-nonce', 'updraft_delete_old_dirs_nonce')); - $wpfs = WP_Filesystem($credentials); - if (!empty($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) { - foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message); - exit; - } - if (!$wpfs) exit; - - // From WP_CONTENT_DIR - which contains 'themes' - $ret = $this->delete_old_dirs_dir($wp_filesystem->wp_content_dir()); - - $updraft_dir = $updraftplus->backups_dir_location(); - if ($updraft_dir) { - $ret4 = $updraft_dir ? $this->delete_old_dirs_dir($updraft_dir, false) : true; - } else { - $ret4 = true; - } - - $plugs = untrailingslashit($wp_filesystem->wp_plugins_dir()); - if ($wp_filesystem->is_dir($plugs.'-old')) { - echo "".__('Delete', 'updraftplus').": plugins-old: "; - if (!$wp_filesystem->delete($plugs.'-old', true)) { - $ret3 = false; - echo "".__('Failed', 'updraftplus')."
    "; - echo $updraftplus->log_permission_failure_message($wp_filesystem->wp_content_dir(), 'Delete '.$plugs.'-old'); - } else { - $ret3 = true; - echo "".__('OK', 'updraftplus')."
    "; - } - } else { - $ret3 = true; - } - - return $ret && $ret3 && $ret4; - } - - private function delete_old_dirs_dir($dir, $wpfs = true) { - - $dir = trailingslashit($dir); - - global $wp_filesystem, $updraftplus; - - if ($wpfs) { - $list = $wp_filesystem->dirlist($dir); - } else { - $list = scandir($dir); - } - if (!is_array($list)) return false; - - $ret = true; - foreach ($list as $item) { - $name = (is_array($item)) ? $item['name'] : $item; - if ("-old" == substr($name, -4, 4)) { - // recursively delete - print "".__('Delete', 'updraftplus').": ".htmlspecialchars($name).": "; - - if ($wpfs) { - if (!$wp_filesystem->delete($dir.$name, true)) { - $ret = false; - echo "".__('Failed', 'updraftplus')."
    "; - echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name); - } else { - echo "".__('OK', 'updraftplus')."
    "; - } - } else { - if (UpdraftPlus_Filesystem_Functions::remove_local_directory($dir.$name)) { - echo "".__('OK', 'updraftplus')."
    "; - } else { - $ret = false; - echo "".__('Failed', 'updraftplus')."
    "; - echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name); - } - } - } - } - return $ret; - } - - /** - * The aim is to get a directory that is writable by the webserver, because that's the only way we can create zip files - * - * @return Boolean|WP_Error true if successful, otherwise false or a WP_Error - */ - private function create_backup_dir() { - - global $wp_filesystem, $updraftplus; - - if (false === ($credentials = request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir')))) { - return false; - } - - if (!WP_Filesystem($credentials)) { - // our credentials were no good, ask the user for them again - request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir'), '', true); - return false; - } - - $updraft_dir = $updraftplus->backups_dir_location(); - - $default_backup_dir = $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir); - - $updraft_dir = ($updraft_dir) ? $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir) : $default_backup_dir; - - if (!$wp_filesystem->is_dir($default_backup_dir) && !$wp_filesystem->mkdir($default_backup_dir, 0775)) { - $wperr = new WP_Error; - if ($wp_filesystem->errors->get_error_code()) { - foreach ($wp_filesystem->errors->get_error_messages() as $message) { - $wperr->add('mkdir_error', $message); - } - return $wperr; - } else { - return new WP_Error('mkdir_error', __('The request to the filesystem to create the directory failed.', 'updraftplus')); - } - } - - if ($wp_filesystem->is_dir($default_backup_dir)) { - - if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true; - - @$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true; - - @$wp_filesystem->chmod($default_backup_dir, 0777);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) { - echo '

    '.__('The folder was created, but we had to change its file permissions to 777 (world-writable) to be able to write to it. You should check with your hosting provider that this will not cause any problems', 'updraftplus').'

    '; - return true; - } else { - @$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $show_dir = (0 === strpos($default_backup_dir, ABSPATH)) ? substr($default_backup_dir, strlen(ABSPATH)) : $default_backup_dir; - return new WP_Error('writable_error', __('The folder exists, but your webserver does not have permission to write to it.', 'updraftplus').' '.__('You will need to consult with your web hosting provider to find out how to set permissions for a WordPress plugin to write to the directory.', 'updraftplus').' ('.$show_dir.')'); - } - } - - return true; - } - - /** - * scans the content dir to see if any -old dirs are present - * - * @param Boolean $print_as_comment Echo information in an HTML comment - * @return Boolean - */ - private function scan_old_dirs($print_as_comment = false) { - global $updraftplus; - $dirs = scandir(untrailingslashit(WP_CONTENT_DIR)); - if (!is_array($dirs)) $dirs = array(); - $dirs_u = @scandir($updraftplus->backups_dir_location());// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (!is_array($dirs_u)) $dirs_u = array(); - foreach (array_merge($dirs, $dirs_u) as $dir) { - if (preg_match('/-old$/', $dir)) { - if ($print_as_comment) echo ''; - return true; - } - } - // No need to scan ABSPATH - we don't backup there - if (is_dir(untrailingslashit(WP_PLUGIN_DIR).'-old')) { - if ($print_as_comment) echo ''; - return true; - } - return false; - } - - /** - * Outputs html for a storage method using the parameters passed in, this version should be removed when all remote storages use the multi version - * - * @param String $method a list of methods to be used when - * @param String $header the table header content - * @param String $contents the table contents - */ - public function storagemethod_row($method, $header, $contents) { - ?> - - - - - - - - - - - '.$header.' - '.$contents.' - '; - } - - /** - * Get HTML suitable for the admin area for the status of the last backup - * - * @return String - */ - public function last_backup_html() { - - global $updraftplus; - - $updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup'); - - if ($updraft_last_backup) { - - // Convert to GMT, then to blog time - $backup_time = (int) $updraft_last_backup['backup_time']; - - $print_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $backup_time), 'D, F j, Y H:i'); - - if (empty($updraft_last_backup['backup_time_incremental'])) { - $last_backup_text = "".$print_time.''; - } else { - $inc_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraft_last_backup['backup_time_incremental']), 'D, F j, Y H:i'); - $last_backup_text = "$inc_time (".sprintf(__('incremental backup; base backup: %s', 'updraftplus'), $print_time).')'; - } - - $last_backup_text .= '
    '; - - // Show errors + warnings - if (is_array($updraft_last_backup['errors'])) { - foreach ($updraft_last_backup['errors'] as $err) { - $level = (is_array($err)) ? $err['level'] : 'error'; - $message = (is_array($err)) ? $err['message'] : $err; - $last_backup_text .= ('warning' == $level) ? "" : ""; - if ('warning' == $level) { - $message = sprintf(__("Warning: %s", 'updraftplus'), make_clickable(htmlspecialchars($message))); - } else { - $message = htmlspecialchars($message); - } - $last_backup_text .= $message; - $last_backup_text .= '
    '; - } - } - - // Link log - if (!empty($updraft_last_backup['backup_nonce'])) { - $updraft_dir = $updraftplus->backups_dir_location(); - - $potential_log_file = $updraft_dir."/log.".$updraft_last_backup['backup_nonce'].".txt"; - if (is_readable($potential_log_file)) $last_backup_text .= "".__('Download log file', 'updraftplus').""; - } - - } else { - $last_backup_text = "".__('No backup has been completed', 'updraftplus').""; - } - - return $last_backup_text; - - } - - /** - * Get a list of backup intervals - * - * @param String $what_for - 'files' or 'db' - * - * @return Array - keys are used as identifiers in the UI drop-down; values are user-displayed text describing the interval - */ - public function get_intervals($what_for = 'db') { - global $updraftplus; - - if ($updraftplus->is_restricted_hosting('only_one_backup_per_month')) { - $intervals = array( - 'manual' => _x('Manual', 'i.e. Non-automatic', 'updraftplus'), - 'monthly' => __('Monthly', 'updraftplus') - ); - } else { - $intervals = array( - 'manual' => _x('Manual', 'i.e. Non-automatic', 'updraftplus'), - 'everyhour' => __('Every hour', 'updraftplus'), - 'every2hours' => sprintf(__('Every %s hours', 'updraftplus'), '2'), - 'every4hours' => sprintf(__('Every %s hours', 'updraftplus'), '4'), - 'every8hours' => sprintf(__('Every %s hours', 'updraftplus'), '8'), - 'twicedaily' => sprintf(__('Every %s hours', 'updraftplus'), '12'), - 'daily' => __('Daily', 'updraftplus'), - 'weekly' => __('Weekly', 'updraftplus'), - 'fortnightly' => __('Fortnightly', 'updraftplus'), - 'monthly' => __('Monthly', 'updraftplus'), - ); - - if ('files' == $what_for) unset($intervals['everyhour']); - } - - return apply_filters('updraftplus_backup_intervals', $intervals, $what_for); - } - - public function really_writable_message($really_is_writable, $updraft_dir) { - if ($really_is_writable) { - $dir_info = ''.__('Backup directory specified is writable, which is good.', 'updraftplus').''; - } else { - $dir_info = ''; - if (!is_dir($updraft_dir)) { - $dir_info .= __('Backup directory specified does not exist.', 'updraftplus'); - } else { - $dir_info .= __('Backup directory specified exists, but is not writable.', 'updraftplus'); - } - $dir_info .= ''.__('Follow this link to attempt to create the directory and set the permissions', 'updraftplus').', '.__('or, to reset this option', 'updraftplus').' '.__('press here', 'updraftplus').'. '.__('If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.', 'updraftplus').''; - } - return $dir_info; - } - - /** - * Directly output the settings form (suitable for the admin area) - * - * @param Array $options current options (passed on to the template) - */ - public function settings_formcontents($options = array()) { - $this->include_template('wp-admin/settings/form-contents.php', false, array( - 'options' => $options - )); - if (!(defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND)) { - $this->include_template('wp-admin/settings/exclude-modal.php', false); - } - } - - public function get_settings_js($method_objects, $really_is_writable, $updraft_dir, $active_service) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use - - global $updraftplus; - - ob_start(); - ?> - jQuery(function() { - - backup_methods as $method => $description) { - // already done: require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php'); - $call_method = "UpdraftPlus_BackupModule_$method"; - if (method_exists($call_method, 'config_print_javascript_onready')) { - $method_objects[$method]->config_print_javascript_onready(); - } - } - ?> - }); - get_backupable_file_entities(true, true); - // The true (default value if non-existent) here has the effect of forcing a default of on. - $include_more_paths = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path'); - foreach ($backupable_entities as $key => $info) { - $included = (UpdraftPlus_Options::get_updraft_option("updraft_include_$key", apply_filters("updraftplus_defaultoption_include_".$key, true))) ? 'checked="checked"' : ""; - if ('others' == $key || 'uploads' == $key) { - - $data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : ''; - - $ret .= ''; - - if ($show_exclusion_options) { - $include_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_'.$key.'_exclude', ('others' == $key) ? UPDRAFT_DEFAULT_OTHERS_EXCLUDE : UPDRAFT_DEFAULT_UPLOADS_EXCLUDE); - - $display = ($included) ? '' : 'class="updraft-hidden" style="display:none;"'; - $exclude_container_class = $prefix.'updraft_include_'.$key.'_exclude'; - if (!$for_updraftcentral) $exclude_container_class .= '_container'; - - $ret .= "
    "; - - $ret .= ''; - - $exclude_input_type = $for_updraftcentral ? "text" : "hidden"; - $exclude_input_extra_attr = $for_updraftcentral ? 'title="'.__('If entering multiple files/directories, then separate them with commas. For entities at the top level, you can use a * at the start or end of the entry as a wildcard.', 'updraftplus').'" size="54"' : ''; - $ret .= ''; - - if (!$for_updraftcentral) { - global $updraftplus; - $backupable_file_entities = $updraftplus->get_backupable_file_entities(); - - if ('uploads' == $key) { - $path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['uploads']); - } elseif ('others' == $key) { - $path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['others']); - } - $ret .= $this->include_template('wp-admin/settings/file-backup-exclude.php', true, array( - 'key' => $key, - 'include_exclude' => $include_exclude, - 'path' => $path, - 'show_exclusion_options' => $show_exclusion_options, - )); - } - $ret .= '
    '; - } - - } else { - - if ('more' != $key || true === $include_more || ('sometimes' === $include_more && !empty($include_more_paths))) { - - $data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : ''; - - $ret .= ""; - $ret .= apply_filters("updraftplus_config_option_include_$key", '', $prefix, $for_updraftcentral); - } - } - } - - return $ret; - } - - /** - * Output or echo HTML for an error condition relating to a remote storage method - * - * @param String $text - the text of the message; this should already be escaped (no more is done) - * @param String $extraclass - a CSS class for the resulting DOM node - * @param Integer $echo - if set, then the results will be echoed as well as returned - * - * @return String - the results - */ - public function show_double_warning($text, $extraclass = '', $echo = true) { - - $ret = "

    $text

    "; - $ret .= "

    $text

    "; - - if ($echo) echo $ret; - return $ret; - - } - - public function optionfilter_split_every($value) { - return max(absint($value), UPDRAFTPLUS_SPLIT_MIN); - } - - /** - * Check if curl exists; if not, print or return appropriate error messages - * - * @param String $service the service description (used only for user-visible messages - so, use the description) - * @param Boolean $has_fallback set as true if the lack of Curl only affects the ability to connect over SSL - * @param String $extraclass an extra CSS class for any resulting message, passed on to show_double_warning() - * @param Boolean $echo_instead_of_return whether the result should be echoed or returned - * @return String any resulting message, if $echo_instead_of_return was set - */ - public function curl_check($service, $has_fallback = false, $extraclass = '', $echo_instead_of_return = true) { - - $ret = ''; - - // Check requirements - if (!function_exists("curl_init") || !function_exists('curl_exec')) { - - $ret .= $this->show_double_warning(''.__('Warning', 'updraftplus').': '.sprintf(__("Your web server's PHP installation does not included a required (for %s) module (%s). Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'), $service, 'Curl').' ', $extraclass, false); - - } else { - $curl_version = curl_version(); - $curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL); - if (!$curl_ssl_supported) { - if ($has_fallback) { - $ret .= '

    '.__('Warning', 'updraftplus').': '.sprintf(__("Your web server's PHP/Curl installation does not support https access. Communications with %s will be unencrypted. Ask your web host to install Curl/SSL in order to gain the ability for encryption (via an add-on).", 'updraftplus'), $service).'

    '; - } else { - $ret .= $this->show_double_warning('

    '.__('Warning', 'updraftplus').': '.sprintf(__("Your web server's PHP/Curl installation does not support https access. We cannot access %s without this support. Please contact your web hosting provider's support. %s requires Curl+https. Please do not file any support requests; there is no alternative.", 'updraftplus'), $service, $service).'

    ', $extraclass, false); - } - } else { - $ret .= '

    '.sprintf(__("Good news: Your site's communications with %s can be encrypted. If you see any errors to do with encryption, then look in the 'Expert Settings' for more help.", 'updraftplus'), $service).'

    '; - } - } - if ($echo_instead_of_return) { - echo $ret; - } else { - return $ret; - } - } - - /** - * Get backup information in HTML format for a specific backup - * - * @param Array $backup_history all backups history - * @param String $key backup timestamp - * @param String $nonce backup nonce (job ID) - * @param Array|Null $job_data if an array, then use this as the job data (if null, then it will be fetched directly) - * - * @return string HTML-formatted backup information - */ - public function raw_backup_info($backup_history, $key, $nonce, $job_data = null) { - - global $updraftplus; - - $backup = $backup_history[$key]; - - $only_remote_sent = !empty($backup['service']) && (array('remotesend') === $backup['service'] || 'remotesend' === $backup['service']); - - $pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $key), 'M d, Y G:i'); - - $rawbackup = "

    $pretty_date

    "; - - if (!empty($backup['label'])) $rawbackup .= ''.$backup['label'].''; - - if (null === $job_data) $job_data = empty($nonce) ? array() : $updraftplus->jobdata_getarray($nonce); - - if (!$only_remote_sent) { - $rawbackup .= '
    '; - $rawbackup .= ''; - } - - $rawbackup .= '

    '; - - $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); - - $checksums = $updraftplus->which_checksums(); - - foreach ($backupable_entities as $type => $info) { - if (!isset($backup[$type])) continue; - - $rawbackup .= $updraftplus->printfile($info['description'], $backup, $type, $checksums, $job_data, true); - } - - $total_size = 0; - foreach ($backup as $ekey => $files) { - if ('db' == strtolower(substr($ekey, 0, 2)) && '-size' != substr($ekey, -5, 5)) { - $rawbackup .= $updraftplus->printfile(__('Database', 'updraftplus'), $backup, $ekey, $checksums, $job_data, true); - } - if (!isset($backupable_entities[$ekey]) && ('db' != substr($ekey, 0, 2) || '-size' == substr($ekey, -5, 5))) continue; - if (is_string($files)) $files = array($files); - foreach ($files as $findex => $file) { - $size_key = (0 == $findex) ? $ekey.'-size' : $ekey.$findex.'-size'; - $total_size = (false === $total_size || !isset($backup[$size_key]) || !is_numeric($backup[$size_key])) ? false : $total_size + $backup[$size_key]; - } - } - - $services = empty($backup['service']) ? array('none') : $backup['service']; - if (!is_array($services)) $services = array('none'); - - $rawbackup .= ''.__('Uploaded to:', 'updraftplus').' '; - - $show_services = ''; - foreach ($services as $serv) { - if ('none' == $serv || '' == $serv) { - $add_none = true; - } elseif (isset($updraftplus->backup_methods[$serv])) { - $show_services .= $show_services ? ', '.$updraftplus->backup_methods[$serv] : $updraftplus->backup_methods[$serv]; - } else { - $show_services .= $show_services ? ', '.$serv : $serv; - } - } - if ('' == $show_services && $add_none) $show_services .= __('None', 'updraftplus'); - - $rawbackup .= $show_services; - - if (false !== $total_size) { - $rawbackup .= '

    '.__('Total backup size:', 'updraftplus').' '.UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_size).'

    '; - } - - $rawbackup .= '


    '.print_r($backup, true).'

    '; - - if (!empty($job_data) && is_array($job_data)) { - $rawbackup .= '

    '.htmlspecialchars(print_r($job_data, true)).'

    '; - } - - return esc_attr($rawbackup); - } - - private function download_db_button($bkey, $key, $esc_pretty_date, $backup, $accept = array()) { - - if (!empty($backup['meta_foreign']) && isset($accept[$backup['meta_foreign']])) { - $desc_source = $accept[$backup['meta_foreign']]['desc']; - } else { - $desc_source = __('unknown source', 'updraftplus'); - } - - $ret = ''; - - if ('db' == $bkey) { - $dbt = empty($backup['meta_foreign']) ? esc_attr(__('Database', 'updraftplus')) : esc_attr(sprintf(__('Database (created by %s)', 'updraftplus'), $desc_source)); - } else { - $dbt = __('External database', 'updraftplus').' ('.substr($bkey, 2).')'; - } - - $ret .= $this->download_button($bkey, $key, 0, null, '', $dbt, $esc_pretty_date, '0'); - - return $ret; - } - - /** - * Go through each of the file entities - * - * @param Array $backup An array of meta information - * @param Integer $key Backup timestamp (epoch time) - * @param Array $accept An array of values to be accepted from vaules within $backup - * @param String $entities Entities to be added - * @param String $esc_pretty_date Whether the button needs to escape the pretty date format - * @return String - the resulting HTML - */ - public function download_buttons($backup, $key, $accept, &$entities, $esc_pretty_date) { - global $updraftplus; - $ret = ''; - $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); - - $first_entity = true; - - foreach ($backupable_entities as $type => $info) { - if (!empty($backup['meta_foreign']) && 'wpcore' != $type) continue; - - $ide = ''; - - if (empty($backup['meta_foreign'])) { - $sdescrip = preg_replace('/ \(.*\)$/', '', $info['description']); - if (strlen($sdescrip) > 20 && isset($info['shortdescription'])) $sdescrip = $info['shortdescription']; - } else { - $info['description'] = 'WordPress'; - - if (isset($accept[$backup['meta_foreign']])) { - $desc_source = $accept[$backup['meta_foreign']]['desc']; - $ide .= sprintf(__('Backup created by: %s.', 'updraftplus'), $accept[$backup['meta_foreign']]['desc']).' '; - } else { - $desc_source = __('unknown source', 'updraftplus'); - $ide .= __('Backup created by unknown source (%s) - cannot be restored.', 'updraftplus').' '; - } - - $sdescrip = (empty($accept[$backup['meta_foreign']]['separatedb'])) ? sprintf(__('Files and database WordPress backup (created by %s)', 'updraftplus'), $desc_source) : sprintf(__('Files backup (created by %s)', 'updraftplus'), $desc_source); - } - if (isset($backup[$type])) { - if (!is_array($backup[$type])) $backup[$type] = array($backup[$type]); - $howmanyinset = count($backup[$type]); - $expected_index = 0; - $index_missing = false; - $set_contents = ''; - $entities .= "/$type="; - $whatfiles = $backup[$type]; - ksort($whatfiles); - foreach ($whatfiles as $findex => $bfile) { - $set_contents .= ('' == $set_contents) ? $findex : ",$findex"; - if ($findex != $expected_index) $index_missing = true; - $expected_index++; - } - $entities .= $set_contents.'/'; - if (!empty($backup['meta_foreign'])) { - $entities .= '/plugins=0//themes=0//uploads=0//others=0/'; - } - $printing_first = true; - $total_file_size = 0; - foreach ($whatfiles as $findex => $bfile) { - - $pdescrip = ($findex > 0) ? $sdescrip.' ('.($findex+1).')' : $sdescrip; - if ($printing_first) { - $ide .= __('Press here to download or browse', 'updraftplus').' '.strtolower($info['description']); - } else { - $ret .= ''; - } else { - $printing_first = false; - } - } - $ret = str_replace('%UP_backups_total_file_size%', UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_file_size), $ret); - } - } - return $ret; - } - - public function date_label($pretty_date, $key, $backup, $jobdata, $nonce, $simple_format = false) { - - $pretty_date = $simple_format ? $pretty_date : '
    '.$pretty_date.'
    '; - - $ret = apply_filters('updraftplus_showbackup_date', $pretty_date, $backup, $jobdata, (int) $key, $simple_format); - if (is_array($jobdata) && !empty($jobdata['resume_interval']) && (empty($jobdata['jobstatus']) || 'finished' != $jobdata['jobstatus'])) { - if ($simple_format) { - $ret .= ' '.__('(Not finished)', 'updraftplus'); - } else { - $ret .= apply_filters('updraftplus_msg_unfinishedbackup', "
    ".__('(Not finished)', 'updraftplus').'', $jobdata, $nonce); - } - } - return $ret; - } - - public function download_button($type, $backup_timestamp, $findex, $info, $title, $pdescrip, $esc_pretty_date, $set_contents) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use - - $ret = ''; - - $wp_nonce = wp_create_nonce('updraftplus_download'); - - // updraft_downloader(base, backup_timestamp, what, whicharea, set_contents, prettydate, async) - $ret .= ''; - // onclick="'."return updraft_downloader('uddlstatus_', '$backup_timestamp', '$type', '.ud_downloadstatus', '$set_contents', '$esc_pretty_date', true)".'" - - return $ret; - } - - public function restore_button($backup, $key, $pretty_date, $entities = '') { - $ret = '
    '; - - if ($entities) { - $show_data = $pretty_date; - if (isset($backup['native']) && false == $backup['native']) { - $show_data .= ' '.__('(backup set imported from remote location)', 'updraftplus'); - } - - $ret .= ''; - } - $ret .= "
    \n"; - return $ret; - } - - /** - * Get HTML for the 'Upload' button for a particular backup in the 'Existing backups' tab - * - * @param Integer $backup_time - backup timestamp (epoch time) - * @param String $nonce - backup nonce - * @param Array $backup - backup information array - * @param Null|Array $jobdata - if not null, then use as the job data instead of fetching - * - * @return String - the resulting HTML - */ - public function upload_button($backup_time, $nonce, $backup, $jobdata = null) { - global $updraftplus; - - // Check the job is not still running. - if (null === $jobdata) $jobdata = $updraftplus->jobdata_getarray($nonce); - - if (!empty($jobdata) && 'finished' != $jobdata['jobstatus']) return ''; - - // Check that the user has remote storage setup. - $services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list()); - if (empty($services)) return ''; - - $show_upload = false; - - // Check that the backup has not already been sent to remote storage before. - if (empty($backup['service']) || array('none') == $backup['service'] || array('') == $backup['service'] || 'none' == $backup['service']) { - $show_upload = true; - // If it has been uploaded then check if there are any new remote storage options that it has not yet been sent to. - } elseif (!empty($backup['service']) && array('none') != $backup['service'] && array('') != $backup['service'] && 'none' != $backup['service']) { - - foreach ($services as $key => $value) { - if (in_array($value, $backup['service'])) unset($services[$key]); - } - - if (!empty($services)) $show_upload = true; - } - - if ($show_upload) { - - $backup_local = $this->check_backup_is_complete($backup, false, false, true); - - if ($backup_local) { - $service_list = ''; - $service_list_display = ''; - $is_first_service = true; - - foreach ($services as $key => $service) { - if (!$is_first_service) { - $service_list .= ','; - $service_list_display .= ', '; - } - $service_list .= $service; - $service_list_display .= $updraftplus->backup_methods[$service]; - - $is_first_service = false; - } - - return '
    - -
    '; - } - - return ''; - } - } - - /** - * Get HTML for the 'Delete' button for a particular backup in the 'Existing backups' tab - * - * @param Integer $backup_time - backup timestamp (epoch time) - * @param String $nonce - backup nonce - * @param Array $backup - backup information array - * - * @return String - the resulting HTML - */ - public function delete_button($backup_time, $nonce, $backup) { - $sval = (!empty($backup['service']) && 'email' != $backup['service'] && 'none' != $backup['service'] && array('email') !== $backup['service'] && array('none') !== $backup['service'] && array('remotesend') !== $backup['service']) ? '1' : '0'; - return ''; - } - - public function log_button($backup) { - global $updraftplus; - $updraft_dir = $updraftplus->backups_dir_location(); - $ret = ''; - if (isset($backup['nonce']) && preg_match("/^[0-9a-f]{12}$/", $backup['nonce']) && is_readable($updraft_dir.'/log.'.$backup['nonce'].'.txt')) { - $nval = $backup['nonce']; - $lt = __('View Log', 'updraftplus'); - $url = esc_attr(UpdraftPlus_Options::admin_page()."?page=updraftplus&action=downloadlog&updraftplus_backup_nonce=$nval"); - $ret .= << - - $lt - - -
    -ENDHERE; - return $ret; - } - } - - /** - * This function will check that a backup is complete depending on the parameters passed in. - * A backup is complete in the case of a "clone" if it contains a db, plugins, themes, uploads and others. - * A backup is complete in the case of a "full backup" when it contains everything the user has set in their options to be backed up. - * It can also check if the backup is local on the filesystem. - * - * @param array $backup - the backup array we want to check - * @param boolean $full_backup - a boolean to indicate if the backup should also be a full backup - * @param boolean $clone - a boolean to indicate if the backup is for a clone, if so it does not need to be a full backup it only needs to include everything a clone can restore - * @param boolean $local - a boolean to indicate if the backup should be present on the local file system or not - * - * @return boolean - returns true if the backup is complete and if specified is found on the local system otherwise false - */ - private function check_backup_is_complete($backup, $full_backup, $clone, $local) { - - global $updraftplus; - - if (empty($backup)) return false; - - if ($clone) { - $entities = array('db' => '', 'plugins' => '', 'themes' => '', 'uploads' => '', 'others' => ''); - } else { - $entities = $updraftplus->get_backupable_file_entities(true, true); - - // Add the database to the entities array ready to loop over - $entities['db'] = ''; - - foreach ($entities as $key => $info) { - if (!UpdraftPlus_Options::get_updraft_option("updraft_include_$key", false)) { - unset($entities[$key]); - } - } - } - - $updraft_dir = trailingslashit($updraftplus->backups_dir_location()); - - foreach ($entities as $type => $info) { - - if ($full_backup) { - if (UpdraftPlus_Options::get_updraft_option("updraft_include_$type", false) && !isset($backup[$type])) return false; - } - - if (!isset($backup[$type])) return false; - - if ($local) { - // Cast this to an array so that a warning is not thrown when we encounter a Database. - foreach ((array) $backup[$type] as $value) { - if (!file_exists($updraft_dir . DIRECTORY_SEPARATOR . $value)) return false; - } - } - } - - return true; - } - - /** - * This function will set up the backup job data for when we are uploading a local backup to remote storage. It changes the initial jobdata so that UpdraftPlus knows about what files it's uploading and so that it skips directly to the upload stage. - * - * @param array $jobdata - the initial job data that we want to change - * @param array $options - options sent from the front end includes backup timestamp and nonce - * - * @return array - the modified jobdata - */ - public function upload_local_backup_jobdata($jobdata, $options) { - global $updraftplus; - - if (!is_array($jobdata)) return $jobdata; - - $backup_history = UpdraftPlus_Backup_History::get_history(); - $services = !empty($options['services']) ? $options['services'] : array(); - $backup = $backup_history[$options['use_timestamp']]; - - /* - The initial job data is not set up in a key value array instead it is set up so key "x" is the name of the key and then key "y" is the value. - e.g array[0] = 'backup_name' array[1] = 'my_backup' - */ - $jobstatus_key = array_search('jobstatus', $jobdata) + 1; - $backup_time_key = array_search('backup_time', $jobdata) + 1; - $backup_database_key = array_search('backup_database', $jobdata) + 1; - $backup_files_key = array_search('backup_files', $jobdata) + 1; - $service_key = array_search('service', $jobdata) + 1; - - $db_backups = $jobdata[$backup_database_key]; - $db_backup_info = $updraftplus->update_database_jobdata($db_backups, $backup); - $file_backups = $updraftplus->update_files_jobdata($backup); - - // Next we need to build the services array using the remote storage destinations the user has selected to upload this backup set to - $selected_services = array(); - - foreach ($services as $storage_info) { - $selected_services[] = $storage_info['value']; - } - - $jobdata[$jobstatus_key] = 'clouduploading'; - $jobdata[$backup_time_key] = $options['use_timestamp']; - $jobdata[$backup_files_key] = 'finished'; - $jobdata[] = 'backup_files_array'; - $jobdata[] = $file_backups; - $jobdata[] = 'blog_name'; - $jobdata[] = $db_backup_info['blog_name']; - $jobdata[$backup_database_key] = $db_backup_info['db_backups']; - $jobdata[] = 'local_upload'; - $jobdata[] = true; - if (!empty($selected_services)) $jobdata[$service_key] = $selected_services; - - - return $jobdata; - } - - /** - * This function allows us to change the backup name, this is needed when uploading a local database backup to remote storage when the backup has come from another site. - * - * @param string $backup_name - the current name of the backup file - * @param string $use_time - the current timestamp we are using - * @param string $blog_name - the blog name of the current site - * - * @return string - the new filename or the original if the blog name from the job data is not set - */ - public function upload_local_backup_name($backup_name, $use_time, $blog_name) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use - global $updraftplus; - - $backup_blog_name = $updraftplus->jobdata_get('blog_name', ''); - - if ('' != $blog_name && '' != $backup_blog_name) { - return str_replace($blog_name, $backup_blog_name, $backup_name); - } - - return $backup_name; - } - - /** - * This function starts the updraftplus restore process it processes $_REQUEST - * (keys: updraft_*, meta_foreign, backup_timestamp and job_id) - * - * @return void - */ - public function prepare_restore() { - - global $updraftplus; - - // on restore start job_id is empty but if we needed file system permissions or this is a resumption then we have already started a job so reuse it - $restore_job_id = empty($_REQUEST['job_id']) ? false : $_REQUEST['job_id']; - - // Set up nonces, log files etc. - $updraftplus->initiate_restore_job($restore_job_id); - - // If this is the start of a restore then get the restore data from the posted data and put it into jobdata. - if (isset($_REQUEST['action']) && 'updraft_restore' == $_REQUEST['action']) { - - if (empty($restore_job_id)) { - $jobdata_to_save = array(); - foreach ($_REQUEST as $key => $value) { - if (false !== strpos($key, 'updraft_') || 'backup_timestamp' == $key || 'meta_foreign' == $key) { - if ('updraft_restorer_restore_options' == $key) parse_str(stripslashes($value), $value); - $jobdata_to_save[$key] = $value; - } - } - - if (isset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_table_options']) && !empty($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_table_options'])) { - - $restore_table_options = $jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_table_options']; - - $include_unspecified_tables = false; - $tables_to_restore = array(); - $tables_to_skip = array(); - - foreach ($restore_table_options as $table) { - if ('udp_all_other_tables' == $table) { - $include_unspecified_tables = true; - } elseif ('udp-skip-table-' == substr($table, 0, 15)) { - $tables_to_skip[] = substr($table, 15); - } else { - $tables_to_restore[] = $table; - } - } - - $jobdata_to_save['updraft_restorer_restore_options']['include_unspecified_tables'] = $include_unspecified_tables; - $jobdata_to_save['updraft_restorer_restore_options']['tables_to_restore'] = $tables_to_restore; - $jobdata_to_save['updraft_restorer_restore_options']['tables_to_skip'] = $tables_to_skip; - unset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_table_options']); - } - - $updraftplus->jobdata_set_multi($jobdata_to_save); - - // Use a site option, as otherwise on multisite when all the array of options is updated via UpdraftPlus_Options::update_site_option(), it will over-write any restored UD options from the backup - update_site_option('updraft_restore_in_progress', $updraftplus->nonce); - } - } - - // If this is the start of an ajax restore then end execution here so it can then be booted over ajax - if (isset($_REQUEST['updraftplus_ajax_restore']) && 'start_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) { - // return to prevent any more code from running - return $this->prepare_ajax_restore(); - - } elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) { - // If we enter here then in order to restore we needed to require the filesystem credentials we should save these before returning back to the browser and load them back after the AJAX call, this prevents us asking for the filesystem credentials again - $filesystem_credentials = array( - 'hostname' => '', - 'username' => '', - 'password' => '', - 'connection_type' => '', - 'upgrade' => '', - ); - - $credentials_found = false; - - foreach ($_REQUEST as $key => $value) { - if (array_key_exists($key, $filesystem_credentials)) { - $filesystem_credentials[$key] = stripslashes($value); - $credentials_found = true; - } - } - - if ($credentials_found) $updraftplus->jobdata_set('filesystem_credentials', $filesystem_credentials); - - // return to prevent any more code from running - return $this->prepare_ajax_restore(); - } - - if (!empty($_REQUEST['updraftplus_ajax_restore'])) add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 5); - - $is_continuation = ('updraft_ajaxrestore_continue' == $_REQUEST['action']) ? true : false; - - if ($is_continuation) { - $restore_in_progress = get_site_option('updraft_restore_in_progress'); - if ($restore_in_progress != $_REQUEST['job_id']) { - $abort_restore_already = true; - $updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_mismatch)', 'error', 'job_id_mismatch'); - } else { - $restore_jobdata = $updraftplus->jobdata_getarray($restore_in_progress); - if (is_array($restore_jobdata) && isset($restore_jobdata['job_type']) && 'restore' == $restore_jobdata['job_type'] && isset($restore_jobdata['second_loop_entities']) && !empty($restore_jobdata['second_loop_entities']) && isset($restore_jobdata['job_time_ms']) && isset($restore_jobdata['backup_timestamp'])) { - $backup_timestamp = $restore_jobdata['backup_timestamp']; - $continuation_data = $restore_jobdata; - $continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore'; - } else { - $abort_restore_already = true; - $updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_nojobdata)', 'error', 'job_id_nojobdata'); - } - } - } elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'do_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) { - $backup_timestamp = $updraftplus->jobdata_get('backup_timestamp'); - $continuation_data = array('updraftplus_ajax_restore' => 'do_ajax_restore'); - } else { - $backup_timestamp = $_REQUEST['backup_timestamp']; - $continuation_data = null; - } - - $filesystem_credentials = $updraftplus->jobdata_get('filesystem_credentials', array()); - - if (!empty($filesystem_credentials)) { - $continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore'; - // If the filesystem credentials are not empty then we now need to load these back into $_POST so that WP_Filesystem can access them - foreach ($filesystem_credentials as $key => $value) { - $_POST[$key] = $value; - } - } - - if (empty($abort_restore_already)) { - $backup_success = $this->restore_backup($backup_timestamp, $continuation_data); - } else { - $backup_success = false; - } - - if (empty($updraftplus->errors) && true === $backup_success) { - // TODO: Deal with the case of some of the work having been deferred - echo '

    '; - $updraftplus->log_e('Restore successful!'); - echo '

    '; - $updraftplus->log('Restore successful'); - $s_val = 1; - if (!empty($this->entities_to_restore) && is_array($this->entities_to_restore)) { - foreach ($this->entities_to_restore as $v) { - if ('db' != $v) $s_val = 2; - } - } - $pval = $updraftplus->have_addons ? 1 : 0; - - echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . ''; - return; - - } elseif (is_wp_error($backup_success)) { - echo '

    '; - $updraftplus->log_e('Restore failed...'); - echo '

    '; - $updraftplus->log_wp_error($backup_success); - $updraftplus->log('Restore failed'); - echo '
    '; - $updraftplus->list_errors(); - echo '
    '; - echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . ''; - return; - } elseif (false === $backup_success) { - // This means, "not yet - but stay on the page because we may be able to do it later, e.g. if the user types in the requested information" - echo '

    '; - $updraftplus->log_e('Restore failed...'); - echo '

    '; - $updraftplus->log("Restore failed"); - echo '
    '; - $updraftplus->list_errors(); - echo '
    '; - echo '' . __('Actions', 'updraftplus') . ': ' . __('Return to UpdraftPlus configuration', 'updraftplus') . ''; - return; - } - } - - /** - * This function will load the required ajax and output any relevant html for the ajax restore - * - * @return void - */ - private function prepare_ajax_restore() { - global $updraftplus; - - $debug = $updraftplus->use_unminified_scripts(); - $enqueue_version = $debug ? $updraftplus->version . '.' . time() : $updraftplus->version; - $updraft_min_or_not = $updraftplus->get_updraftplus_file_version(); - $ajax_action = isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore'] && 'updraft_restore' != $_REQUEST['action'] ? 'updraft_ajaxrestore_continue' : 'updraft_ajaxrestore'; - - // get the entities info - $jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce); - $restore_components = $jobdata['updraft_restore']; - usort($restore_components, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities')); - - $backupable_entities = $updraftplus->get_backupable_file_entities(true, true); - $pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_timestamp']), 'M d, Y G:i'); - - wp_enqueue_script('updraft-admin-restore', UPDRAFTPLUS_URL . '/js/updraft-admin-restore' . $updraft_min_or_not . '.js', array(), $enqueue_version); - - echo '
    '; - echo '
    '; - echo '

    '. __('Warning: If you can still read these words after the page finishes loading, then there is a JavaScript or jQuery problem in the site.', 'updraftplus') .' '.__('This may prevent the restore procedure from being able to proceed.', 'updraftplus').''; - echo ' '. __('Go here for more information.', 'updraftplus') .'

    '; - echo '
    '; - echo '
    '.__('UpdraftPlus Restoration', 'updraftplus').' - '.__('Backup', 'updraftplus').' '.$pretty_date.'
    '; - echo '
    '; - - if ($debug) echo ''; - echo ''; - echo ''; - echo ''; - - echo '
    '; - echo '

    '.sprintf(__('The restore operation has begun (%s). Do not close this page until it reports itself as having finished.', 'updraftplus'), $updraftplus->nonce).'

    '; - echo '

    '.__('Restoration progress:', 'updraftplus').'

    '; - echo '
    '; - echo '
      '; - echo '
    • '.__('Verifying', 'updraftplus').'
    • '; - foreach ($restore_components as $restore_component) { - // Set Database description - if ('db' == $restore_component && !isset($backupable_entities[$restore_component]['description'])) $backupable_entities[$restore_component]['description'] = __('Database', 'updraftplus'); - echo '
    • '.(isset($backupable_entities[$restore_component]['description']) ? $backupable_entities[$restore_component]['description'] : $restore_component).'
    • '; - } - echo '
    • '.__('Cleaning', 'updraftplus').'
    • '; - echo '
    • '.__('Finished', 'updraftplus').'
    • '; - echo '
    '; // end ul.updraft_restore_components_list - // Provide download link for the log file - echo '

    '.__('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus').'

    '; - echo '
    '; // end .updraft_restore_main--components - echo '
    '; - echo '

    '.__('Activity log', 'updraftplus').'

    '; - echo '
    '; - echo '
    '; // end .updraft_restore_main--activity - echo ' - '; - echo '
    '; // end .updraft_restore_main - echo '
    '; // end .updraft_restore_container - } - - /** - * Processes the jobdata to build an array of entities to restore. - * - * @param Array $backup_set - information on the backup to restore - * - * @return Array - the entities to restore built from the restore jobdata - */ - private function get_entities_to_restore_from_jobdata($backup_set) { - - global $updraftplus; - - $updraft_restore = $updraftplus->jobdata_get('updraft_restore'); - - if (empty($updraft_restore) || (!is_array($updraft_restore))) $updraft_restore = array(); - - $entities_to_restore = array(); - $foreign_known = apply_filters('updraftplus_accept_archivename', array()); - - foreach ($updraft_restore as $entity) { - if (empty($backup_set['meta_foreign'])) { - $entities_to_restore[$entity] = $entity; - } else { - if ('db' == $entity && !empty($foreign_known[$backup_set['meta_foreign']]) && !empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) { - $entities_to_restore[$entity] = 'db'; - } else { - $entities_to_restore[$entity] = 'wpcore'; - } - } - } - - return $entities_to_restore; - } - - /** - * Processes the jobdata to build an array of restoration options - * - * @return Array - the restore options built from the restore jobdata - */ - private function get_restore_options_from_jobdata() { - - global $updraftplus; - - $restore_options = $updraftplus->jobdata_get('updraft_restorer_restore_options'); - $updraft_encryptionphrase = $updraftplus->jobdata_get('updraft_encryptionphrase'); - $include_wpconfig = $updraftplus->jobdata_get('updraft_restorer_wpcore_includewpconfig'); - - $restore_options['updraft_encryptionphrase'] = empty($updraft_encryptionphrase) ? '' : $updraft_encryptionphrase; - - $restore_options['updraft_restorer_wpcore_includewpconfig'] = !empty($include_wpconfig); - - $restore_options['updraft_incremental_restore_point'] = empty($restore_options['updraft_incremental_restore_point']) ? -1 : (int) $restore_options['updraft_incremental_restore_point']; - - return $restore_options; - } - - /** - * Carry out the restore process within the WP admin dashboard, using data from $_POST - * - * @param Array $timestamp Identifying the backup to be restored - * @param Array|null $continuation_data For continuing a multi-stage restore; this is the saved jobdata for the job; in this method the keys used are second_loop_entities, restore_options; but it is also passed on to Updraft_Restorer::perform_restore() - * @return Boolean|WP_Error - a WP_Error indicates a terminal failure; false indicates not-yet complete (not necessarily terminal); true indicates complete. - */ - private function restore_backup($timestamp, $continuation_data = null) { - - global $updraftplus, $updraftplus_restorer; - - $second_loop_entities = empty($continuation_data['second_loop_entities']) ? array() : $continuation_data['second_loop_entities']; - - // If this is a resumption and we still need to restore the database we should rebuild the backup history to ensure the database is in there. - if (!empty($second_loop_entities['db'])) UpdraftPlus_Backup_History::rebuild(); - - $backup_set = UpdraftPlus_Backup_History::get_history($timestamp); - - if (empty($backup_set)) { - echo '

    '.__('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus')." $timestamp


    "; - return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')." ($timestamp)"); - } - - $backup_set['timestamp'] = $timestamp; - - $url_parameters = array( - 'backup_timestamp' => $timestamp, - 'job_id' => $updraftplus->nonce - ); - - if (!empty($continuation_data['updraftplus_ajax_restore'])) { - $url_parameters['updraftplus_ajax_restore'] = 'continue_ajax_restore'; - $updraftplus->output_to_browser(''); // Start timer - // Force output buffering off so that we get log lines sent to the browser as they come not all at once at the end of the ajax restore - // zlib creates an output buffer, and waits for the entire page to be generated before it can send it to the client try to turn it off - @ini_set("zlib.output_compression", 0);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // Turn off PHP output buffering for NGINX - header('X-Accel-Buffering: no'); - header('Content-Encoding: none'); - while (ob_get_level()) { - ob_end_flush(); - } - ob_implicit_flush(1); - } - - $updraftplus->log("Ensuring WP_Filesystem is setup for a restore"); - - // This will print HTML and die() if necessary - UpdraftPlus_Filesystem_Functions::ensure_wp_filesystem_set_up_for_restore($url_parameters); - - $updraftplus->log("WP_Filesystem is setup and ready for a restore"); - - $entities_to_restore = $this->get_entities_to_restore_from_jobdata($backup_set); - - if (empty($entities_to_restore)) { - $restore_jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce); - echo '

    '.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'

    '.__('If making a request for support, please include this information:', 'updraftplus').' '.count($restore_jobdata).' : '.htmlspecialchars(serialize($restore_jobdata)).'

    '; - return new WP_Error('missing_info', 'Backup information not found'); - } - - // This is used in painting the admin page after a successful restore - $this->entities_to_restore = $entities_to_restore; - - // This will be removed by Updraft_Restorer::post_restore_clean_up() - set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); - - // Set $restore_options, either from the continuation data, or from $_POST - if (!empty($continuation_data['restore_options'])) { - $restore_options = $continuation_data['restore_options']; - } else { - // Gather the restore options into one place - code after here should read the options - $restore_options = $this->get_restore_options_from_jobdata(); - $updraftplus->jobdata_set('restore_options', $restore_options); - } - - add_action('updraftplus_restoration_title', array($this, 'restoration_title')); - - $updraftplus->log_restore_update(array('type' => 'state', 'stage' => 'started', 'data' => array())); - - // We use a single object for each entity, because we want to store information about the backup set - $updraftplus_restorer = new Updraft_Restorer(new Updraft_Restorer_Skin, $backup_set, false, $restore_options, $continuation_data); - - $restore_result = $updraftplus_restorer->perform_restore($entities_to_restore, $restore_options); - - $updraftplus_restorer->post_restore_clean_up($restore_result); - - $pval = $updraftplus->have_addons ? 1 : 0; - $sval = (true === $restore_result) ? 1 : 0; - - $pages = get_pages(array('number' => 2)); - $page_urls = array( - 'home' => get_home_url(), - ); - - foreach ($pages as $page_info) { - $page_urls[$page_info->post_name] = get_page_link($page_info->ID); - } - - $updraftplus->log_restore_update( - array( - 'type' => 'state', - 'stage' => 'finished', - 'data' => array( - 'actions' => array( - __('Return to UpdraftPlus configuration', 'updraftplus') => UpdraftPlus_Options::admin_page_url() . '?page=updraftplus&updraft_restore_success=' . $sval . '&pval=' . $pval - ), - 'urls' => $page_urls, - ) - ) - ); - - return $restore_result; - } - - /** - * Called when the restore process wants to print a title - * - * @param String $title - title - */ - public function restoration_title($title) { - echo '

    '.$title.'

    '; - } - - /** - * Logs a line from the restore process, being called from UpdraftPlus::log(). - * Hooks the WordPress filter updraftplus_logline - * In future, this can get more sophisticated. For now, things are funnelled through here, giving the future possibility. - * - * @param String $line - the line to be logged - * @param String $nonce - the job ID of the restore job - * @param String $level - the level of the log notice - * @param String|Boolean $uniq_id - a unique ID for the log if it should only be logged once; or false otherwise - * @param String $destination - the type of job ongoing. If it is not 'restore', then we will skip the logging. - * - * @return String|Boolean - the filtered value. If set to false, then UpdraftPlus::log() will stop processing the log line. - */ - public function updraftplus_logline($line, $nonce, $level, $uniq_id, $destination) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found - - if ('progress' != $destination || (defined('WP_CLI') && WP_CLI) || false === $line || false === strpos($line, 'RINFO:')) return $line; - - global $updraftplus; - - $updraftplus->output_to_browser($line); - - // Indicate that we have completely handled all logging needed - return false; - } - - /** - * Ensure that what is returned is an array. Used as a WP options filter. - * - * @param Array $input - input - * - * @return Array - */ - public function return_array($input) { - return is_array($input) ? $input : array(); - } - - /** - * Called upon the WP action wp_ajax_updraft_savesettings. Will die(). - */ - public function updraft_ajax_savesettings() { - try { - if (empty($_POST) || empty($_POST['subaction']) || 'savesettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check'); - - if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data'); - - parse_str(stripslashes($_POST['settings']), $posted_settings); - // We now have $posted_settings as an array - if (!empty($_POST['updraftplus_version'])) $posted_settings['updraftplus_version'] = $_POST['updraftplus_version']; - - echo json_encode($this->save_settings($posted_settings)); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } - die; - } - - public function updraft_ajax_importsettings() { - try { - if (empty($_POST) || empty($_POST['subaction']) || 'importsettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check'); - - if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data'); - - $this->import_settings($_POST); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - echo json_encode(array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - )); - } - } - - /** - * This method handles the imported json settings it will convert them into a readable format for the existing save settings function, it will also update some of the options to match the new remote storage options format (Apr 2017) - * - * @param Array $settings - The settings from the imported json file - */ - public function import_settings($settings) { - // A bug in UD releases around 1.12.40 - 1.13.3 meant that it was saved in URL-string format, instead of JSON - $perhaps_not_yet_parsed = json_decode(stripslashes($settings['settings']), true); - - if (!is_array($perhaps_not_yet_parsed)) { - parse_str($perhaps_not_yet_parsed, $posted_settings); - } else { - $posted_settings = $perhaps_not_yet_parsed; - } - - if (!empty($settings['updraftplus_version'])) $posted_settings['updraftplus_version'] = $settings['updraftplus_version']; - - // Handle the settings name change of WebDAV and SFTP (Apr 2017) if someone tries to import an old settings to this version - if (isset($posted_settings['updraft_webdav_settings'])) { - $posted_settings['updraft_webdav'] = $posted_settings['updraft_webdav_settings']; - unset($posted_settings['updraft_webdav_settings']); - } - - if (isset($posted_settings['updraft_sftp_settings'])) { - $posted_settings['updraft_sftp'] = $posted_settings['updraft_sftp_settings']; - unset($posted_settings['updraft_sftp_settings']); - } - - // We also need to wrap some of the options in the new style settings array otherwise later on we will lose the settings if this information is missing - if (empty($posted_settings['updraft_webdav']['settings'])) $posted_settings['updraft_webdav'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_webdav']); - if (empty($posted_settings['updraft_googledrive']['settings'])) $posted_settings['updraft_googledrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googledrive']); - if (empty($posted_settings['updraft_googlecloud']['settings'])) $posted_settings['updraft_googlecloud'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googlecloud']); - if (empty($posted_settings['updraft_onedrive']['settings'])) $posted_settings['updraft_onedrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_onedrive']); - if (empty($posted_settings['updraft_azure']['settings'])) $posted_settings['updraft_azure'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_azure']); - if (empty($posted_settings['updraft_dropbox']['settings'])) $posted_settings['updraft_dropbox'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_dropbox']); - - echo json_encode($this->save_settings($posted_settings)); - - die; - } - - /** - * This function will get a list of remote storage methods with valid connection details and create a HTML list of checkboxes - * - * @return String - HTML checkbox list of remote storage methods with valid connection details - */ - private function backup_now_remote_message() { - global $updraftplus; - - $services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list()); - $all_services = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids($services); - $active_remote_storage_list = ''; - - foreach ($all_services as $method => $sinfo) { - if ('email' == $method) { - $possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email')); - if (!empty($possible_emails)) { - $active_remote_storage_list .= '
    '; - } - } elseif (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) { - continue; - } - - $instance_count = 1; - foreach ($sinfo['instance_settings'] as $instance => $opt) { - if ($sinfo['object']->options_exist($opt)) { - $instance_count_label = (1 == $instance_count) ? '' : ' ('.$instance_count.')'; - $label = empty($opt['instance_label']) ? $sinfo['object']->get_description() . $instance_count_label : $opt['instance_label']; - if (!isset($opt['instance_enabled'])) $opt['instance_enabled'] = 1; - $checked = empty($opt['instance_enabled']) ? '' : 'checked="checked"'; - $active_remote_storage_list .= '
    '; - $instance_count++; - } - } - } - - $service = $updraftplus->just_one(UpdraftPlus_Options::get_updraft_option('updraft_service')); - if (is_string($service)) $service = array($service); - if (!is_array($service)) $service = array(); - - $no_remote_configured = (empty($service) || array('none') === $service || array('') === $service) ? true : false; - - if ($no_remote_configured && empty($active_remote_storage_list)) { - return ' '.sprintf(__("Backup won't be sent to any remote storage - none has been saved in the %s", 'updraftplus'), ''.__('settings', 'updraftplus')).'. '.__('Not got any remote storage?', 'updraftplus').' '.__("Check out UpdraftPlus Vault.", 'updraftplus').''; - } - - if (empty($active_remote_storage_list)) { - $active_remote_storage_list = '

    '.__('No remote storage locations with valid options found.', 'updraftplus').'

    '; - } - - return ' (...)
    '; - } - - /** - * This method works through the passed in settings array and saves the settings to the database clearing old data and setting up a return array with content to update the page via ajax - * - * @param array $settings An array of settings taking from the admin page ready to be saved to the database - * @return array An array response containing the status of the update along with content to be used to update the admin page. - */ - public function save_settings($settings) { - - global $updraftplus; - - // Make sure that settings filters are registered - UpdraftPlus_Options::admin_init(); - - $more_files_path_updated = false; - - if (isset($settings['updraftplus_version']) && $updraftplus->version == $settings['updraftplus_version']) { - - $return_array = array('saved' => true); - - $add_to_post_keys = array('updraft_interval', 'updraft_interval_database', 'updraft_interval_increments', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_files', 'updraft_startday_db'); - - // If database and files are on same schedule, override the db day/time settings - if (isset($settings['updraft_interval_database']) && isset($settings['updraft_interval_database']) && $settings['updraft_interval_database'] == $settings['updraft_interval'] && isset($settings['updraft_starttime_files'])) { - $settings['updraft_starttime_db'] = $settings['updraft_starttime_files']; - $settings['updraft_startday_db'] = $settings['updraft_startday_files']; - } - foreach ($add_to_post_keys as $key) { - // For add-ons that look at $_POST to find saved settings, add the relevant keys to $_POST so that they find them there - if (isset($settings[$key])) { - $_POST[$key] = $settings[$key]; - } - } - - // Check if updraft_include_more_path is set, if it is then we need to update the page, if it's not set but there's content already in the database that is cleared down below so again we should update the page. - $more_files_path_updated = false; - - // i.e. If an option has been set, or if it was currently active in the settings - if (isset($settings['updraft_include_more_path']) || UpdraftPlus_Options::get_updraft_option('updraft_include_more_path')) { - $more_files_path_updated = true; - } - - // Wipe the extra retention rules, as they are not saved correctly if the last one is deleted - UpdraftPlus_Options::update_updraft_option('updraft_retain_extrarules', array()); - UpdraftPlus_Options::update_updraft_option('updraft_email', array()); - UpdraftPlus_Options::update_updraft_option('updraft_report_warningsonly', array()); - UpdraftPlus_Options::update_updraft_option('updraft_report_wholebackup', array()); - UpdraftPlus_Options::update_updraft_option('updraft_extradbs', array()); - UpdraftPlus_Options::update_updraft_option('updraft_include_more_path', array()); - - $relevant_keys = $updraftplus->get_settings_keys(); - - if (isset($settings['updraft_auto_updates']) && in_array('updraft_auto_updates', $relevant_keys)) { - $updraftplus->set_automatic_updates($settings['updraft_auto_updates']); - unset($settings['updraft_auto_updates']); // unset the key and its value to prevent being processed the second time - } - - if (method_exists('UpdraftPlus_Options', 'mass_options_update')) { - $original_settings = $settings; - $settings = UpdraftPlus_Options::mass_options_update($settings); - $mass_updated = true; - } - - foreach ($settings as $key => $value) { - - if (in_array($key, $relevant_keys)) { - if ('updraft_service' == $key && is_array($value)) { - foreach ($value as $subkey => $subvalue) { - if ('0' == $subvalue) unset($value[$subkey]); - } - } - - // This flag indicates that either the stored database option was changed, or that the supplied option was changed before being stored. It isn't comprehensive - it's only used to update some UI elements with invalid input. - $updated = empty($mass_updated) ? (is_string($value) && UpdraftPlus_Options::get_updraft_option($key) != $value) : (is_string($value) && (!isset($original_settings[$key]) || $original_settings[$key] != $value)); - - if (empty($mass_updated)) UpdraftPlus_Options::update_updraft_option($key, $value); - - // Add information on what has changed to array to loop through to update links etc. - // Restricting to strings for now, to prevent any unintended leakage (since this is just used for UI updating) - if ($updated) { - $value = UpdraftPlus_Options::get_updraft_option($key); - if (is_string($value)) $return_array['changed'][$key] = $value; - } - // @codingStandardsIgnoreLine - } else { - // This section is ignored by CI otherwise it will complain the ELSE is empty. - - // When last active, it was catching: option_page, action, _wpnonce, _wp_http_referer, updraft_s3_endpoint, updraft_dreamobjects_endpoint. The latter two are empty; probably don't need to be in the page at all. - // error_log("Non-UD key when saving from POSTed data: ".$key); - } - } - } else { - $return_array = array('saved' => false, 'error_message' => sprintf(__('UpdraftPlus seems to have been updated to version (%s), which is different to the version running when this settings page was loaded. Please reload the settings page before trying to save settings.', 'updraftplus'), $updraftplus->version)); - } - - // Checking for various possible messages - $updraft_dir = $updraftplus->backups_dir_location(false); - $really_is_writable = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir); - $dir_info = $this->really_writable_message($really_is_writable, $updraft_dir); - $button_title = esc_attr(__('This button is disabled because your backup directory is not writable (see the settings).', 'updraftplus')); - - $return_array['backup_now_message'] = $this->backup_now_remote_message(); - - $return_array['backup_dir'] = array('writable' => $really_is_writable, 'message' => $dir_info, 'button_title' => $button_title); - - // Check if $more_files_path_updated is true, is so then there's a change and we should update the backup modal - if ($more_files_path_updated) { - $return_array['updraft_include_more_path'] = $this->files_selector_widgetry('backupnow_files_', false, 'sometimes'); - } - - // Because of the single AJAX call, we need to remove the existing UD messages from the 'all_admin_notices' action - remove_all_actions('all_admin_notices'); - - // Moving from 2 to 1 ajax call - ob_start(); - - $service = UpdraftPlus_Options::get_updraft_option('updraft_service'); - - $this->setup_all_admin_notices_global($service); - $this->setup_all_admin_notices_udonly($service); - - do_action('all_admin_notices'); - - if (!$really_is_writable) { // Check if writable - $this->show_admin_warning_unwritable(); - } - - if ($return_array['saved']) { // - $this->show_admin_warning(__('Your settings have been saved.', 'updraftplus'), 'updated fade'); - } else { - if (isset($return_array['error_message'])) { - $this->show_admin_warning($return_array['error_message'], 'error'); - } else { - $this->show_admin_warning(__('Your settings failed to save. Please refresh the settings page and try again', 'updraftplus'), 'error'); - } - } - - $messages_output = ob_get_contents(); - - ob_clean(); - - // Backup schedule output - $this->next_scheduled_backups_output('line'); - - $scheduled_output = ob_get_clean(); - - $return_array['messages'] = $messages_output; - $return_array['scheduled'] = $scheduled_output; - $return_array['files_scheduled'] = $this->next_scheduled_files_backups_output(true); - $return_array['database_scheduled'] = $this->next_scheduled_database_backups_output(true); - - - // Add the updated options to the return message, so we can update on screen - return $return_array; - - } - - /** - * Authenticate remote storage instance - * - * @param array - $data It consists of below key elements: - * $remote_method - Remote storage service - * $instance_id - Remote storage instance id - * @return array An array response containing the status of the authentication - */ - public function auth_remote_method($data) { - global $updraftplus; - - $response = array(); - - if (isset($data['remote_method']) && isset($data['instance_id'])) { - $response['result'] = 'success'; - $remote_method = $data['remote_method']; - $instance_id = $data['instance_id']; - - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method)); - - try { - $storage_objects_and_ids[$remote_method]['object']->authenticate_storage($instance_id); - } catch (Exception $e) { - $response['result'] = 'error'; - $response['message'] = $updraftplus->backup_methods[$remote_method] . ' ' . __('authentication error', 'updraftplus') . ' ' . $e->getMessage(); - } - } else { - $response['result'] = 'error'; - $response['message'] = __('Remote storage method and instance id are required for authentication.', 'updraftplus'); - } - - return $response; - } - - /** - * Deauthenticate remote storage instance - * - * @param array - $data It consists of below key elements: - * $remote_method - Remote storage service - * $instance_id - Remote storage instance id - * @return array An array response containing the status of the deauthentication - */ - public function deauth_remote_method($data) { - global $updraftplus; - - $response = array(); - - if (isset($data['remote_method']) && isset($data['instance_id'])) { - $response['result'] = 'success'; - $remote_method = $data['remote_method']; - $instance_id = $data['instance_id']; - - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method)); - - try { - $storage_objects_and_ids[$remote_method]['object']->deauthenticate_storage($instance_id); - } catch (Exception $e) { - $response['result'] = 'error'; - $response['message'] = $updraftplus->backup_methods[$remote_method] . ' deauthentication error ' . $e->getMessage(); - } - } else { - $response['result'] = 'error'; - $response['message'] = 'Remote storage method and instance id are required for deauthentication.'; - } - - return $response; - } - - /** - * A method to remove UpdraftPlus settings from the options table. - * - * @param boolean $wipe_all_settings Set to true as default as we want to remove all options, set to false if calling from UpdraftCentral, as we do not want to remove the UpdraftCentral key or we will lose connection to the site. - * @return boolean - */ - public function wipe_settings($wipe_all_settings = true) { - - global $updraftplus; - - $settings = $updraftplus->get_settings_keys(); - - // if this is false the UDC has called it we don't want to remove the UDC key other wise we will lose connection to the remote site. - if (false == $wipe_all_settings) { - $key = array_search('updraft_central_localkeys', $settings); - unset($settings[$key]); - } - - foreach ($settings as $s) UpdraftPlus_Options::delete_updraft_option($s); - - $updraftplus->wipe_state_data(true); - - $site_options = array('updraft_oneshotnonce'); - foreach ($site_options as $s) delete_site_option($s); - - $this->show_admin_warning(__("Your settings have been wiped.", 'updraftplus')); - - return true; - } - - /** - * This get the details for updraft vault and to be used globally - * - * @param string $instance_id - the instance_id of the current instance being used - * @return object - the UpdraftVault option setup to use the passed in instance id or if one wasn't passed then use the default set of options - */ - public function get_updraftvault($instance_id = '') { - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array('updraftvault')); - - if (isset($storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id])) { - $opts = $storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id]; - $vault = $storage_objects_and_ids['updraftvault']['object']; - $vault->set_options($opts, false, $instance_id); - } else { - include_once(UPDRAFTPLUS_DIR.'/methods/updraftvault.php'); - $vault = new UpdraftPlus_BackupModule_updraftvault(); - } - - return $vault; - } - - /** - * http_get will allow the HTTP Fetch execute available in advanced tools - * - * @param String $uri Specific URL passed to curl - * @param Boolean $curl True or False if cURL is to be used - * @return String - JSON encoded results - */ - public function http_get($uri = null, $curl = false) { - - if (!preg_match('/^https?/', $uri)) return json_encode(array('e' => 'Non-http URL specified')); - - if ($curl) { - if (!function_exists('curl_exec')) { - return json_encode(array('e' => 'No Curl installed')); - die; - } - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $uri); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_FAILONERROR, true); - curl_setopt($ch, CURLOPT_HEADER, false); - curl_setopt($ch, CURLOPT_VERBOSE, true); - curl_setopt($ch, CURLOPT_STDERR, $output = fopen('php://temp', "w+")); - $response = curl_exec($ch); - $error = curl_error($ch); - $getinfo = curl_getinfo($ch); - curl_close($ch); - - rewind($output); - $verb = stream_get_contents($output); - - $resp = array(); - if (false === $response) { - $resp['e'] = htmlspecialchars($error); - } - $resp['r'] = (empty($response)) ? '' : htmlspecialchars(substr($response, 0, 2048)); - - if (!empty($verb)) $resp['r'] = htmlspecialchars($verb)."\n\n".$resp['r']; - - // Extra info returned for Central - $resp['verb'] = $verb; - $resp['response'] = $response; - $resp['status'] = $getinfo; - - return json_encode($resp); - } else { - $response = wp_remote_get($uri, array('timeout' => 10)); - if (is_wp_error($response)) { - return json_encode(array('e' => htmlspecialchars($response->get_error_message()))); - } - return json_encode( - array( - 'r' => wp_remote_retrieve_response_code($response).': '.htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)), - 'code' => wp_remote_retrieve_response_code($response), - 'html_response' => htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)), - 'response' => $response - ) - ); - } - } - - /** - * This will return all the details for raw backup and file list, in HTML format - * - * @param Boolean $no_pre_tags - if set, then
     tags will be removed from the output
    -	 *
    -	 * @return String
    -	 */
    -	public function show_raw_backups($no_pre_tags = false) {
    -		global $updraftplus;
    -		
    -		$response = array();
    -		
    -		$response['html'] = '

    '.__('Known backups (raw)', 'updraftplus').'

    ';
    -		ob_start();
    -		$history = UpdraftPlus_Backup_History::get_history();
    -		var_dump($history);
    -		$response["html"] .= ob_get_clean();
    -		$response['html'] .= '
    '; - - $response['html'] .= '

    '.__('Files', 'updraftplus').'

    ';
    -		$updraft_dir = $updraftplus->backups_dir_location();
    -		$raw_output = array();
    -		$d = dir($updraft_dir);
    -		while (false !== ($entry = $d->read())) {
    -			$fp = $updraft_dir.'/'.$entry;
    -			$mtime = filemtime($fp);
    -			if (is_dir($fp)) {
    -				$size = '       d';
    -			} elseif (is_link($fp)) {
    -				$size = '       l';
    -			} elseif (is_file($fp)) {
    -				$size = sprintf("%8.1f", round(filesize($fp)/1024, 1)).' '.gmdate('r', $mtime);
    -			} else {
    -				$size = '       ?';
    -			}
    -			if (preg_match('/^log\.(.*)\.txt$/', $entry, $lmatch)) $entry = ''.$entry.'';
    -			$raw_output[$mtime] = empty($raw_output[$mtime]) ? sprintf("%s %s\n", $size, $entry) : $raw_output[$mtime].sprintf("%s %s\n", $size, $entry);
    -		}
    -		@$d->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
    -		krsort($raw_output, SORT_NUMERIC);
    -
    -		foreach ($raw_output as $line) {
    -			$response['html'] .= $line;
    -		}
    -
    -		$response['html'] .= '
    '; - - $response['html'] .= '

    '.__('Options (raw)', 'updraftplus').'

    '; - $opts = $updraftplus->get_settings_keys(); - asort($opts); - // '.__('Key', 'updraftplus').''.__('Value', 'updraftplus').' - $response['html'] .= ''; - foreach ($opts as $opt) { - $response['html'] .= ''; - } - - // Get the option saved by yahnis-elsts/plugin-update-checker - $response['html'] .= ''; - - $response['html'] .= '
    '.htmlspecialchars($opt).''.htmlspecialchars(print_r(UpdraftPlus_Options::get_updraft_option($opt), true)).'
    external_updates-updraftplus
    '.htmlspecialchars(print_r(get_site_option('external_updates-updraftplus'), true)).'
    '; - - ob_start(); - do_action('updraftplus_showrawinfo'); - $response['html'] .= ob_get_clean(); - - if (true == $no_pre_tags) { - $response['html'] = str_replace('
    ', '', $response['html']);
    -			$response['html'] = str_replace('
    ', '', $response['html']); - } - - return $response; - } - - /** - * This will call any wp_action - * - * @param Array|Null $data The array of data with the vaules for wpaction - * @param Callable|Boolean $close_connection_callable A callable to call to close the browser connection, or true for a default suitable for internal use, or false for none - * @return Array - results - */ - public function call_wp_action($data = null, $close_connection_callable = false) { - global $updraftplus; - - ob_start(); - - $res = 'Request received: '; - - if (preg_match('/^([^:]+)+:(.*)$/', $data['wpaction'], $matches)) { - $action = $matches[1]; - if (null === ($args = json_decode($matches[2], true))) { - $res .= "The parameters (should be JSON) could not be decoded"; - $action = false; - } else { - if (is_string($args)) $args = array($args); - $res .= "Will despatch action: ".htmlspecialchars($action).", parameters: ".htmlspecialchars(implode(',', $args)); - } - } else { - $action = $data['wpaction']; - $res .= "Will despatch action: ".htmlspecialchars($action).", no parameters"; - } - - ob_get_clean(); - - // Need to add this as the close browser should only work for UDP - if ($close_connection_callable) { - if (is_callable($close_connection_callable)) { - call_user_func($close_connection_callable, array('r' => $res)); - } else { - $updraftplus->close_browser_connection(json_encode(array('r' => $res))); - } - } - - if (!empty($action)) { - if (!empty($args)) { - ob_start(); - $returned = do_action_ref_array($action, $args); - $output = ob_get_clean(); - $res .= " - do_action_ref_array Trigger "; - } else { - ob_start(); - do_action($action); - $output = ob_get_contents(); - ob_end_clean(); - $res .= " - do_action Trigger "; - } - } - $response = array(); - $response['response'] = $res; - $response['log'] = $output; - - // Check if response is empty - if (!empty($returned)) $response['status'] = $returned; - - return $response; - } - - /** - * Enqueue JSTree JavaScript and CSS, taking into account whether it is already enqueued, and current debug settings - */ - public function enqueue_jstree() { - global $updraftplus; - - static $already_enqueued = false; - if ($already_enqueued) return; - - $already_enqueued = true; - $jstree_enqueue_version = $updraftplus->use_unminified_scripts() ? '3.3'.'.'.time() : '3.3'; - $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min'; - - wp_enqueue_script('jstree', UPDRAFTPLUS_URL.'/includes/jstree/jstree'.$min_or_not.'.js', array('jquery'), $jstree_enqueue_version); - wp_enqueue_style('jstree', UPDRAFTPLUS_URL.'/includes/jstree/themes/default/style'.$min_or_not.'.css', array(), $jstree_enqueue_version); - } - - /** - * Detects byte-order mark at the start of common files and change waning message texts - * - * @return string|boolean BOM warning text or false if not bom characters detected - */ - public function get_bom_warning_text() { - $files_to_check = array( - ABSPATH.'wp-config.php', - get_template_directory().DIRECTORY_SEPARATOR.'functions.php', - ); - if (is_child_theme()) { - $files_to_check[] = get_stylesheet_directory().DIRECTORY_SEPARATOR.'functions.php'; - } - $corrupted_files = array(); - foreach ($files_to_check as $file) { - if (!file_exists($file)) continue; - if (false === ($fp = fopen($file, 'r'))) continue; - if (false === ($file_data = fread($fp, 8192))); - fclose($fp); - $substr_file_data = array(); - for ($substr_length = 2; $substr_length <= 5; $substr_length++) { - $substr_file_data[$substr_length] = substr($file_data, 0, $substr_length); - } - // Detect UTF-7, UTF-8, UTF-16 (BE), UTF-16 (LE), UTF-32 (BE) & UTF-32 (LE) Byte order marks (BOM) - $bom_decimal_representations = array( - array(43, 47, 118, 56), // UTF-7 (Hexadecimal: 2B 2F 76 38) - array(43, 47, 118, 57), // UTF-7 (Hexadecimal: 2B 2F 76 39) - array(43, 47, 118, 43), // UTF-7 (Hexadecimal: 2B 2F 76 2B) - array(43, 47, 118, 47), // UTF-7 (Hexadecimal: 2B 2F 76 2F) - array(43, 47, 118, 56, 45), // UTF-7 (Hexadecimal: 2B 2F 76 38 2D) - array(239, 187, 191), // UTF-8 (Hexadecimal: 2B 2F 76 38 2D) - array(254, 255), // UTF-16 (BE) (Hexadecimal: FE FF) - array(255, 254), // UTF-16 (LE) (Hexadecimal: FF FE) - array(0, 0, 254, 255), // UTF-32 (BE) (Hexadecimal: 00 00 FE FF) - array(255, 254, 0, 0), // UTF-32 (LE) (Hexadecimal: FF FE 00 00) - ); - foreach ($bom_decimal_representations as $bom_decimal_representation) { - $no_of_chars = count($bom_decimal_representation); - array_unshift($bom_decimal_representation, 'C*'); - $binary = call_user_func_array('pack', $bom_decimal_representation); - if ($binary == $substr_file_data[$no_of_chars]) { - $corrupted_files[] = $file; - break; - } - } - } - if (empty($corrupted_files)) { - return false; - } else { - $corrupted_files_count = count($corrupted_files); - return ''.__('Warning', 'updraftplus').': '.sprintf(_n('The file %s has a "byte order mark" (BOM) at its beginning.', 'The files %s have a "byte order mark" (BOM) at their beginning.', $corrupted_files_count, 'updraftplus'), ''.implode(', ', $corrupted_files).'').' '.__('Follow this link for more information', 'updraftplus').''; - } - } - - /** - * Gets an instance of the "UpdraftPlus_UpdraftCentral_Cloud" class which will be - * used to login or register the user to the UpdraftCentral cloud - * - * @return object - */ - public function get_updraftcentral_cloud() { - if (!class_exists('UpdraftPlus_UpdraftCentral_Cloud')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftcentral.php'); - return new UpdraftPlus_UpdraftCentral_Cloud(); - } - - /** - * This function will build and return the UpdraftPlus tempoaray clone ui widget - * - * @param boolean $include_testing_ui - a boolean to indicate if testing-only UI elements should be shown (N.B. they can only work if the user also has testing permissions) - * @param array $supported_wp_versions - an array of supported WordPress versions - * @param array $supported_packages - an array of supported clone packages - * @param array $supported_regions - an array of supported clone regions - * - * @return string - the clone UI widget - */ - public function updraftplus_clone_ui_widget($include_testing_ui, $supported_wp_versions, $supported_packages, $supported_regions) { - global $updraftplus; - - $output = '

    '; - $output .= ''.sprintf(__('%s version:', 'updraftplus'), 'PHP').' '; - $output .= $this->output_select_data($this->php_versions, 'php'); - $output .= '

    '; - $output .= '

    '; - $output .= ' '.sprintf(__('%s version:', 'updraftplus'), 'WordPress').' '; - $output .= $this->output_select_data($this->get_wordpress_versions($supported_wp_versions), 'wp'); - $output .= '

    '; - $output .= '

    '; - $output .= ' '.__('Clone region:', 'updraftplus').' '; - $output .= $this->output_select_data($supported_regions, 'region'); - $output .= '

    '; - - $backup_history = UpdraftPlus_Backup_History::get_history(); - - foreach ($backup_history as $key => $backup) { - $backup_complete = $this->check_backup_is_complete($backup, false, true, false); - $remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']); - if (!$backup_complete || $remote_sent) unset($backup_history[$key]); - } - - - $output .= '

    '; - $output .= ' '.__('Clone:', 'updraftplus').' '; - $output .= ''; - $output .= '

    '; - $output .= '

    '; - $output .= ' '.__('Clone package:', 'updraftplus').' '; - $output .= ''; - $output .= '

    '; - - if ((defined('UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT') && UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT) || $include_testing_ui) { - $output .= '

    '; - $output .= ' UpdraftClone Branch: '; - $output .= ''; - $output .= '

    '; - $output .= '

    '; - $output .= ' UpdraftPlus Branch: '; - $output .= ''; - $output .= '

    '; - $output .= '

    '; - } - $output .= '

    '; - $output .= ''; - $output .= ''; - $output .= '

    '; - - $output = apply_filters('updraftplus_clone_additional_ui', $output); - - return $output; - } - - /** - * This function will output a select input using the passed in values. - * - * @param array $data - the keys and values for the select - * @param string $name - the name of the items in the select input - * @param string $selected - the value we want selected by default - * - * @return string - the output of the select input - */ - public function output_select_data($data, $name, $selected = '') { - - $name_version = empty($selected) ? $this->get_current_version($name) : $selected; - - $output = ''; - - return $output; - } - - /** - * This function will output the clones network information - * - * @param string $url - the clone URL - * - * @return string - the clone network information - */ - public function updraftplus_clone_info($url) { - global $updraftplus; - - if (!empty($url)) { - $content = '
    '; - $content .= '

    ' . __('Your clone has started and will be available at the following URLs once it is ready.', 'updraftplus') . '

    '; - $content .= '

    ' . __('Front page:', 'updraftplus') . ' ' . esc_html($url) . '

    '; - $content .= '

    ' . __('Dashboard:', 'updraftplus') . ' ' . esc_html(trailingslashit($url)) . 'wp-admin

    '; - $content .= '
    '; - $content .= '

    '.__('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus').'

    '; - } else { - $content = '

    ' . __('Your clone has started, network information is not yet available but will be displayed here and at your updraftplus.com account once it is ready.', 'updraftplus') . '

    '; - $content .= '

    ' . __('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus') . '

    '; - } - - return $content; - } - - /** - * This function will build and return an array of major WordPress versions, the array is built by calling the WordPress version API once every 24 hours and adding any new entires to our existing array of versions. - * - * @param array $supported_wp_versions - an array of supported WordPress versions - * - * @return array - an array of WordPress major versions - */ - private function get_wordpress_versions($supported_wp_versions) { - - if (empty($supported_wp_versions)) $supported_wp_versions[] = $this->get_current_version('wp'); - - $key = array_search($this->get_current_version('wp'), $supported_wp_versions); - - if ($key) { - $supported_wp_versions = array_slice($supported_wp_versions, $key); - } - - $version_array = $supported_wp_versions; - - return $version_array; - } - - /** - * This function will get the current version the server is running for the passed in item e.g WordPress or PHP - * - * @param string $name - the thing we want to get the version for e.g WordPress or PHP - * - * @return string - returns the current version of the passed in item - */ - public function get_current_version($name) { - - $version = ''; - - if ('php' == $name) { - $parts = explode(".", PHP_VERSION); - $version = $parts[0] . "." . $parts[1]; - } elseif ('wp' == $name) { - global $updraftplus; - $wp_version = $updraftplus->get_wordpress_version(); - $parts = explode(".", $wp_version); - $version = $parts[0] . "." . $parts[1]; - } - - return $version; - } - - /** - * Show which remote storage settings are partially setup error, or if manual auth is supported show the manual auth UI - */ - public function show_admin_warning_if_remote_storage_with_partial_setttings() { - if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) { - $enabled_services = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids(array_keys($this->storage_service_with_partial_settings)); - foreach ($this->storage_service_with_partial_settings as $method => $method_name) { - if (empty($enabled_services[$method]['object']) || empty($enabled_services[$method]['instance_settings']) || !$enabled_services[$method]['object']->supports_feature('manual_authentication')) { - $this->show_admin_warning(sprintf(__('The following remote storage (%s) have only been partially configured, manual authorization is not supported with this remote storage, please try again and if the problem persists contact support.', 'updraftplus'), $method), 'error'); - } else { - $this->show_admin_warning($enabled_services[$method]['object']->get_manual_authorisation_template(), 'error'); - } - } - } else { - $this->show_admin_warning('UpdraftPlus: '.sprintf(__('The following remote storage (%s) have only been partially configured, if you are having problems you can try to manually authorise at the UpdraftPlus settings page.', 'updraftplus'), implode(', ', $this->storage_service_with_partial_settings)).' '.__('Return to UpdraftPlus configuration', 'updraftplus').'', 'error'); - } - } - - /** - * Show remote storage settings are empty warning - */ - public function show_admin_warning_if_remote_storage_settting_are_empty() { - if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) { - $this->show_admin_warning(sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)), 'error'); - } else { - $this->show_admin_warning('UpdraftPlus: '.sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)).' '.__('Return to UpdraftPlus configuration', 'updraftplus').'', 'error'); - } - } - - /** - * Receive Heartbeat data and respond. - * - * Processes data received via a Heartbeat request, and returns additional data to pass back to the front end. - * - * @param array $response - Heartbeat response data to pass back to front end. - * @param array $data - Data received from the front end (unslashed). - */ - public function process_status_in_heartbeat($response, $data) { - if (!is_array($response) || empty($data['updraftplus'])) return $response; - try { - $response['updraftplus'] = $this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($data['updraftplus'])); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - $response['updraftplus'] = array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - ); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - $response['updraftplus'] = array( - 'fatal_error' => true, - 'fatal_error_message' => $log_message - ); - } - - if (UpdraftPlus_Options::user_can_manage() && isset($data['updraftplus']['updraft_credentialtest_nonce'])) { - if (!wp_verify_nonce($data['updraftplus']['updraft_credentialtest_nonce'], 'updraftplus-credentialtest-nonce')) { - $response['updraftplus']['updraft_credentialtest_nonce'] = wp_create_nonce('updraftplus-credentialtest-nonce'); - } - } - - $response['updraftplus']['time_now'] = get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'D, F j, Y H:i'); - - return $response; - } - - /** - * Show warning about restriction implied by the hosting company (can only perform a full backup once per month, incremental backup should not go above one per day) - */ - public function show_admin_warning_one_backup_per_month() { - - global $updraftplus; - - $hosting_company = $updraftplus->get_hosting_info(); - - $txt1 = __('Your website is hosted with %s (%s).', 'updraftplus'); - $txt2 = __('%s permits UpdraftPlus to perform only one backup per month. Thus, we recommend you choose a full backup when performing a manual backup and to use that option when creating a scheduled backup.', 'updraftplus'); - $txt3 = __('Due to the restriction, some settings can be automatically adjusted, disabled or not available.', 'updraftplus'); - - $this->show_plugin_page_admin_warning(''.__('Warning', 'updraftplus').': '.sprintf("$txt1 $txt2 $txt3", $hosting_company['name'], $hosting_company['website'], $hosting_company['name']), 'update-nag notice notice-warning', true); - } - - /** - * Find out if the current request is a backup download request, and proceed with the download if it is - */ - public function maybe_download_backup_from_email() { - global $pagenow; - if ((!defined('DOING_AJAX') || !DOING_AJAX) && UpdraftPlus_Options::admin_page() === $pagenow && isset($_REQUEST['page']) && 'updraftplus' === $_REQUEST['page'] && isset($_REQUEST['action']) && 'updraft_download_backup' === $_REQUEST['action']) { - $findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex']; - $timestamp = empty($_REQUEST['timestamp']) ? '' : $_REQUEST['timestamp']; - $nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce']; - $type = empty($_REQUEST['type']) ? '' : $_REQUEST['type']; - if (empty($timestamp) || empty($nonce) || empty($type)) wp_die(__('The download link is broken, you may have clicked the link from untrusted source', 'updraftplus'), '', array('back_link' => true)); - $backup_history = UpdraftPlus_Backup_History::get_history(); - if (!isset($backup_history[$timestamp]['nonce']) || $backup_history[$timestamp]['nonce'] !== $nonce) wp_die(__("The download link is broken or the backup file is no longer available", 'updraftplus'), '', array('back_link' => true)); - $this->do_updraft_download_backup($findexes, $type, $timestamp, 2, false, ''); - exit; // we don't need anything else but an exit - } - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/backup.php b/wordpress/wp-content/plugins/updraftplus/backup.php deleted file mode 100644 index 814aa61d1cc1e3f90f4b262a84b28312675c6a59..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/backup.php +++ /dev/null @@ -1,3949 +0,0 @@ - times - private $altered_since = -1; - - // Time for the current entity - private $makezip_if_altered_since = -1; - - private $excluded_extensions = false; - - private $use_zip_object = 'UpdraftPlus_ZipArchive'; - - public $debug = false; - - public $updraft_dir; - - private $site_name; - - private $wpdb_obj; - - private $job_file_entities = array(); - - private $first_run = 0; - - // Record of zip files created - private $backup_files_array = array(); - - // Used when deciding to use the 'store' or 'deflate' zip storage method - private $extensions_to_not_compress = array(); - - // Append to this any skipped tables - private $skipped_tables; - - // When initialised, a boolean - public $last_storage_instance; - - // The absolute upper limit that will be considered for a zip batch (in bytes) - private $zip_batch_ceiling; - - private $backup_excluded_patterns = array(); - - // Bytes of uncompressed data written since last open - private $db_current_raw_bytes = 0; - - private $table_prefix; - - private $table_prefix_raw; - - /** - * Class constructor - * - * @param Array|String $backup_files - files to backup, or (string)'no' - * @param Integer $altered_since - only backup files altered since this time (UNIX epoch time) - */ - public function __construct($backup_files, $altered_since = -1) { - - global $updraftplus; - - $this->site_name = $this->get_site_name(); - - // Decide which zip engine to begin with - $this->debug = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode'); - $this->updraft_dir = $updraftplus->backups_dir_location(); - $this->updraft_dir_realpath = realpath($this->updraft_dir); - - require_once(UPDRAFTPLUS_DIR.'/includes/class-database-utility.php'); - - if ('no' === $backup_files) { - $this->use_zip_object = 'UpdraftPlus_PclZip'; - return; - } - - $this->extensions_to_not_compress = array_unique(array_map('strtolower', array_map('trim', explode(',', UPDRAFTPLUS_ZIP_NOCOMPRESS)))); - - $this->backup_excluded_patterns = array( - array( - // all in one wp migration pattern: WP_PLUGIN_DIR/all-in-one-wp-migration/storage/*/*.wpress, `ai1wm-backups` folder in wp-content is already implicitly handled on the UDP settings with a `*backups` predefined exclusion rule for `others` directory - 'directory' => realpath(WP_PLUGIN_DIR).DIRECTORY_SEPARATOR.'all-in-one-wp-migration'.DIRECTORY_SEPARATOR.'storage', - 'regex' => '/.+\.wpress$/is', - ), - ); - - $this->altered_since = $altered_since; - - $resumptions_since_last_successful = $updraftplus->current_resumption - $updraftplus->last_successful_resumption; - - // false means 'tried + failed'; whereas 0 means 'not yet tried' - // Disallow binzip on OpenVZ when we're not sure there's plenty of memory - if (0 === $this->binzip && (!defined('UPDRAFTPLUS_PREFERPCLZIP') || !UPDRAFTPLUS_PREFERPCLZIP) && (!defined('UPDRAFTPLUS_NO_BINZIP') || !UPDRAFTPLUS_NO_BINZIP) && ($updraftplus->current_resumption < 9 || $resumptions_since_last_successful < 2)) { - - if (@file_exists('/proc/user_beancounters') && @file_exists('/proc/meminfo') && @is_readable('/proc/meminfo')) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $meminfo = @file_get_contents('/proc/meminfo', false, null, 0, 200);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (is_string($meminfo) && preg_match('/MemTotal:\s+(\d+) kB/', $meminfo, $matches)) { - $memory_mb = $matches[1]/1024; - // If the report is of a large amount, then we're probably getting the total memory on the hypervisor (this has been observed), and don't really know the VPS's memory - $vz_log = "OpenVZ; reported memory: ".round($memory_mb, 1)." MB"; - if ($memory_mb < 1024 || $memory_mb > 8192) { - $openvz_lowmem = true; - $vz_log .= " (will not use BinZip)"; - } - $updraftplus->log($vz_log); - } - } - if (empty($openvz_lowmem)) { - $updraftplus->log('Checking if we have a zip executable available'); - $binzip = $updraftplus->find_working_bin_zip(); - if (is_string($binzip)) { - $updraftplus->log("Zip engine: found/will use a binary zip: $binzip"); - $this->binzip = $binzip; - $this->use_zip_object = 'UpdraftPlus_BinZip'; - } - } - } - - // In tests, PclZip was found to be 25% slower than ZipArchive - if ('UpdraftPlus_PclZip' != $this->use_zip_object && empty($this->binzip) && ((defined('UPDRAFTPLUS_PREFERPCLZIP') && UPDRAFTPLUS_PREFERPCLZIP == true) || !class_exists('ZipArchive') || !class_exists('UpdraftPlus_ZipArchive') || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile')))) { - global $updraftplus; - $updraftplus->log("Zip engine: ZipArchive (a.k.a. php-zip) is not available or is disabled (will use PclZip (much slower) if needed)"); - $this->use_zip_object = 'UpdraftPlus_PclZip'; - } - - $this->zip_batch_ceiling = (defined('UPDRAFTPLUS_ZIP_BATCH_CEILING') && UPDRAFTPLUS_ZIP_BATCH_CEILING > 104857600) ? UPDRAFTPLUS_ZIP_BATCH_CEILING : 200 * 1048576; - - add_filter('updraftplus_exclude_file', array($this, 'backup_exclude_file'), 10, 2); - - } - - /** - * Get a site name suitable for use in the backup filename - * - * @return String - */ - private function get_site_name() { - // Get the blog name and rip out known-problematic characters. Remember that we may need to be able to upload this to any FTP server or cloud storage, where filename support may be unknown - $site_name = str_replace('__', '_', preg_replace('/[^A-Za-z0-9_]/', '', str_replace(' ', '_', substr(get_bloginfo(), 0, 32)))); - if (!$site_name || preg_match('#^_+$#', $site_name)) { - // Try again... - $parsed_url = parse_url(home_url(), PHP_URL_HOST); - $parsed_subdir = untrailingslashit(parse_url(home_url(), PHP_URL_PATH)); - if ($parsed_subdir && '/' != $parsed_subdir) $parsed_url .= str_replace(array('/', '\\'), '_', $parsed_subdir); - $site_name = str_replace('__', '_', preg_replace('/[^A-Za-z0-9_]/', '', str_replace(' ', '_', substr($parsed_url, 0, 32)))); - if (!$site_name || preg_match('#^_+$#', $site_name)) $site_name = 'WordPress_Backup'; - } - - // Allow an over-ride. Careful about introducing characters not supported by your filesystem or cloud storage. - return apply_filters('updraftplus_blog_name', $site_name); - } - - /** - * Public, because called from the 'More Files' add-on - * - * @param String|Array $create_from_dir Directory/ies to create the zip - * @param String $whichone Entity being backed up (e.g. 'plugins', 'uploads') - * @param String $backup_file_basename Name of backup file - * @param Integer $index Index of zip in the sequence - * @param Integer|Boolean $first_linked_index First linked index in the sequence, or false - * - * @return Boolean - */ - public function create_zip($create_from_dir, $whichone, $backup_file_basename, $index, $first_linked_index = false) { - // Note: $create_from_dir can be an array or a string - - set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT); - - $original_index = $index; - - $this->index = $index; - $this->first_linked_index = (false === $first_linked_index) ? 0 : $first_linked_index; - $this->whichone = $whichone; - - global $updraftplus; - - $this->zip_split_every = max((int) $updraftplus->jobdata_get('split_every'), UPDRAFTPLUS_SPLIT_MIN)*1048576; - - if ('others' != $whichone) $updraftplus->log("Beginning creation of dump of $whichone (split every: ".round($this->zip_split_every/1048576, 1)." MB)"); - - if (is_string($create_from_dir) && !file_exists($create_from_dir)) { - $flag_error = true; - $updraftplus->log("Does not exist: $create_from_dir"); - if ('mu-plugins' == $whichone) { - if (!function_exists('get_mu_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php'); - $mu_plugins = get_mu_plugins(); - if (count($mu_plugins) == 0) { - $updraftplus->log("There appear to be no mu-plugins to backup. Will not raise an error."); - $flag_error = false; - } - } - if ($flag_error) $updraftplus->log(sprintf(__("%s - could not back this entity up; the corresponding directory does not exist (%s)", 'updraftplus'), $whichone, $create_from_dir), 'error'); - return false; - } - - $itext = empty($index) ? '' : $index+1; - $base_path = $backup_file_basename.'-'.$whichone.$itext.'.zip'; - $full_path = $this->updraft_dir.'/'.$base_path; - $time_now = time(); - - // This is compatible with filenames which indicate increments, as it is looking only for the current increment - if (file_exists($full_path)) { - // Gather any further files that may also exist - $files_existing = array(); - while (file_exists($full_path)) { - $files_existing[] = $base_path; - $time_mod = (int) @filemtime($full_path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $updraftplus->log($base_path.": this file has already been created (age: ".round($time_now-$time_mod, 1)." s)"); - if ($time_mod > 100 && ($time_now - $time_mod) < 30) { - UpdraftPlus_Job_Scheduler::terminate_due_to_activity($base_path, $time_now, $time_mod); - } - $index++; - // This is compatible with filenames which indicate increments, as it is looking only for the current increment - $base_path = $backup_file_basename.'-'.$whichone.($index+1).'.zip'; - $full_path = $this->updraft_dir.'/'.$base_path; - } - } - - // Temporary file, to be able to detect actual completion (upon which, it is renamed) - - // Jun-13 - be more aggressive in removing temporary files from earlier attempts - anything >=600 seconds old of this kind - UpdraftPlus_Filesystem_Functions::clean_temporary_files('_'.$updraftplus->file_nonce."-$whichone", 600); - - // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active - $zip_name = $full_path.'.tmp'; - $time_mod = (int) @filemtime($zip_name);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) { - UpdraftPlus_Job_Scheduler::terminate_due_to_activity($zip_name, $time_now, $time_mod); - } - - if (file_exists($zip_name)) { - $updraftplus->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")"); - } - - // Now, check for other forms of temporary file, which would indicate that some activity is going on (even if it hasn't made it into the main zip file yet) - // Note: this doesn't catch PclZip temporary files - $d = dir($this->updraft_dir); - $match = '_'.$updraftplus->file_nonce."-".$whichone; - while (false !== ($e = $d->read())) { - if ('.' == $e || '..' == $e || !is_file($this->updraft_dir.'/'.$e)) continue; - $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $e); - $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $e); - $pclzip_match = preg_match("/^pclzip-[a-z0-9]+.tmp$/", $e); - if ($time_now-filemtime($this->updraft_dir.'/'.$e) < 30 && ($ziparchive_match || (0 != $updraftplus->current_resumption && ($binzip_match || $pclzip_match)))) { - UpdraftPlus_Job_Scheduler::terminate_due_to_activity($this->updraft_dir.'/'.$e, $time_now, filemtime($this->updraft_dir.'/'.$e)); - } - } - @$d->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - clearstatcache(); - - if (isset($files_existing)) { - // Because of zip-splitting, the mere fact that files exist is not enough to indicate that the entity is finished. For that, we need to also see that no subsequent file has been started. - // Q. What if the previous runner died in between zips, and it is our job to start the next one? A. The next temporary file is created before finishing the former zip, so we are safe (and we are also safe-guarded by the updated value of the index being stored in the database). - return $files_existing; - } - - $this->log_account_space(); - - $this->zip_microtime_start = microtime(true); - - // The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front - $zipcode = $this->make_zipfile($create_from_dir, $backup_file_basename, $whichone); - if (true !== $zipcode) { - $updraftplus->log("ERROR: Zip failure: Could not create $whichone zip (".$this->index." / $index)"); - $updraftplus->log(sprintf(__("Could not create %s zip. Consult the log file for more information.", 'updraftplus'), $whichone), 'error'); - // The caller is required to update $index from $this->index - return false; - } else { - $itext = empty($this->index) ? '' : $this->index+1; - $full_path = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip'; - if (file_exists($full_path.'.tmp')) { - if (@filesize($full_path.'.tmp') === 0) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $updraftplus->log("Did not create $whichone zip (".$this->index.") - not needed"); - @unlink($full_path.'.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - - $checksum_description = ''; - - $checksums = $updraftplus->which_checksums(); - - foreach ($checksums as $checksum) { - - $cksum = hash_file($checksum, $full_path.'.tmp'); - $updraftplus->jobdata_set($checksum.'-'.$whichone.$this->index, $cksum); - if ($checksum_description) $checksum_description .= ', '; - $checksum_description .= "$checksum: $cksum"; - - } - - @rename($full_path.'.tmp', $full_path);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $timetaken = max(microtime(true)-$this->zip_microtime_start, 0.000001); - $kbsize = filesize($full_path)/1024; - $rate = round($kbsize/$timetaken, 1); - $updraftplus->log("Created $whichone zip (".$this->index.") - ".round($kbsize, 1)." KB in ".round($timetaken, 1)." s ($rate KB/s) ($checksum_description)"); - // We can now remove any left-over temporary files from this job - } - } elseif ($this->index > $original_index) { - $updraftplus->log("Did not create $whichone zip (".$this->index.") - not needed (2)"); - // Added 12-Feb-2014 (to help multiple morefiles) - $this->index--; - } else { - $updraftplus->log("Looked-for $whichone zip (".$this->index.") was not found (".basename($full_path).".tmp)", 'warning'); - } - UpdraftPlus_Filesystem_Functions::clean_temporary_files('_'.$updraftplus->file_nonce."-$whichone", 0); - } - - // Remove cache list files as well, if there are any - UpdraftPlus_Filesystem_Functions::clean_temporary_files('_'.$updraftplus->file_nonce."-$whichone", 0, true); - - // Create the results array to send back (just the new ones, not any prior ones) - $files_existing = array(); - $res_index = $original_index; - for ($i = $original_index; $i<= $this->index; $i++) { - $itext = empty($i) ? '' : ($i+1); - $full_path = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip'; - if (file_exists($full_path)) { - $files_existing[$res_index] = $backup_file_basename.'-'.$whichone.$itext.'.zip'; - } - $res_index++; - } - return $files_existing; - } - - /** - * This method is for calling outside of a cloud_backup() context. It constructs a list of services for which prune operations should be attempted, and then calls prune_retained_backups() if necessary upon them. - */ - public function do_prune_standalone() { - global $updraftplus; - - $services = (array) $updraftplus->just_one($updraftplus->jobdata_get('service')); - - $prune_services = array(); - - foreach ($services as $service) { - if ('none' === $service || '' == $service) continue; - - $objname = "UpdraftPlus_BackupModule_${service}"; - if (!class_exists($objname) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$service.'.php')) { - include_once(UPDRAFTPLUS_DIR.'/methods/'.$service.'.php'); - } - if (class_exists($objname)) { - $remote_obj = new $objname; - $prune_services[$service]['all'] = array($remote_obj, null); - } else { - $updraftplus->log("Could not prune from service $service: remote method not found"); - } - - } - - if (!empty($prune_services)) $this->prune_retained_backups($prune_services); - } - - /** - * Dispatch to the relevant function - * - * @param Array $backup_array List of archives for the backup - */ - public function cloud_backup($backup_array) { - - global $updraftplus; - - $services = (array) $updraftplus->just_one($updraftplus->jobdata_get('service')); - $remote_storage_instances = $updraftplus->jobdata_get('remote_storage_instances', array()); - - // We need to make sure that the loop below actually runs - if (empty($services)) $services = array('none'); - - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids($services, $remote_storage_instances); - - $total_instances_count = 0; - - foreach ($storage_objects_and_ids as $service) { - if ($service['object']->supports_feature('multi_options')) $total_instances_count += count($service['instance_settings']); - } - - $updraftplus->jobdata_set('jobstatus', 'clouduploading'); - - $updraftplus->register_wp_http_option_hooks(); - - $upload_status = $updraftplus->jobdata_get('uploading_substatus'); - if (!is_array($upload_status) || !isset($upload_status['t'])) { - $upload_status = array('i' => 0, 'p' => 0, 't' => max(1, $total_instances_count)*count($backup_array)); - $updraftplus->jobdata_set('uploading_substatus', $upload_status); - } - - $do_prune = array(); - - // If there was no check-in last time, then attempt a different service first - in case a time-out on the attempted service leads to no activity and everything stopping - if (count($services) >1 && !empty($updraftplus->no_checkin_last_time)) { - $updraftplus->log('No check-in last time: will try a different remote service first'); - array_push($services, array_shift($services)); - // Make sure that the 'no worthwhile activity' detector isn't flumoxed by the starting of a new upload at 0% - if ($updraftplus->current_resumption > 9) $updraftplus->jobdata_set('uploaded_lastreset', $updraftplus->current_resumption); - if (1 == ($updraftplus->current_resumption % 2) && count($services)>2) array_push($services, array_shift($services)); - } - - $errors_before_uploads = $updraftplus->error_count(); - - foreach ($services as $ind => $service) { - try { - $instance_id_count = 0; - $total_instance_ids = ('none' !== $service && '' !== $service && $storage_objects_and_ids[$service]['object']->supports_feature('multi_options')) ? count($storage_objects_and_ids[$service]['instance_settings']) : 1; - - // Used for logging by record_upload_chunk() - $this->current_service = $service; - - // Used when deciding whether to delete the local file - $this->last_storage_instance = ($ind+1 >= count($services) && $instance_id_count+1 >= $total_instance_ids && $errors_before_uploads == $updraftplus->error_count()) ? true : false; - $log_extra = $this->last_storage_instance ? ' (last)' : ''; - $updraftplus->log("Cloud backup selection (".($ind+1)."/".count($services)."): ".$service." with instance (".($instance_id_count+1)."/".$total_instance_ids.")".$log_extra); - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if ('none' == $service || '' == $service) { - $updraftplus->log('No remote despatch: user chose no remote backup service'); - // Still want to mark as "uploaded", to signal that nothing more needs doing. (Important on incremental runs with no cloud storage). - foreach ($backup_array as $file) { - if ($updraftplus->is_uploaded($file)) { - $updraftplus->log("Already uploaded: $file"); - } else { - $updraftplus->uploaded_file($file, true); - } - } - $this->prune_retained_backups(array('none' => array('all' => array(null, null)))); - } elseif (!empty($storage_objects_and_ids[$service]['object']) && !$storage_objects_and_ids[$service]['object']->supports_feature('multi_options')) { - $remote_obj = $storage_objects_and_ids[$service]['object']; - - $do_prune = array_merge_recursive($do_prune, $this->upload_cloud($remote_obj, $service, $backup_array, '')); - } elseif (!empty($storage_objects_and_ids[$service]['instance_settings'])) { - foreach ($storage_objects_and_ids[$service]['instance_settings'] as $instance_id => $options) { - - if ($instance_id_count > 0) { - $this->last_storage_instance = ($ind+1 >= count($services) && $instance_id_count+1 >= $total_instance_ids && $errors_before_uploads == $updraftplus->error_count()) ? true : false; - $log_extra = $this->last_storage_instance ? ' (last)' : ''; - $updraftplus->log("Cloud backup selection (".($ind+1)."/".count($services)."): ".$service." with instance (".($instance_id_count+1)."/".$total_instance_ids.")".$log_extra); - } - - // Used for logging by record_upload_chunk() - $this->current_instance = $instance_id; - - if (!isset($options['instance_enabled'])) $options['instance_enabled'] = 1; - - // if $remote_storage_instances is not empty then we are looping over a list of instances the user wants to backup to so we want to ignore if the instance is enabled or not - if (1 == $options['instance_enabled'] || !empty($remote_storage_instances)) { - $remote_obj = $storage_objects_and_ids[$service]['object']; - $remote_obj->set_options($options, true, $instance_id); - $do_prune = array_merge_recursive($do_prune, $this->upload_cloud($remote_obj, $service, $backup_array, $instance_id)); - } else { - $updraftplus->log("This instance id ($instance_id) is set as inactive."); - } - - $instance_id_count++; - } - } - } catch (Exception $e) { - $log_message = 'Exception ('.get_class($e).') occurred during backup uploads to the '.$service.'. Exception Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - $updraftplus->log($log_message); - error_log($log_message); - $updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during backup uploads to the '.$service.'. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - $updraftplus->log($log_message); - error_log($log_message); - $updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - } - } - - if (!empty($do_prune)) $this->prune_retained_backups($do_prune); - - $updraftplus->register_wp_http_option_hooks(false); - - } - - /** - * This method will start the upload of the backups to the chosen remote storage method and return an array of files to be pruned and their location. - * - * @param Object $remote_obj - the remote storage object - * @param String $service - the name of the service we are uploading to - * @param Array $backup_array - an array that contains the backup files we want to upload - * @param String $instance_id - the instance id we are using - * @return Array - an array with information about what files to prune and where they are located - */ - private function upload_cloud($remote_obj, $service, $backup_array, $instance_id) { - - global $updraftplus; - - $do_prune = array(); - - if ('' == $instance_id) { - $updraftplus->log("Beginning dispatch of backup to remote ($service)"); - } else { - $updraftplus->log("Beginning dispatch of backup to remote ($service) (instance identifier $instance_id)"); - } - - $errors_before_uploads = $updraftplus->error_count(); - - $sarray = array(); - foreach ($backup_array as $bind => $file) { - if ($updraftplus->is_uploaded($file, $service, $instance_id)) { - if ('' == $instance_id) { - $updraftplus->log("Already uploaded to $service: $file", 'notice', false, true); - } else { - $updraftplus->log("Already uploaded to $service / $instance_id: $file", 'notice', false, true); - } - } else { - $sarray[$bind] = $file; - } - } - - if (count($sarray) > 0) { - $pass_to_prune = $remote_obj->backup($sarray); - if ('remotesend' != $service) { - $do_prune[$service][$instance_id] = array($remote_obj, $pass_to_prune); - } else { - $do_prune[$service]['default'] = array($remote_obj, $pass_to_prune); - } - - // Check there are no errors in the uploads, if none then call upload_completed() if it exists otherwise mark as complete - if ($errors_before_uploads == $updraftplus->error_count()) { - if (is_callable(array($remote_obj, 'upload_completed'))) { - $result = $remote_obj->upload_completed(); - if ($result) $updraftplus->mark_upload_complete($service); - } else { - $updraftplus->mark_upload_complete($service); - } - } - } else { - // We still need to make sure that prune is run on this remote storage method, even if all entities were previously uploaded - $do_prune[$service]['all'] = array($remote_obj, null); - } - - return $do_prune; - } - - /** - * Group the backup history into sets for retention processing and indicate the retention rule to apply to each group. This is a 'default' function which just puts them all in together. - * - * @param Array $backup_history - * - * @return Array - */ - private function group_backups($backup_history) { - return array(array('sets' => $backup_history, 'process_order' => 'keep_newest')); - } - - /** - * Logs a message; with the message being logged to the database also only if that has not been done in the last 3 seconds. Useful for better overall performance on slow database servers with rapid logging. - * - * @uses UpdraftPlus::log() - * - * @param String $message - the message to log - * @param String $level - the log level - */ - private function log_with_db_occasionally($message, $level = 'notice') { - global $updraftplus; - static $last_db = false; - - if (time() > $last_db + 3) { - $last_db = time(); - $skip_dblog = false; - } else { - $skip_dblog = true; - } - - return $updraftplus->log($message, $level, false, $skip_dblog); - } - - /** - * Prunes historical backups, according to the user's settings - * - * @param Array $services - An associative array with list of services as key and remote object and boolean flag as values to prune on. This must be an array (i.e. it is not flexible like some other places) - * - * @return void - */ - public function prune_retained_backups($services) { - - global $updraftplus, $wpdb; - - if ('' != $updraftplus->jobdata_get('remotesend_info')) { - $updraftplus->log("Prune old backups from local store: skipping, as this was a remote send operation"); - return; - } - - if (method_exists($wpdb, 'check_connection') && (!defined('UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS') || !UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS)) { - if (!$wpdb->check_connection(false)) { - UpdraftPlus_Job_Scheduler::reschedule(60); - $updraftplus->log('It seems the database went away; scheduling a resumption and terminating for now'); - UpdraftPlus_Job_Scheduler::record_still_alive(); - die; - } - } - - // If they turned off deletion on local backups, then there is nothing to do - if (!UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1) && 1 == count($services) && array_key_exists('none', $services)) { - $updraftplus->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups"); - return; - } - - $updraftplus->jobdata_set_multi(array('jobstatus' => 'pruning', 'prune' => 'begun')); - - // Number of backups to retain - files - $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 2); - $updraft_retain = is_numeric($updraft_retain) ? $updraft_retain : 1; - - // Number of backups to retain - db - $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain); - $updraft_retain_db = is_numeric($updraft_retain_db) ? $updraft_retain_db : 1; - - $updraftplus->log("Retain: beginning examination of existing backup sets; user setting: retain_files=$updraft_retain, retain_db=$updraft_retain_db"); - - // Returns an array, most recent first, of backup sets - $backup_history = UpdraftPlus_Backup_History::get_history(); - - $ignored_because_imported = array(); - - // Remove non-native (imported) backups, which are neither counted nor pruned. It's neater to do these in advance, and log only one line. - $functional_backup_history = $backup_history; - foreach ($functional_backup_history as $backup_time => $backup_to_examine) { - if (isset($backup_to_examine['native']) && false == $backup_to_examine['native']) { - $ignored_because_imported[] = $backup_time; - unset($functional_backup_history[$backup_time]); - } - } - if (!empty($ignored_because_imported)) { - $updraftplus->log("These backup set(s) were imported from a remote location, so will not be counted or pruned. Skipping: ".implode(', ', $ignored_because_imported)); - } - - $backupable_entities = $updraftplus->get_backupable_file_entities(true); - - $database_backups_found = array(); - - $file_entities_backups_found = array(); - foreach ($backupable_entities as $entity => $info) { - $file_entities_backups_found[$entity] = 0; - } - - if (false === ($backup_db_groups = apply_filters('updraftplus_group_backups_for_pruning', false, $functional_backup_history, 'db'))) { - $backup_db_groups = $this->group_backups($functional_backup_history); - } - $updraftplus->log("Number of backup sets in history: ".count($backup_history)."; groups (db): ".count($backup_db_groups)); - - foreach ($backup_db_groups as $group_id => $group) { - - // N.B. The array returned by UpdraftPlus_Backup_History::get_history() is already sorted, with most-recent first - - if (empty($group['sets']) || !is_array($group['sets'])) continue; - $sets = $group['sets']; - - // Sort the groups into the desired "keep this first" order - $process_order = (!empty($group['process_order']) && 'keep_oldest' == $group['process_order']) ? 'keep_oldest' : 'keep_newest'; - if ('keep_oldest' == $process_order) ksort($sets); - - $rule = !empty($group['rule']) ? $group['rule'] : array('after-howmany' => 0, 'after-period' => 0, 'every-period' => 1, 'every-howmany' => 1); - - foreach ($sets as $backup_datestamp => $backup_to_examine) { - - $files_to_prune = array(); - $nonce = empty($backup_to_examine['nonce']) ? '???' : $backup_to_examine['nonce']; - - // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads - // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted - $this->log_with_db_occasionally(sprintf("Examining (for databases) backup set with group_id=$group_id, nonce=%s, datestamp=%s (%s)", $nonce, $backup_datestamp, gmdate('M d Y H:i:s', $backup_datestamp))); - - // "Always Keep" Backups should be counted in the count of how many have been retained for purposes of the "how many to retain" count... but if that count is already matched, it's not a problem - $is_always_keep = !empty($backup_to_examine['always_keep']); - - // Auto-backups are only counted or deleted once we have reached the retain limit - before that, they are skipped - $is_autobackup = !empty($backup_to_examine['autobackup']); - - $remote_sent = (!empty($backup_to_examine['service']) && ((is_array($backup_to_examine['service']) && in_array('remotesend', $backup_to_examine['service'])) || 'remotesend' === $backup_to_examine['service'])) ? true : false; - - $any_deleted_via_filter_yet = false; - - // Databases - foreach ($backup_to_examine as $key => $data) { - if ('db' != strtolower(substr($key, 0, 2)) || '-size' == substr($key, -5, 5)) continue; - - if (empty($database_backups_found[$key])) $database_backups_found[$key] = 0; - - if ($nonce == $updraftplus->nonce || $nonce == $updraftplus->file_nonce) { - $this->log_with_db_occasionally("This backup set is the backup set just made, so will not be deleted."); - $database_backups_found[$key]++; - continue; - } - - if ($is_always_keep) { - if ($database_backups_found[$key] < $updraft_retain) { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an 'Always Keep' backup, and we have not yet reached any retain limits, so it should be counted in the count of how many have been retained for purposes of the 'how many to retain' count. It will not be pruned. Skipping."); - $database_backups_found[$key]++; - } else { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an 'Always Keep' backup, so it will not be pruned. Skipping."); - } - continue; - } - - if ($is_autobackup) { - if ($any_deleted_via_filter_yet) { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an automatic backup, but we have previously deleted a backup due to a limit, so it will be pruned (but not counted towards numerical limits)."); - $prune_it = true; - } elseif ($database_backups_found[$key] < $updraft_retain_db) { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an automatic backup, and we have not yet reached any retain limits, so it will not be counted or pruned. Skipping."); - continue; - } else { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an automatic backup, and we have already reached retain limits, so it will be pruned."); - $prune_it = true; - } - } else { - $prune_it = false; - } - - if ($remote_sent) { - $prune_it = true; - $this->log_with_db_occasionally("$backup_datestamp: $key: was sent to remote site; will remove from local record (only)"); - } - - // All non-auto backups must be run through this filter (in date order) regardless of the current state of $prune_it - so that filters are able to track state. - $prune_it_before_filter = $prune_it; - - if (!$is_autobackup) $prune_it = apply_filters('updraftplus_prune_or_not', $prune_it, 'db', $backup_datestamp, $key, $database_backups_found[$key], $rule, $group_id); - - // Apply the final retention limit list (do not increase the 'retained' counter before seeing if the backup is being pruned for some other reason) - if (!$prune_it && !$is_autobackup) { - - if ($database_backups_found[$key] + 1 > $updraft_retain_db) { - $prune_it = true; - - $fname = is_string($data) ? $data : $data[0]; - $this->log_with_db_occasionally("$backup_datestamp: $key: this set includes a database (".$fname."); db count is now ".$database_backups_found[$key]); - - $this->log_with_db_occasionally("$backup_datestamp: $key: over retain limit ($updraft_retain_db); will delete this database"); - } - - } - - if ($prune_it) { - if (!$prune_it_before_filter) $any_deleted_via_filter_yet = true; - - if (!empty($data)) { - $size_key = $key.'-size'; - $size = isset($backup_to_examine[$size_key]) ? $backup_to_examine[$size_key] : null; - foreach ($services as $service => $instance_ids_to_prune) { - foreach ($instance_ids_to_prune as $instance_id_to_prune => $sd) { - if ('none' != $service && '' != $service && $sd[0]->supports_feature('multi_options')) { - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($service)); - if ('all' == $instance_id_to_prune) { - foreach ($storage_objects_and_ids[$service]['instance_settings'] as $saved_instance_id => $options) { - $sd[0]->set_options($options, false, $saved_instance_id); - $this->prune_file($service, $data, $sd[0], $sd[1], array($size)); - } - } else { - $opts = $storage_objects_and_ids[$service]['instance_settings'][$instance_id_to_prune]; - $sd[0]->set_options($opts, false, $instance_id_to_prune); - $this->prune_file($service, $data, $sd[0], $sd[1], array($size)); - } - } else { - $this->prune_file($service, $data, $sd[0], $sd[1], array($size)); - } - } - } - } - unset($backup_to_examine[$key]); - UpdraftPlus_Job_Scheduler::record_still_alive(); - } elseif (!$is_autobackup) { - $database_backups_found[$key]++; - } - - $backup_to_examine = $this->remove_backup_set_if_empty($backup_to_examine, $backupable_entities); - if (empty($backup_to_examine)) { - unset($functional_backup_history[$backup_datestamp]); - unset($backup_history[$backup_datestamp]); - $this->maybe_save_backup_history_and_reschedule($backup_history); - } else { - $functional_backup_history[$backup_datestamp] = $backup_to_examine; - $backup_history[$backup_datestamp] = $backup_to_examine; - } - } - } - } - - if (false === ($backup_files_groups = apply_filters('updraftplus_group_backups_for_pruning', false, $functional_backup_history, 'files'))) { - $backup_files_groups = $this->group_backups($functional_backup_history); - } - - $updraftplus->log("Number of backup sets in history: ".count($backup_history)."; groups (files): ".count($backup_files_groups)); - - // Now again - this time for the files - foreach ($backup_files_groups as $group_id => $group) { - - // N.B. The array returned by UpdraftPlus_Backup_History::get_history() is already sorted, with most-recent first - - if (empty($group['sets']) || !is_array($group['sets'])) continue; - $sets = $group['sets']; - - // Sort the groups into the desired "keep this first" order - $process_order = (!empty($group['process_order']) && 'keep_oldest' == $group['process_order']) ? 'keep_oldest' : 'keep_newest'; - // Youngest - i.e. smallest epoch - first - if ('keep_oldest' == $process_order) ksort($sets); - - $rule = !empty($group['rule']) ? $group['rule'] : array('after-howmany' => 0, 'after-period' => 0, 'every-period' => 1, 'every-howmany' => 1); - - foreach ($sets as $backup_datestamp => $backup_to_examine) { - - $files_to_prune = array(); - $nonce = empty($backup_to_examine['nonce']) ? '???' : $backup_to_examine['nonce']; - - // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads - // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted - $this->log_with_db_occasionally(sprintf("Examining (for files) backup set with nonce=%s, datestamp=%s (%s)", $nonce, $backup_datestamp, gmdate('M d Y H:i:s', $backup_datestamp))); - - // "Always Keep" Backups should be counted in the count of how many have been retained for purposes of the "how many to retain" count... but if that count is already matched, it's not a problem - $is_always_keep = !empty($backup_to_examine['always_keep']); - - // Auto-backups are only counted or deleted once we have reached the retain limit - before that, they are skipped - $is_autobackup = !empty($backup_to_examine['autobackup']); - - $remote_sent = (!empty($backup_to_examine['service']) && ((is_array($backup_to_examine['service']) && in_array('remotesend', $backup_to_examine['service'])) || 'remotesend' === $backup_to_examine['service'])) ? true : false; - - $any_deleted_via_filter_yet = false; - - $file_sizes = array(); - - // Files - foreach ($backupable_entities as $entity => $info) { - if (!empty($backup_to_examine[$entity])) { - - // This should only be able to happen if you import backups with a future timestamp - if ($nonce == $updraftplus->nonce || $nonce == $updraftplus->file_nonce) { - $updraftplus->log("This backup set is the backup set just made, so will not be deleted."); - $file_entities_backups_found[$entity]++; - continue; - } - - if ($is_always_keep) { - if ($file_entities_backups_found[$entity] < $updraft_retain) { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an 'Always Keep' backup, and we have not yet reached any retain limits, so it should be counted in the count of how many have been retained for purposes of the 'how many to retain' count. It will not be pruned. Skipping."); - $file_entities_backups_found[$entity]++; - } else { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an 'Always Keep' backup, so it will not be pruned. Skipping."); - } - continue; - } - - if ($is_autobackup) { - if ($any_deleted_via_filter_yet) { - $this->log_with_db_occasionally("This backup set was an automatic backup, but we have previously deleted a backup due to a limit, so it will be pruned (but not counted towards numerical limits)."); - $prune_it = true; - } elseif ($file_entities_backups_found[$entity] < $updraft_retain) { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an automatic backup, and we have not yet reached any retain limits, so it will not be counted or pruned. Skipping."); - continue; - } else { - $this->log_with_db_occasionally("This backup set ($backup_datestamp) was an automatic backup, and we have already reached retain limits, so it will be pruned."); - $prune_it = true; - } - } else { - $prune_it = false; - } - - if ($remote_sent) { - $prune_it = true; - } - - // All non-auto backups must be run through this filter (in date order) regardless of the current state of $prune_it - so that filters are able to track state. - $prune_it_before_filter = $prune_it; - if (!$is_autobackup) $prune_it = apply_filters('updraftplus_prune_or_not', $prune_it, 'files', $backup_datestamp, $entity, $file_entities_backups_found[$entity], $rule, $group_id); - - // The "more than maximum to keep?" counter should not be increased until we actually know that the set is being kept. Before verison 1.11.22, we checked this before running the filter, which resulted in the counter being increased for sets that got pruned via the filter (i.e. not kept) - and too many backups were thus deleted - if (!$prune_it && !$is_autobackup) { - if ($file_entities_backups_found[$entity] >= $updraft_retain) { - $this->log_with_db_occasionally("$entity: over retain limit ($updraft_retain); will delete this file entity"); - $prune_it = true; - } - } - - if ($prune_it) { - if (!$prune_it_before_filter) $any_deleted_via_filter_yet = true; - $prune_this = $backup_to_examine[$entity]; - if (is_string($prune_this)) $prune_this = array($prune_this); - - foreach ($prune_this as $k => $prune_file) { - if ($remote_sent) { - $updraftplus->log("$entity: $backup_datestamp: was sent to remote site; will remove from local record (only)"); - } - $size_key = (0 == $k) ? $entity.'-size' : $entity.$k.'-size'; - $size = (isset($backup_to_examine[$size_key])) ? $backup_to_examine[$size_key] : null; - $files_to_prune[] = $prune_file; - $file_sizes[] = $size; - } - unset($backup_to_examine[$entity]); - - } elseif (!$is_autobackup) { - $file_entities_backups_found[$entity]++; - } - } - } - - // Sending an empty array is not itself a problem - except that the remote storage method may not check that before setting up a connection, which can waste time: especially if this is done every time around the loop. - if (!empty($files_to_prune)) { - // Actually delete the files - foreach ($services as $service => $instance_ids_to_prune) { - foreach ($instance_ids_to_prune as $instance_id_to_prune => $sd) { - if ("none" != $service && '' != $service && $sd[0]->supports_feature('multi_options')) { - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($service)); - if ('all' == $instance_id_to_prune) { - foreach ($storage_objects_and_ids[$service]['instance_settings'] as $saved_instance_id => $options) { - $sd[0]->set_options($options, false, $saved_instance_id); - $this->prune_file($service, $files_to_prune, $sd[0], $sd[1], array($size)); - } - } else { - $opts = $storage_objects_and_ids[$service]['instance_settings'][$instance_id_to_prune]; - $sd[0]->set_options($opts, false, $instance_id_to_prune); - $this->prune_file($service, $files_to_prune, $sd[0], $sd[1], array($size)); - } - } else { - $this->prune_file($service, $files_to_prune, $sd[0], $sd[1], array($size)); - } - UpdraftPlus_Job_Scheduler::record_still_alive(); - } - } - } - - $backup_to_examine = $this->remove_backup_set_if_empty($backup_to_examine, $backupable_entities); - if (empty($backup_to_examine)) { -// unset($functional_backup_history[$backup_datestamp]); - unset($backup_history[$backup_datestamp]); - $this->maybe_save_backup_history_and_reschedule($backup_history); - } else { -// $functional_backup_history[$backup_datestamp] = $backup_to_examine; - $backup_history[$backup_datestamp] = $backup_to_examine; - } - - // Loop over backup sets - } - - // Look over backup groups - } - - $updraftplus->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation"); - UpdraftPlus_Backup_History::save_history($backup_history, false); - - do_action('updraftplus_prune_retained_backups_finished'); - - $updraftplus->jobdata_set('prune', 'finished'); - - } - - /** - * The purpose of this is to save the backup history periodically - for the benefit of setups where the pruning takes longer than the total allow run time (e.g. if the network communications to the remote storage have delays in, and there are a lot of sets to scan) - * - * @param Array $backup_history - the backup history to possible save - */ - private function maybe_save_backup_history_and_reschedule($backup_history) { - static $last_saved_at = 0; - if (!$last_saved_at) $last_saved_at = time(); - if (time() - $last_saved_at >= 10) { - global $updraftplus; - $updraftplus->log("Retain: saving new backup history, because at least 10 seconds have passed since the last save (sets now: ".count($backup_history).")"); - UpdraftPlus_Backup_History::save_history($backup_history, false); - UpdraftPlus_Job_Scheduler::something_useful_happened(); - $last_saved_at = time(); - } - } - - /** - * Examine a backup set; if it is empty (no files or DB), then remove the associated log file - * - * @param Array $backup_to_examine - backup set - * @param Array $backupable_entities - compare with this list of backup entities - * - * @return Array|Boolean - if it was empty, false is returned - */ - private function remove_backup_set_if_empty($backup_to_examine, $backupable_entities) { - - global $updraftplus; - - // Get new result, post-deletion; anything left in this set? - $contains_files = 0; - foreach ($backupable_entities as $entity => $info) { - if (isset($backup_to_examine[$entity])) { - $contains_files = 1; - break; - } - } - - $contains_db = 0; - foreach ($backup_to_examine as $key => $data) { - if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) { - $contains_db = 1; - break; - } - } - - // Delete backup set completely if empty, o/w just remove DB - // We search on the four keys which represent data, allowing other keys to be used to track other things - if (!$contains_files && !$contains_db) { - $updraftplus->log("This backup set is now empty; will remove from history"); - if (isset($backup_to_examine['nonce'])) { - $fullpath = $this->updraft_dir."/log.".$backup_to_examine['nonce'].".txt"; - if (is_file($fullpath)) { - $updraftplus->log("Deleting log file (log.".$backup_to_examine['nonce'].".txt)"); - @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - $updraftplus->log("Corresponding log file (log.".$backup_to_examine['nonce'].".txt) not found - must have already been deleted"); - } - } else { - $updraftplus->log("No nonce record found in the backup set, so cannot delete any remaining log file"); - } - return false; - } else { - $updraftplus->log("This backup set remains non-empty (f=$contains_files/d=$contains_db); will retain in history"); - return $backup_to_examine; - } - - } - - /** - * Prune files from remote and local storage - * - * @param String $service Service to prune (one only) - * @param Array|String $dofiles An array of files (or a single string for one file) - * @param Array $method_object specific method object - * @param Array $object_passback specific passback object - * @param Array $file_sizes size of files - */ - private function prune_file($service, $dofiles, $method_object = null, $object_passback = null, $file_sizes = array()) { - global $updraftplus; - if (!is_array($dofiles)) $dofiles = array($dofiles); - - if (!apply_filters('updraftplus_prune_file', true, $dofiles, $service, $method_object, $object_passback, $file_sizes)) { - $updraftplus->log("Prune: service=$service: skipped via filter"); - } - - foreach ($dofiles as $dofile) { - if (empty($dofile)) continue; - $updraftplus->log("Delete file: $dofile, service=$service"); - $fullpath = $this->updraft_dir.'/'.$dofile; - // delete it if it's locally available - if (file_exists($fullpath)) { - $updraftplus->log("Deleting local copy ($dofile)"); - unlink($fullpath); - } - } - // Despatch to the particular method's deletion routine - if (!is_null($method_object)) $method_object->delete($dofiles, $object_passback, $file_sizes); - } - - /** - * The purpose of this function is to make sure that the options table is put in the database first, then the users table, then the site + blogs tables (if present - multisite), then the usermeta table; and after that the core WP tables - so that when restoring we restore the core tables first - * - * @param Array $a_arr First array to be compared - * @param Array $b_arr Second array to be compared - * @return Integer - according to the rules of usort() - */ - private function backup_db_sorttables($a_arr, $b_arr) { - - $a = $a_arr['name']; - $a_table_type = $a_arr['type']; - $b = $b_arr['name']; - $b_table_type = $b_arr['type']; - - // Views must always go after tables (since they can depend upon them) - if ('VIEW' == $a_table_type && 'VIEW' != $b_table_type) return 1; - if ('VIEW' == $b_table_type && 'VIEW' != $a_table_type) return -1; - - if ('wp' != $this->whichdb) return strcmp($a, $b); - - global $updraftplus; - if ($a == $b) return 0; - $our_table_prefix = $this->table_prefix_raw; - if ($a == $our_table_prefix.'options') return -1; - if ($b == $our_table_prefix.'options') return 1; - if ($a == $our_table_prefix.'site') return -1; - if ($b == $our_table_prefix.'site') return 1; - if ($a == $our_table_prefix.'blogs') return -1; - if ($b == $our_table_prefix.'blogs') return 1; - if ($a == $our_table_prefix.'users') return -1; - if ($b == $our_table_prefix.'users') return 1; - if ($a == $our_table_prefix.'usermeta') return -1; - if ($b == $our_table_prefix.'usermeta') return 1; - - if (empty($our_table_prefix)) return strcmp($a, $b); - - try { - $core_tables = array_merge($this->wpdb_obj->tables, $this->wpdb_obj->global_tables, $this->wpdb_obj->ms_global_tables); - } catch (Exception $e) { - $updraftplus->log($e->getMessage()); - } - - if (empty($core_tables)) $core_tables = array('terms', 'term_taxonomy', 'termmeta', 'term_relationships', 'commentmeta', 'comments', 'links', 'postmeta', 'posts', 'site', 'sitemeta', 'blogs', 'blogversions', 'blogmeta'); - - global $updraftplus; - $na = UpdraftPlus_Manipulation_Functions::str_replace_once($our_table_prefix, '', $a); - $nb = UpdraftPlus_Manipulation_Functions::str_replace_once($our_table_prefix, '', $b); - if (in_array($na, $core_tables) && !in_array($nb, $core_tables)) return -1; - if (!in_array($na, $core_tables) && in_array($nb, $core_tables)) return 1; - return strcmp($a, $b); - } - - /** - * Log the amount account space free/used, if possible - */ - private function log_account_space() { - // Don't waste time if space is huge - if (!empty($this->account_space_oodles)) return; - global $updraftplus; - $hosting_bytes_free = $updraftplus->get_hosting_disk_quota_free(); - if (is_array($hosting_bytes_free)) { - $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1); - $updraftplus->log(sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %")); - } - } - - /** - * Returns the basename up to and including the nonce (but not the entity) - * - * @param Integer $use_time epoch time to use - * @return String - */ - private function get_backup_file_basename_from_time($use_time) { - global $updraftplus; - return apply_filters('updraftplus_get_backup_file_basename_from_time', 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $use_time), 'Y-m-d-Hi').'_'.$this->site_name.'_'.$updraftplus->file_nonce, $use_time, $this->site_name); - } - - /** - * Find the zip files for a given nonce - * - * @param String $dir - directory to look in - * @param Strign $match_nonce - backup ID to match - * - * @return Array - */ - private function find_existing_zips($dir, $match_nonce) { - $zips = array(); - if ($handle = opendir($dir)) { - while (false !== ($entry = readdir($handle))) { - if ("." != $entry && ".." != $entry) { - if (preg_match('/^backup_(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?\.zip$/i', $entry, $matches)) { - if ($matches[6] !== $match_nonce) continue; - $timestamp = mktime($matches[4], $matches[5], 0, $matches[2], $matches[3], $matches[1]); - $entity = $matches[7]; - $index = empty($matches[8]) ? '0' : $matches[8]; - $zips[$entity][$index] = array($timestamp, $entry); - } - } - } - } - return $zips; - } - - /** - * Get information on whether a particular file exists in a set - * - * @param Array $files should be an array as returned by find_existing_zips()] - * @param String $entity entty of the file (e.g. 'plugins') - * @param Integer $index Index within the files array - * @return String|Boolean - false if the file does not exist; otherwise, the basename - */ - private function file_exists($files, $entity, $index = 0) { - if (isset($files[$entity]) && isset($files[$entity][$index])) { - $file = $files[$entity][$index]; - // Return the filename - return $file[1]; - } else { - return false; - } - } - - /** - * This function is resumable - * - * @param String $job_status Current status - * - * @return Array - array of backed-up files - */ - private function backup_dirs($job_status) { - - global $updraftplus; - - if (!$updraftplus->backup_time) $updraftplus->backup_time_nonce(); - - $use_time = $updraftplus->backup_time; - $backup_file_basename = $this->get_backup_file_basename_from_time($use_time); - - $backup_array = array(); - - // Was there a check-in last time? If not, then reduce the amount of data attempted - if ('finished' != $job_status && $updraftplus->current_resumption >= 2) { - - // NOTYET: Possible amendment to original algorithm; not just no check-in, but if the check in was very early (can happen if we get a very early checkin for some trivial operation, then attempt something too big) - - // 03-Sep-2015 - came across a case (HS#2052) where there apparently was a check-in 'last time', but no resumption was scheduled because the 'useful_checkin' jobdata was *not* last time - which must indicate dying at a very unfortunate/unlikely point in the code. As a result, the split was not auto-reduced. Consequently, we've added !$updraftplus->newresumption_scheduled as a condition on the first check here (it was already on the second), as if no resumption is scheduled then whatever checkin there was last time was only partial. This was on GoDaddy, for which a number of curious I/O event combinations have been seen in recent months - their platform appears to have some odd behaviour when PHP is killed off. - // 04-Sep-2015 - move the '$updraftplus->current_resumption<=10' check to the inner loop (instead of applying to this whole section), as I see no reason for that restriction (case seen in HS#2064 where it was required on resumption 15) - if (!empty($updraftplus->no_checkin_last_time) || !$updraftplus->newresumption_scheduled) { - // Apr 2015: !$updraftplus->newresumption_scheduled added after seeing a log where there was no activity on resumption 9, and extra resumption 10 then tried the same operation. - if ($updraftplus->current_resumption - $updraftplus->last_successful_resumption > 2 || !$updraftplus->newresumption_scheduled) { - $this->try_split = true; - } elseif ($updraftplus->current_resumption <= 10) { - $maxzipbatch = $updraftplus->jobdata_get('maxzipbatch', 26214400); - if ((int) $maxzipbatch < 1) $maxzipbatch = 26214400; - $new_maxzipbatch = max(floor($maxzipbatch * 0.75), 20971520); - if ($new_maxzipbatch < $maxzipbatch) { - $updraftplus->log("No check-in was detected on the previous run - as a result, we are reducing the batch amount (old=$maxzipbatch, new=$new_maxzipbatch)"); - $updraftplus->jobdata_set('maxzipbatch', $new_maxzipbatch); - $updraftplus->jobdata_set('maxzipbatch_ceiling', $new_maxzipbatch); - } - } - } - } - - if ('finished' != $job_status && !UpdraftPlus_Filesystem_Functions::really_is_writable($this->updraft_dir)) { - $updraftplus->log("Backup directory (".$this->updraft_dir.") is not writable, or does not exist"); - $updraftplus->log(sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $this->updraft_dir), 'error'); - return array(); - } - - $this->job_file_entities = $updraftplus->jobdata_get('job_file_entities'); - - // This is just used for the visual feedback (via the 'substatus' key) - $which_entity = 0; - // e.g. plugins, themes, uploads, others - // $whichdir might be an array (if $youwhat is 'more') - - // Returns an array (keyed off the entity) of ($timestamp, $filename) arrays - $existing_zips = $this->find_existing_zips($this->updraft_dir, $updraftplus->file_nonce); - - $possible_backups = $updraftplus->get_backupable_file_entities(true); - - foreach ($possible_backups as $youwhat => $whichdir) { - - if (!isset($this->job_file_entities[$youwhat])) { - $updraftplus->log("No backup of $youwhat: excluded by user's options"); - continue; - } - - $index = (int) $this->job_file_entities[$youwhat]['index']; - if (empty($index)) $index=0; - $indextext = (0 == $index) ? '' : (1+$index); - - $zip_file = $this->updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.$indextext.'.zip'; - - // Split needed? - $split_every = max((int) $updraftplus->jobdata_get('split_every'), 250); - - if (false != ($existing_file = $this->file_exists($existing_zips, $youwhat, $index)) && filesize($this->updraft_dir.'/'.$existing_file) > $split_every*1048576) { - $index++; - $this->job_file_entities[$youwhat]['index'] = $index; - $updraftplus->jobdata_set('job_file_entities', $this->job_file_entities); - } - - // Populate prior parts of $backup_array, if we're on a subsequent zip file - if ($index > 0) { - for ($i=0; $i<$index; $i++) { - $itext = (0 == $i) ? '' : ($i+1); - // Get the previously-stored filename if possible (which should be always); failing that, base it on the current run - - $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][$i])) ? $this->backup_files_array[$youwhat][$i] : $backup_file_basename.'-'.$youwhat.$itext.'.zip'; - - $backup_array[$youwhat][$i] = $zip_file; - $z = $this->updraft_dir.'/'.$zip_file; - $itext = (0 == $i) ? '' : $i; - - $fs_key = $youwhat.$itext.'-size'; - if (file_exists($z)) { - $backup_array[$fs_key] = filesize($z); - } elseif (isset($this->backup_files_array[$fs_key])) { - $backup_array[$fs_key] = $this->backup_files_array[$fs_key]; - } - } - } - - // I am not certain that all the conditions in here are possible. But there's no harm. - if ('finished' == $job_status) { - // Add the final part of the array - if ($index > 0) { - $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][$index])) ? $this->backup_files_array[$youwhat][$index] : $backup_file_basename.'-'.$youwhat.($index+1).'.zip'; - $z = $this->updraft_dir.'/'.$zip_file; - $fs_key = $youwhat.$index.'-size'; - $backup_array[$youwhat][$index] = $zip_file; - if (file_exists($z)) { - $backup_array[$fs_key] = filesize($z); - } elseif (isset($this->backup_files_array[$fs_key])) { - $backup_array[$fs_key] = $this->backup_files_array[$fs_key]; - } - } else { - $zip_file = (isset($this->backup_files_array[$youwhat]) && isset($this->backup_files_array[$youwhat][0])) ? $this->backup_files_array[$youwhat][0] : $backup_file_basename.'-'.$youwhat.'.zip'; - - $backup_array[$youwhat] = $zip_file; - $fs_key=$youwhat.'-size'; - - if (file_exists($zip_file)) { - $backup_array[$fs_key] = filesize($zip_file); - } elseif (isset($this->backup_files_array[$fs_key])) { - $backup_array[$fs_key] = $this->backup_files_array[$fs_key]; - } - } - } else { - - $which_entity++; - $updraftplus->jobdata_set('filecreating_substatus', array('e' => $youwhat, 'i' => $which_entity, 't' => count($this->job_file_entities))); - - if ('others' == $youwhat) $updraftplus->log("Beginning backup of other directories found in the content directory (index: $index)"); - - // Apply a filter to allow add-ons to provide their own method for creating a zip of the entity - $created = apply_filters('updraftplus_backup_makezip_'.$youwhat, $whichdir, $backup_file_basename, $index); - - // If the filter did not lead to something being created, then use the default method - if ($created === $whichdir) { - - // http://www.phpconcept.net/pclzip/user-guide/53 - /* First parameter to create is: - An array of filenames or dirnames, - or - A string containing the filename or a dirname, - or - A string containing a list of filename or dirname separated by a comma. - */ - - if ('others' == $youwhat) { - $dirlist = $updraftplus->backup_others_dirlist(true); - } elseif ('uploads' == $youwhat) { - $dirlist = $updraftplus->backup_uploads_dirlist(true); - } else { - $dirlist = $whichdir; - if (is_array($dirlist)) $dirlist = array_shift($dirlist); - } - - if (!empty($dirlist)) { - $created = $this->create_zip($dirlist, $youwhat, $backup_file_basename, $index); - // Now, store the results - if (!is_string($created) && !is_array($created)) $updraftplus->log("$youwhat: create_zip returned an error"); - } else { - $updraftplus->log("No backup of $youwhat: there was nothing found to backup"); - } - } - - if ($created != $whichdir && (is_string($created) || is_array($created))) { - if (is_string($created)) $created =array($created); - foreach ($created as $fname) { - $backup_array[$youwhat][$index] = $fname; - $itext = (0 == $index) ? '' : $index; - $index++; - $backup_array[$youwhat.$itext.'-size'] = filesize($this->updraft_dir.'/'.$fname); - } - } - - $this->job_file_entities[$youwhat]['index'] = $this->index; - $updraftplus->jobdata_set('job_file_entities', $this->job_file_entities); - - } - } - - return $backup_array; - } - - /** - * This uses a saved status indicator; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the saved status indicator just helps save resources. - * - * @param Integer $resumption_no Check for first run - * - * @return Array - */ - public function resumable_backup_of_files($resumption_no) { - global $updraftplus; - // Backup directories and return a numerically indexed array of file paths to the backup files - $bfiles_status = $updraftplus->jobdata_get('backup_files'); - $this->backup_files_array = $updraftplus->jobdata_get('backup_files_array'); - - if (!is_array($this->backup_files_array)) $this->backup_files_array = array(); - if ('finished' == $bfiles_status) { - $updraftplus->log("Creation of backups of directories: already finished"); - // Check for recent activity - foreach ($this->backup_files_array as $files) { - if (!is_array($files)) $files =array($files); - foreach ($files as $file) $updraftplus->check_recent_modification($this->updraft_dir.'/'.$file); - } - } elseif ('begun' == $bfiles_status) { - $this->first_run = apply_filters('updraftplus_filerun_firstrun', 0); - if ($resumption_no > $this->first_run) { - $updraftplus->log("Creation of backups of directories: had begun; will resume"); - } else { - $updraftplus->log("Creation of backups of directories: beginning"); - } - $updraftplus->jobdata_set('jobstatus', 'filescreating'); - $this->backup_files_array = $this->backup_dirs($bfiles_status); - $updraftplus->jobdata_set('backup_files_array', $this->backup_files_array); - $updraftplus->jobdata_set('backup_files', 'finished'); - $updraftplus->jobdata_set('jobstatus', 'filescreated'); - } else { - // This is not necessarily a backup run which is meant to contain files at all - $updraftplus->log('This backup run is not intended for files - skipping'); - return array(); - } - - /* - // DOES NOT WORK: there is no crash-safe way to do this here - have to be renamed at cloud-upload time instead - $new_backup_array = array(); - foreach ($backup_array as $entity => $files) { - if (!is_array($files)) $files=array($files); - $outof = count($files); - foreach ($files as $ind => $file) { - $nval = $file; - if (preg_match('/^(backup_[\-0-9]{15}_.*_[0-9a-f]{12}-[\-a-z]+)([0-9]+)?\.zip$/i', $file, $matches)) { - $num = max((int)$matches[2],1); - $new = $matches[1].$num.'of'.$outof.'.zip'; - if (file_exists($this->updraft_dir.'/'.$file)) { - if (@rename($this->updraft_dir.'/'.$file, $this->updraft_dir.'/'.$new)) { - $updraftplus->log(sprintf("Renaming: %s to %s", $file, $new)); - $nval = $new; - } - } elseif (file_exists($this->updraft_dir.'/'.$new)) { - $nval = $new; - } - } - $new_backup_array[$entity][$ind] = $nval; - } - } - */ - return $this->backup_files_array; - } - - /** - * This function is resumable, using the following method: - * Each table is written out to ($final_filename).table.tmp - * When the writing finishes, it is renamed to ($final_filename).table - * When all tables are finished, they are concatenated into the final file - * - * @param String $already_done Status of backup - * @param String $whichdb Indicated which database is being backed up - * @param Array $dbinfo is only used when whichdb != 'wp'; and the keys should be: user, pass, name, host, prefix - * - * @return Boolean|String - the basename of the database backup, or false for failure - */ - public function backup_db($already_done = 'begun', $whichdb = 'wp', $dbinfo = array()) { - - global $updraftplus, $wpdb; - - $this->whichdb = $whichdb; - $this->whichdb_suffix = ('wp' == $whichdb) ? '' : $whichdb; - - if (!$updraftplus->backup_time) $updraftplus->backup_time_nonce(); - if (!$updraftplus->opened_log_time) $updraftplus->logfile_open($updraftplus->nonce); - - if ('wp' == $this->whichdb) { - $this->wpdb_obj = $wpdb; - // The table prefix after being filtered - i.e. what filters what we'll actually backup - $this->table_prefix = $updraftplus->get_table_prefix(true); - // The unfiltered table prefix - i.e. the real prefix that things are relative to - $this->table_prefix_raw = $updraftplus->get_table_prefix(false); - $dbinfo['host'] = DB_HOST; - $dbinfo['name'] = DB_NAME; - $dbinfo['user'] = DB_USER; - $dbinfo['pass'] = DB_PASSWORD; - } else { - if (!is_array($dbinfo) || empty($dbinfo['host'])) return false; - // The methods that we may use: check_connection (WP>=3.9), get_results, get_row, query - $this->wpdb_obj = new UpdraftPlus_WPDB_OtherDB($dbinfo['user'], $dbinfo['pass'], $dbinfo['name'], $dbinfo['host']); - if (!empty($this->wpdb_obj->error)) { - $updraftplus->log($dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'].' : database connection attempt failed'); - $updraftplus->log($dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'].' : '.__('database connection attempt failed.', 'updraftplus').' '.__('Connection failed: check your access details, that the database server is up, and that the network connection is not firewalled.', 'updraftplus'), 'error'); - return $updraftplus->log_wp_error($this->wpdb_obj->error); - } - $this->table_prefix = $dbinfo['prefix']; - $this->table_prefix_raw = $dbinfo['prefix']; - } - - $this->dbinfo = $dbinfo; - - do_action('updraftplus_backup_db_begin', $whichdb, $dbinfo, $already_done, $this); - - UpdraftPlus_Database_Utility::set_sql_mode(array(), array('ANSI_QUOTES'), $this->wpdb_obj); - - $errors = 0; - - $use_time = apply_filters('updraftplus_base_backup_timestamp', $updraftplus->backup_time); - $file_base = $this->get_backup_file_basename_from_time($use_time); - $backup_file_base = $this->updraft_dir.'/'.$file_base; - - if ('finished' == $already_done) return basename($backup_file_base).'-db'.(('wp' == $whichdb) ? '' : $whichdb).'.gz'; - if ('encrypted' == $already_done) return basename($backup_file_base).'-db'.(('wp' == $whichdb) ? '' : $whichdb).'.gz.crypt'; - - $updraftplus->jobdata_set('jobstatus', 'dbcreating'.$this->whichdb_suffix); - - $binsqldump = $updraftplus->find_working_sqldump(); - - $total_tables = 0; - - // WP 3.9 onwards - https://core.trac.wordpress.org/browser/trunk/src/wp-includes/wp-db.php?rev=27925 - check_connection() allows us to get the database connection back if it had dropped - if ('wp' == $whichdb && method_exists($this->wpdb_obj, 'check_connection') && (!defined('UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS') || !UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS)) { - if (!$this->wpdb_obj->check_connection(false)) { - UpdraftPlus_Job_Scheduler::reschedule(60); - $updraftplus->log("It seems the database went away; scheduling a resumption and terminating for now"); - UpdraftPlus_Job_Scheduler::record_still_alive(); - die; - } - } - - // SHOW FULL - so that we get to know whether it's a BASE TABLE or a VIEW - $all_tables = $this->wpdb_obj->get_results("SHOW FULL TABLES", ARRAY_N); - - if (empty($all_tables) && !empty($this->wpdb_obj->last_error)) { - $all_tables = $this->wpdb_obj->get_results("SHOW TABLES", ARRAY_N); - $all_tables = array_map(array($this, 'cb_get_name_base_type'), $all_tables); - } else { - $all_tables = array_map(array($this, 'cb_get_name_type'), $all_tables); - } - - // If this is not the WP database, then we do not consider it a fatal error if there are no tables - if ('wp' == $whichdb && 0 == count($all_tables)) { - $extra = ($updraftplus->newresumption_scheduled) ? ' - '.__('please wait for the rescheduled attempt', 'updraftplus') : ''; - $updraftplus->log("Error: No WordPress database tables found (SHOW TABLES returned nothing)".$extra); - $updraftplus->log(__("No database tables found", 'updraftplus').$extra, 'error'); - die; - } - - // Put the options table first - usort($all_tables, array($this, 'backup_db_sorttables')); - - $all_table_names = array_map(array($this, 'cb_get_name'), $all_tables); - - if (!UpdraftPlus_Filesystem_Functions::really_is_writable($this->updraft_dir)) { - $updraftplus->log("The backup directory (".$this->updraft_dir.") could not be written to (could be account/disk space full, or wrong permissions)."); - $updraftplus->log($this->updraft_dir.": ".__('The backup directory is not writable (or disk space is full) - the database backup is expected to shortly fail.', 'updraftplus'), 'warning'); - // Why not just fail now? We saw a bizarre case when the results of really_is_writable() changed during the run. - } - - // This check doesn't strictly get all possible duplicates; it's only designed for the case that can happen when moving between deprecated Windows setups and Linux - $this->duplicate_tables_exist = false; - foreach ($all_table_names as $table) { - if (strtolower($table) != $table && in_array(strtolower($table), $all_table_names)) { - $this->duplicate_tables_exist = true; - $updraftplus->log("Tables with names differing only based on case-sensitivity exist in the MySQL database: $table / ".strtolower($table)); - } - } - $how_many_tables = count($all_tables); - - $stitch_files = array(); - $found_options_table = false; - $is_multisite = is_multisite(); - - $anonymisation_options = $updraftplus->jobdata_get('anonymisation_options', array()); - - if (!empty($anonymisation_options)) { - $updraftplus->log("Anonymisation options have been set, so mysqldump (which does not support them) will be disabled."); - } - - // Gather the list of files that look like partial table files once only - $potential_stitch_files = array(); - $table_file_prefix_base= $file_base.'-db'.$this->whichdb_suffix.'-table-'; - if (false !== ($dir_handle = opendir($this->updraft_dir))) { - while (false !== ($e = readdir($dir_handle))) { - // The 'r' in 'tmpr' indicates that the new scheme is being used. N.B. That does *not* imply that the table has a usable primary key. - if (!is_file($this->updraft_dir.'/'.$e)) continue; - if (preg_match('#'.$table_file_prefix_base.'.*\.table\.tmpr?(\d+)\.gz$#', $e, $matches)) { - // We need to stich them in order - $potential_stitch_files[] = $e; - } - } - } else { - $updraftplus->log("Error: Failed to open directory for reading"); - $updraftplus->log(__("Failed to open directory for reading:", 'updraftplus').' '.$this->updraft_dir, 'error'); - } - - $errors_at_all_tables_start = $updraftplus->error_count(); - - foreach ($all_tables as $ti) { - - $table = $ti['name']; - $stitch_files[$table] = array(); - $table_type = $ti['type']; - $errors_at_table_start = $updraftplus->error_count(); - - $manyrows_warning = false; - $total_tables++; - - // Increase script execution time-limit to 15 min for every table. - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // The table file may already exist if we have produced it on a previous run - $table_file_prefix = $file_base.'-db'.$this->whichdb_suffix.'-table-'.$table.'.table'; - - if ('wp' == $whichdb && (strtolower($this->table_prefix_raw.'options') == strtolower($table) || ($is_multisite && (strtolower($this->table_prefix_raw.'sitemeta') == strtolower($table) || strtolower($this->table_prefix_raw.'1_options') == strtolower($table))))) $found_options_table = true; - - // Already finished? - if (file_exists($this->updraft_dir.'/'.$table_file_prefix.'.gz')) { - $stitched = count($stitch_files, COUNT_RECURSIVE); - $skip_dblog = (($stitched > 10 && 0 != $stitched % 20) || ($stitched > 100 && 0 != $stitched % 100)); - $updraftplus->log("Table $table: corresponding file already exists; moving on", 'notice', false, $skip_dblog); - - $max_record = false; - foreach ($potential_stitch_files as $e) { - // The 'r' in 'tmpr' indicates that the new scheme is being used. N.B. That does *not* imply that the table has a usable primary key. - if (preg_match('#'.$table_file_prefix.'\.tmpr?(\d+)\.gz$#', $e, $matches)) { - // We need to stich them in order - $stitch_files[$table][$matches[1]] = $e; - if (false === $max_record || $matches[1] > $max_record) $max_record = $matches[1]; - } - } - $stitch_files[$table][$max_record+1] = $table_file_prefix.'.gz'; - - // Move on to the next table - continue; - } - - // === is needed with strpos/stripos, otherwise 'false' matches (i.e. prefix does not match) - if (empty($this->table_prefix) || (!$this->duplicate_tables_exist && 0 === stripos($table, $this->table_prefix)) || ($this->duplicate_tables_exist && 0 === strpos($table, $this->table_prefix))) { - - // Skip table due to filter? - if (!apply_filters('updraftplus_backup_table', true, $table, $this->table_prefix, $whichdb, $dbinfo)) { - $updraftplus->log("Skipping table (filtered): $table"); - if (empty($this->skipped_tables)) $this->skipped_tables = array(); - - // whichdb could be an int in which case to get the name of the database and the array key use the name from dbinfo - $key = ('wp' === $whichdb) ? 'wp' : $dbinfo['name']; - - if (empty($this->skipped_tables[$key])) $this->skipped_tables[$key] = array(); - $this->skipped_tables[$key][] = $table; - - $total_tables--; - continue; - } - - add_filter('updraftplus_backup_table_sql_where', array($this, 'backup_exclude_jobdata'), 3, 10); - - $updraftplus->jobdata_set('dbcreating_substatus', array('t' => $table, 'i' => $total_tables, 'a' => $how_many_tables)); - - // .tmp.gz is the current temporary file. When the row limit has been reached, it is moved to .tmp1.gz, .tmp2.gz, etc. (depending on which already exist). When we're all done, then they all get stitched in. - - $db_temp_file = $this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz'; - $updraftplus->check_recent_modification($db_temp_file); - - // Open file, store the handle - if (false === $this->backup_db_open($db_temp_file, true)) return false; - - $table_status = $this->wpdb_obj->get_row("SHOW TABLE STATUS WHERE Name='$table'"); - - // Create the preceding SQL statements for the table - $this->stow("# " . sprintf('Table: %s', UpdraftPlus_Manipulation_Functions::backquote($table)) . "\n"); - if (isset($table_status->Rows)) { - $rows = $table_status->Rows; - $updraftplus->log("Table $table: Total expected rows (approximate): ".$rows); - $this->stow("# Approximate rows expected in table: $rows\n"); - if ($rows > UPDRAFTPLUS_WARN_DB_ROWS) { - $manyrows_warning = true; - $updraftplus->log(sprintf(__("Table %s has very many rows (%s) - we hope your web hosting company gives you enough resources to dump out that table in the backup", 'updraftplus'), $table, $rows).' '.__('If not, you will need to either remove data from this table, or contact your hosting company to request more resources.', 'updraftplus'), 'warning', 'manyrows_'.$this->whichdb_suffix.$table); - } - } - - // If no check-in last time, then we could in future try the other method (but - any point in retrying slow method on large tables??) - - // New Jul 2014: This attempt to use bindump instead at a lower threshold is quite conservative - only if the last successful run was exactly two resumptions ago - may be useful to expand - $bindump_threshold = (!$updraftplus->something_useful_happened && !empty($updraftplus->current_resumption) && (2 == $updraftplus->current_resumption - $updraftplus->last_successful_resumption)) ? 1000 : 8000; - - $bindump = (isset($table_status->Rows) && ($table_status->Rows>$bindump_threshold || (defined('UPDRAFTPLUS_ALWAYS_TRY_MYSQLDUMP') && UPDRAFTPLUS_ALWAYS_TRY_MYSQLDUMP)) && is_string($binsqldump) && empty($anonymisation_options)) ? $this->backup_table_bindump($binsqldump, $table) : false; - - // Means "start of table". N.B. The meaning of an integer depends upon whether the table has a usable primary key or not. - $start_record = true; - $can_use_primary_key = apply_filters('updraftplus_can_use_primary_key_default', true, $table); - foreach ($potential_stitch_files as $e) { - // The 'r' in 'tmpr' indicates that the new scheme is being used. N.B. That does *not* imply that the table has a usable primary key. - if (preg_match('#'.$table_file_prefix.'\.tmp(r)?(\d+)\.gz$#', $e, $matches)) { - $stitch_files[$table][$matches[2]] = $e; - if (true === $start_record || $matches[2] > $start_record) $start_record = $matches[2]; - // Legacy scheme. The purpose of this is to prevent backups failing if one is in progress during an upgrade to a new version that implements the new scheme - if ('r' !== $matches[1]) $can_use_primary_key = false; - } - } - - // Legacy file-naming scheme in use - if (false === $can_use_primary_key && true !== $start_record) { - $start_record = ($start_record + 100) * 1000; - } - - if (true !== $bindump) { - - while (!is_array($start_record) && !is_wp_error($start_record)) { - $start_record = $this->backup_table($table, $table_type, $start_record, $can_use_primary_key); - if (is_integer($start_record) || is_array($start_record)) { - - $this->backup_db_close(); - - // Add one here in case no records were returned - don't want to over-write the previous file - $use_record = is_array($start_record) ? (isset($start_record['next_record']) ? $start_record['next_record']+1 : false) : $start_record; - if (!$can_use_primary_key) $use_record = (ceil($use_record/100000)-1) * 100; - - if (false !== $use_record) { - // N.B. Renaming using the *next* record is intentional - it allows UD to know where to resume from. - $rename_base = $table_file_prefix.'.tmp'.($can_use_primary_key ? 'r' : '').$use_record.'.gz'; - - rename($db_temp_file, $this->updraft_dir.'/'.$rename_base); - $stitch_files[$table][$use_record] = $rename_base; - } - - UpdraftPlus_Job_Scheduler::something_useful_happened(); - - if (false === $this->backup_db_open($db_temp_file, true)) return false; - - } elseif (is_wp_error($start_record)) { - $message = "Error (table=$table, type=$table_type) (".$start_record->get_error_code()."): ".$start_record->get_error_message(); - $updraftplus->log($message); - // If it's a view, then the problem isn't recoverable; but views don't contain actual data except in the definition, which is likely in code, so we should not consider this a fatal error - $level = 'error'; - if ('view' == strtolower($table_type) && 'table_details_error' == $start_record->get_error_code()) { - $level = 'warning'; - $this->stow("# $message\n"); - } - $updraftplus->log(__("Failed to backup database table:", 'updraftplus').' '.$start_record->get_error_message().' ('.$start_record->get_error_code().')', $level); - } - - } - } - - // If we got this far, then there were enough resources; the warning can be removed - if (!empty($manyrows_warning)) $updraftplus->log_remove_warning('manyrows_'.$this->whichdb_suffix.$table); - - $this->backup_db_close(); - - if ($updraftplus->error_count() > $errors_at_table_start) { - - $updraftplus->log('Errors occurred during backing up the table; therefore the open file will be removed'); - @unlink($db_temp_file); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - } else { - - // Renaming the file indicates that writing to it finished - rename($db_temp_file, $this->updraft_dir.'/'.$table_file_prefix.'.gz'); - UpdraftPlus_Job_Scheduler::something_useful_happened(); - - $final_stitch_value = empty($stitch_files[$table]) ? 1 : max(array_keys($stitch_files[$table])) + 1; - - $stitch_files[$table][$final_stitch_value] = $table_file_prefix.'.gz'; - - $total_db_size = 0; - // This is more verbose than it would be if we weren't supporting PHP 5.2 - foreach ($stitch_files[$table] as $basename) { - $total_db_size += filesize($this->updraft_dir.'/'.$basename); - } - - $updraftplus->log("Table $table: finishing file(s) (".count($stitch_files[$table]).', '.round($total_db_size/1024, 1).' KB)', 'notice', false, false); - } - - } else { - $total_tables--; - $updraftplus->log("Skipping table (lacks our prefix (".$this->table_prefix.")): $table"); - if (empty($this->skipped_tables)) $this->skipped_tables = array(); - // whichdb could be an int in which case to get the name of the database and the array key use the name from dbinfo - $key = ('wp' === $whichdb) ? 'wp' : $dbinfo['name']; - if (empty($this->skipped_tables[$key])) $this->skipped_tables[$key] = array(); - $this->skipped_tables[$key][] = $table; - } - } - - if ('wp' == $whichdb) { - if (!$found_options_table) { - if ($is_multisite) { - $updraftplus->log(__('The database backup appears to have failed', 'updraftplus').' - '.__('no options or sitemeta table was found', 'updraftplus'), 'warning', 'optstablenotfound'); - } else { - $updraftplus->log(__('The database backup appears to have failed', 'updraftplus').' - '.__('the options table was not found', 'updraftplus'), 'warning', 'optstablenotfound'); - } - $time_this_run = time()-$updraftplus->opened_log_time; - if ($time_this_run > 2000) { - // Have seen this happen; not sure how, but it was apparently deterministic; if the current process had been running for a long time, then apparently all database commands silently failed. - // If we have been running that long, then the resumption may be far off; bring it closer - UpdraftPlus_Job_Scheduler::reschedule(60); - $updraftplus->log("Have been running very long, and it seems the database went away; scheduling a resumption and terminating for now"); - UpdraftPlus_Job_Scheduler::record_still_alive(); - die; - } - } else { - $updraftplus->log_remove_warning('optstablenotfound'); - } - } - - if ($updraftplus->error_count() > $errors_at_all_tables_start) { - $updraftplus->log('Errors occurred whilst backing up the tables; will cease and wait for resumption'); - die; - } - - // Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side - $backup_final_file_name = $backup_file_base.'-db'.$this->whichdb_suffix.'.gz'; - $time_now = time(); - $time_mod = (int) @filemtime($backup_final_file_name);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<30) { - UpdraftPlus_Job_Scheduler::terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod); - } - - if (file_exists($backup_final_file_name)) { - $updraftplus->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another UpdraftPlus terminated; thus we will continue."); - } - - // Finally, stitch the files together - if (!function_exists('gzopen')) { - $updraftplus->log("PHP function is disabled; abort expected: gzopen()"); - } - - if (false === $this->backup_db_open($backup_final_file_name, true)) return false; - - $this->backup_db_header(); - - // We delay the unlinking because if two runs go concurrently and fail to detect each other (should not happen, but there's no harm in assuming the detection failed) then that would lead to files missing from the db dump - $unlink_files = array(); - - $sind = 1; - - foreach ($stitch_files as $table => $table_stitch_files) { - ksort($table_stitch_files); - foreach ($table_stitch_files as $table_file) { - $updraftplus->log("{$table_file} ($sind/$how_many_tables): adding to final database dump"); - if (!$handle = gzopen($this->updraft_dir.'/'.$table_file, "r")) { - $updraftplus->log("Error: Failed to open database file for reading: ${table_file}.gz"); - $updraftplus->log(__("Failed to open database file for reading:", 'updraftplus').' '.$table_file, 'error'); - $errors++; - } else { - while ($line = gzgets($handle, 65536)) { - $this->stow($line); - } - gzclose($handle); - $unlink_files[] = $this->updraft_dir.'/'.$table_file; - } - $sind++; - // Came across a database with 7600 tables... adding them all took over 500 seconds; and so when the resumption started up, no activity was detected - if (0 == $sind % 100) UpdraftPlus_Job_Scheduler::something_useful_happened(); - } - } - - // DB triggers - if ($this->wpdb_obj->get_results("SHOW TRIGGERS")) { - // N.B. DELIMITER is not a valid SQL command; you cannot pass it to the server. It has to be interpreted by the interpreter - e.g. /usr/bin/mysql, or UpdraftPlus, and used to interpret what follows. The effect of this is that using it means that some SQL clients will stumble; but, on the other hand, failure to use it means that others that don't have special support for CREATE TRIGGER may stumble, because they may feed incomplete statements to the SQL server. Since /usr/bin/mysql uses it, we choose to support it too (both reading and writing). - // Whatever the delimiter is set to needs to be used in the DROP TRIGGER and CREATE TRIGGER commands in this section further down. - $this->stow("DELIMITER ;;\n\n"); - foreach ($all_tables as $ti) { - $table = $ti['name']; - if (!empty($this->skipped_tables)) { - if ('wp' == $this->whichdb) { - if (in_array($table, $this->skipped_tables['wp'])) continue; - } elseif (isset($this->skipped_tables[$this->dbinfo['name']])) { - if (in_array($table, $this->skipped_tables[$this->dbinfo['name']])) continue; - } - } - $table_triggers = $this->wpdb_obj->get_results($wpdb->prepare("SHOW TRIGGERS LIKE %s", $table), ARRAY_A); - if ($table_triggers) { - $this->stow("\n\n# Triggers of ".UpdraftPlus_Manipulation_Functions::backquote($table)."\n\n"); - foreach ($table_triggers as $trigger) { - $trigger_name = $trigger['Trigger']; - $trigger_time = $trigger['Timing']; - $trigger_event = $trigger['Event']; - $trigger_statement = $trigger['Statement']; - // Since trigger name can include backquotes and trigger name is typically enclosed with backquotes as well, the backquote escaping for the trigger name can be done by adding a leading backquote - $quoted_escaped_trigger_name = UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $trigger_name)); - $this->stow("DROP TRIGGER IF EXISTS $quoted_escaped_trigger_name;;\n"); - $trigger_query = "CREATE TRIGGER $quoted_escaped_trigger_name $trigger_time $trigger_event ON ".UpdraftPlus_Manipulation_Functions::backquote($table)." FOR EACH ROW $trigger_statement;;"; - $this->stow("$trigger_query\n\n"); - } - } - } - $this->stow("DELIMITER ;\n\n"); - } - - // DB Stored Routines - $stored_routines = UpdraftPlus_Database_Utility::get_stored_routines(); - if (is_array($stored_routines) && !empty($stored_routines)) { - $updraftplus->log("Dumping routines for database {$this->dbinfo['name']}"); - $this->stow("\n\n# Dumping routines for database ".UpdraftPlus_Manipulation_Functions::backquote($this->dbinfo['name'])."\n\n"); - $this->stow("DELIMITER ;;\n\n"); - foreach ($stored_routines as $routine) { - $routine_name = $routine['Name']; - // Since routine name can include backquotes and routine name is typically enclosed with backquotes as well, the backquote escaping for the routine name can be done by adding a leading backquote - $quoted_escaped_routine_name = UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $routine_name)); - $this->stow("DROP {$routine['Type']} IF EXISTS $quoted_escaped_routine_name;;\n\n"); - $this->stow($routine['Create '.ucfirst(strtolower($routine['Type']))]."\n\n;;\n\n"); - $updraftplus->log("Dumping routine: {$routine['Name']}"); - } - $this->stow("DELIMITER ;\n\n"); - } elseif (is_wp_error($stored_routines)) { - $updraftplus->log($stored_routines->get_error_message()); - } - - $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"); - - $updraftplus->log($file_base.'-db'.$this->whichdb_suffix.'.gz: finished writing out complete database file ('.round(filesize($backup_final_file_name)/1024, 1).' KB)'); - if (!$this->backup_db_close()) { - $updraftplus->log('An error occurred whilst closing the final database file'); - $updraftplus->log(__('An error occurred whilst closing the final database file', 'updraftplus'), 'error'); - $errors++; - } - - foreach ($unlink_files as $unlink_file) { - @unlink($unlink_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - if ($errors > 0) return false; - - // We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop - $updraftplus->jobdata_set('jobstatus', 'dbcreated'.$this->whichdb_suffix); - - $checksums = $updraftplus->which_checksums(); - - $checksum_description = ''; - - foreach ($checksums as $checksum) { - - $cksum = hash_file($checksum, $backup_final_file_name); - $updraftplus->jobdata_set($checksum.'-db'.(('wp' == $whichdb) ? '0' : $whichdb.'0'), $cksum); - if ($checksum_description) $checksum_description .= ', '; - $checksum_description .= "$checksum: $cksum"; - - } - - $updraftplus->log("Total database tables backed up: $total_tables (".basename($backup_final_file_name).", size: ".filesize($backup_final_file_name).", $checksum_description)"); - - return basename($backup_final_file_name); - - } - - /** - * This function will return a SQL WHERE clause to exclude updraft jobdata - * - * @param array $where - an array of where clauses to add to - * @param string $table - the table we want to add a where clause for - * - * @return array - returns an array of where clauses for the table - */ - public function backup_exclude_jobdata($where, $table) { - // Don't include the job data for any backups - so that when the database is restored, it doesn't continue an apparently incomplete backup - if ('wp' == $this->whichdb && (!empty($this->table_prefix) && strtolower($this->table_prefix.'sitemeta') == strtolower($table))) { - $where[] = 'meta_key NOT LIKE "updraft_jobdata_%"'; - } elseif ('wp' == $this->whichdb && (!empty($this->table_prefix) && strtolower($this->table_prefix.'options') == strtolower($table))) { - // These might look similar, but the quotes are different - if ('win' == strtolower(substr(PHP_OS, 0, 3))) { - $updraft_jobdata = "'updraft_jobdata_%'"; - $site_transient_update = "'_site_transient_update_%'"; - } else { - $updraft_jobdata = '"updraft_jobdata_%"'; - $site_transient_update = '"_site_transient_update_%"'; - } - - $where[] = 'option_name NOT LIKE '.$updraft_jobdata.' AND option_name NOT LIKE '.$site_transient_update.''; - } - - return $where; - } - - /** - * Produce a dump of the table using a mysqldump binary - * - * @param String $potsql - the path to the mysqldump binary - * @param String $table_name - the name of the table being dumped - * - * @return Boolean - success status - */ - private function backup_table_bindump($potsql, $table_name) { - - $microtime = microtime(true); - - global $updraftplus; - - // Deal with Windows/old MySQL setups with erroneous table prefixes differing in case - // Can't get binary mysqldump to make this transformation - // $dump_as_table = ($this->duplicate_tables_exist == false && stripos($table, $this->table_prefix) === 0 && strpos($table, $this->table_prefix) !== 0) ? $this->table_prefix.substr($table, strlen($this->table_prefix)) : $table; - - $pfile = md5(time().rand()).'.tmp'; - file_put_contents($this->updraft_dir.'/'.$pfile, "[mysqldump]\npassword=".$this->dbinfo['pass']."\n"); - - $where_array = apply_filters('updraftplus_backup_table_sql_where', array(), $table_name, $this); - $where = ''; - - if (!empty($where_array) && is_array($where_array)) { - // N.B. Don't add a WHERE prefix here; most versions of mysqldump silently strip it out, but one was encountered that didn't. - $first_loop = true; - foreach ($where_array as $condition) { - if (!$first_loop) $where .= " AND "; - $where .= $condition; - $first_loop = false; - } - } - - // Note: escapeshellarg() adds quotes around the string - if ($where) $where = "--where=".escapeshellarg($where); - - if (strtolower(substr(PHP_OS, 0, 3)) == 'win') { - $exec = "cd ".escapeshellarg(str_replace('/', '\\', $this->updraft_dir))." & "; - } else { - $exec = "cd ".escapeshellarg($this->updraft_dir)."; "; - } - - // Allow --max_allowed_packet to be configured via constant. Experience has shown some customers with complex CMS or pagebuilder setups can have extrememly large postmeta entries. - $msqld_max_allowed_packet = (defined('UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET') && (is_int(UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET) || is_string(UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET))) ? UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET : '1M'; - - $exec .= "$potsql --defaults-file=$pfile $where --max_allowed_packet=$msqld_max_allowed_packet --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --user=".escapeshellarg($this->dbinfo['user'])." --host=".escapeshellarg($this->dbinfo['host'])." ".$this->dbinfo['name']." ".escapeshellarg($table_name); - - $ret = false; - $any_output = false; - $writes = 0; - $handle = popen($exec, "r"); - if ($handle) { - while (!feof($handle)) { - $w = fgets($handle); - if ($w) { - $this->stow($w); - $writes++; - $any_output = true; - } - } - $ret = pclose($handle); - if (0 != $ret) { - $updraftplus->log("Binary mysqldump: error (code: $ret)"); - // Keep counter of failures? Change value of binsqldump? - } else { - if ($any_output) { - $updraftplus->log("Table $table_name: binary mysqldump finished (writes: $writes) in ".sprintf("%.02f", max(microtime(true)-$microtime, 0.00001))." seconds"); - $ret = true; - } - } - } else { - $updraftplus->log("Binary mysqldump error: bindump popen failed"); - } - - // Clean temporary files - @unlink($this->updraft_dir.'/'.$pfile);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - return $ret; - - } - - /** - * Write out the initial backup information for a table to the currently open file - * - * @param String $table - Full name of database table to backup - * @param String $dump_as_table - Table name to use when writing out - * @param String $table_type - Table type - 'VIEW' is supported; otherwise it is treated as an ordinary table - * @param Array $table_structure - Table structure as returned by a DESCRIBE command - */ - private function write_table_backup_beginning($table, $dump_as_table, $table_type, $table_structure) { - - $this->stow("\n# Delete any existing table ".UpdraftPlus_Manipulation_Functions::backquote($table)."\n\nDROP TABLE IF EXISTS " . UpdraftPlus_Manipulation_Functions::backquote($dump_as_table).";\n"); - - if ('VIEW' == $table_type) { - $this->stow("DROP VIEW IF EXISTS " . UpdraftPlus_Manipulation_Functions::backquote($dump_as_table) . ";\n"); - } - - $description = ('VIEW' == $table_type) ? 'view' : 'table'; - - $this->stow("\n# Table structure of $description ".UpdraftPlus_Manipulation_Functions::backquote($table)."\n\n"); - - $create_table = $this->wpdb_obj->get_results("SHOW CREATE TABLE ".UpdraftPlus_Manipulation_Functions::backquote($table), ARRAY_N); - if (false === $create_table) { - $this->stow("#\n# Error with SHOW CREATE TABLE for $table\n#\n"); - } - $create_line = UpdraftPlus_Manipulation_Functions::str_lreplace('TYPE=', 'ENGINE=', $create_table[0][1]); - - // Remove PAGE_CHECKSUM parameter from MyISAM - was internal, undocumented, later removed (so causes errors on import) - if (preg_match('/ENGINE=([^\s;]+)/', $create_line, $eng_match)) { - $engine = $eng_match[1]; - if ('myisam' == strtolower($engine)) { - $create_line = preg_replace('/PAGE_CHECKSUM=\d\s?/', '', $create_line, 1); - } - } - - if ($dump_as_table !== $table) $create_line = UpdraftPlus_Manipulation_Functions::str_replace_once($table, $dump_as_table, $create_line); - - $this->stow($create_line.' ;'); - - if (false === $table_structure) { - $this->stow("#\n# Error getting $description structure of $table\n#\n"); - } - - // Add a comment preceding the beginning of the data - $this->stow("\n\n# ".sprintf("Data contents of $description %s", UpdraftPlus_Manipulation_Functions::backquote($table))."\n\n"); - - } - - /** - * Suggest a beginning value for how many rows to fetch in each SELECT statement (before taking into account resumptions) - * - * @param String $table - * - * @return Integer - */ - private function get_rows_on_first_fetch($table) { - - // In future, we could run over the table definition; if it is all non-massive defined lengths, we could base a calculation on that. - - if ($this->table_prefix_raw.'term_relationships' == $table) { - // This table is known to have very small data lengths - $rows = 100000; - } else { - $rows = 1000; - } - - return $rows; - - } - - /** - * Suggest how many rows to fetch in each SELECT statement - * - * @param String $table - the table being fetched - * @param Boolean $allow_further_reductions - whether to enable a second level of reductions - * @param Boolean $is_first_fetch_for_table - whether this is the first fetch on this table - * - * @return Integer - */ - private function number_of_rows_to_fetch($table, $allow_further_reductions, $is_first_fetch_for_table) { - - global $updraftplus; - - $default_on_first_fetch = $this->get_rows_on_first_fetch($table); - - // If this is not the first fetch on a table, then get what was stored last time we set it (if we ever did). On the first fetch, reset back to the starting value (we presume problems are table-specific). - // This means that the same value will persist whilst the table is being backed up, both during the current resumption, and subsequent ones - $fetch_rows = $is_first_fetch_for_table ? $default_on_first_fetch : $updraftplus->jobdata_get('fetch_rows', $default_on_first_fetch); - - $fetch_rows_at_start = $fetch_rows; - - $resumptions_since_last_successful = $updraftplus->current_resumption - $updraftplus->last_successful_resumption; - - // Do we need to reduce the number of rows we attempt to fetch? - // If something useful has happened on this run, then we don't try any reductions (we save them for a resumption after one on which nothing useful happened) - if (!$updraftplus->something_useful_happened && !empty($updraftplus->current_resumption) && $resumptions_since_last_successful > 1) { - // This used to be fixed at 500; but we (after a long time) saw a case that looked like an out-of-memory even at this level. Now that we have implemented resumptions, the risk of timeouts is much lower (we just need to process enough rows). - // October 2020: added further reductions - // Listed in increasing order due to the handling below. At the end it gets quite drastic. Note, though, that currently we don't store this in the job-data. - // A future improvement could, when things get drastic, grab and log data on the size of what is required, so that we can respond more dynamically. The strategy currently here will run out of road if memory falls short multiple times. See: https://stackoverflow.com/questions/4524019/how-to-get-the-byte-size-of-resultset-in-an-sql-query - $fetch_rows_reductions = array(500, 250, 200, 100); - - if ($allow_further_reductions) { - // If we're relying on LIMIT with offsets, then we have to be mindful of how that performs - $fetch_rows_reductions = array_merge($fetch_rows_reductions, array(50, 20, 5)); - } - - $break_after = $is_first_fetch_for_table ? $resumptions_since_last_successful - 1 : 1; - - foreach ($fetch_rows_reductions as $reduce_to) { - if ($fetch_rows > $reduce_to) { - // Go down one level - $fetch_rows = $reduce_to; - $break_after--; - if ($break_after < 1) break; - } - } - - $updraftplus->log("Last successful resumption was $resumptions_since_last_successful runs ago; fetch_rows will thus be: $fetch_rows (allow_further_reductions=$allow_further_reductions, is_first_fetch=$is_first_fetch_for_table)"); - } - - // If it has changed, then preserve it in the job for the next resumption (of this table) - if ($fetch_rows_at_start !== $fetch_rows || $is_first_fetch_for_table) $updraftplus->jobdata_set('fetch_rows', $fetch_rows); - - return $fetch_rows; - - } - - /** - * Original version taken partially from phpMyAdmin and partially from Alain Wolf, Zurich - Switzerland to use the WordPress $wpdb object - * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/ - * Modified by Scott Merrill (http://www.skippy.net/) - * Subsequently heavily improved and modified - * - * This method should be called in a loop for a complete table backup (see the information for the returned parameter). The method may implement whatever strategy it likes for deciding when to return (the assumption is that when it does return with some results, the caller should register that something useful happened). - * - * @param String $table - Full name of database table to backup - * @param String $table_type - Table type - 'VIEW' is supported; otherwise it is treated as an ordinary table - * @param Integer|Boolean $start_record - Specify the starting record, or true to start at the beginning. Our internal page size is fixed at 1000 (though within that we might actually query in smaller batches). - * @param Boolean $can_use_primary_key - Whether it is allowed to perform quicker SELECTS based on the primary key. The intended use case for false is to support backups running during a version upgrade. N.B. This "can" is not absolute; there may be other constraints dealt with within this method. - * - * @return Integer|Array|WP_Error - a WP_Error to indicate an error; an array indicates that it finished (if it includes 'next_record' that means it finished via producing something); an integer to indicate the next page the case that there are more to do. - */ - private function backup_table($table, $table_type = 'BASE TABLE', $start_record = true, $can_use_primary_key = true) { - $process_pages = 100; - - // Preserve the passed-in value - $original_start_record = $start_record; - - global $updraftplus; - - $microtime = microtime(true); - $total_rows = 0; - - // Deal with Windows/old MySQL setups with erroneous table prefixes differing in case - $dump_as_table = (false == $this->duplicate_tables_exist && 0 === stripos($table, $this->table_prefix) && 0 !== strpos($table, $this->table_prefix)) ? $this->table_prefix.substr($table, strlen($this->table_prefix)) : $table; - - $table_structure = $this->wpdb_obj->get_results("DESCRIBE ".UpdraftPlus_Manipulation_Functions::backquote($table)); - if (!$table_structure) { - // $updraftplus->log(__('Error getting table details', 'updraftplus') . ": $table", 'error'); - $error_message = ''; - if ($this->wpdb_obj->last_error) $error_message .= ' ('.$this->wpdb_obj->last_error.')'; - return new WP_Error('table_details_error', $error_message); - } - - // If at the beginning of the dump for a table, then add the DROP and CREATE statements - if (true === $start_record) { - $this->write_table_backup_beginning($table, $dump_as_table, $table_type, $table_structure); - } - - // Some tables have optional data, and should be skipped if they do not work - $table_sans_prefix = substr($table, strlen($this->table_prefix_raw)); - $data_optional_tables = ('wp' == $this->whichdb) ? apply_filters('updraftplus_data_optional_tables', explode(',', UPDRAFTPLUS_DATA_OPTIONAL_TABLES)) : array(); - if (in_array($table_sans_prefix, $data_optional_tables)) { - if (!$updraftplus->something_useful_happened && !empty($updraftplus->current_resumption) && ($updraftplus->current_resumption - $updraftplus->last_successful_resumption > 2)) { - $updraftplus->log("Table $table: Data skipped (previous attempts failed, and table is marked as non-essential)"); - return array(); - } - } - - $table_data = array(); - if ('VIEW' != $table_type) { - $fields = array(); - $defs = array(); - $integer_fields = array(); - $binary_fields = array(); - $bit_fields = array(); - $bit_field_exists = false; - - // false means "not yet set"; a string means what it was set to; null means that there are multiple (and so not useful to us). If it is not a string, then $primary_key_type is invalid and should not be used. - $primary_key = false; - $primary_key_type = false; - - // $table_structure was from "DESCRIBE $table" - foreach ($table_structure as $struct) { - - if (isset($struct->Key) && 'PRI' == $struct->Key && '' != $struct->Field) { - $primary_key = (false === $primary_key) ? $struct->Field : null; - $primary_key_type = $struct->Type; - } - - if ((0 === strpos($struct->Type, 'tinyint')) || (0 === strpos(strtolower($struct->Type), 'smallint')) - || (0 === strpos(strtolower($struct->Type), 'mediumint')) || (0 === strpos(strtolower($struct->Type), 'int')) || (0 === strpos(strtolower($struct->Type), 'bigint')) - ) { - $defs[strtolower($struct->Field)] = (null === $struct->Default) ? 'NULL' : $struct->Default; - $integer_fields[strtolower($struct->Field)] = true; - } - - if ((0 === strpos(strtolower($struct->Type), 'binary')) || (0 === strpos(strtolower($struct->Type), 'varbinary')) || (0 === strpos(strtolower($struct->Type), 'tinyblob')) || (0 === strpos(strtolower($struct->Type), 'mediumblob')) || (0 === strpos(strtolower($struct->Type), 'blob')) || (0 === strpos(strtolower($struct->Type), 'longblob'))) { - $binary_fields[strtolower($struct->Field)] = true; - } - - if (preg_match('/^bit(?:\(([0-9]+)\))?$/i', trim($struct->Type), $matches)) { - if (!$bit_field_exists) $bit_field_exists = true; - $bit_fields[strtolower($struct->Field)] = !empty($matches[1]) ? max(1, (int) $matches[1]) : 1; - // the reason why if bit fields are found then the fields need to be cast into binary type is that if mysqli_query function is being used, mysql will convert the bit field value to a decimal number and represent it in a string format whereas, if mysql_query function is being used, mysql will not convert it to a decimal number but instead will keep it retained as it is - $struct->Field = "CAST(".UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $struct->Field))." AS BINARY) AS ".UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $struct->Field)); - $fields[] = $struct->Field; - } else { - $fields[] = UpdraftPlus_Manipulation_Functions::backquote(str_replace('`', '``', $struct->Field)); - } - } - - // N.B. At this stage this is for optimisation, mainly targets what is used on the core WP tables (bigint(20)); a value can be relied upon, but false is not definitive - $use_primary_key = false; - if ($can_use_primary_key && is_string($primary_key) && preg_match('#^(small|medium|big)?int\(#i', $primary_key_type)) { - $use_primary_key = true; - if (preg_match('# unsigned$#i', $primary_key_type)) { - if (true === $start_record) $start_record = -1; - } else { - if (true === $start_record) { - $min_value = $this->wpdb_obj->get_var('SELECT MIN('.UpdraftPlus_Manipulation_Functions::backquote($primary_key).') FROM '.UpdraftPlus_Manipulation_Functions::backquote($table)); - $start_record = (is_numeric($min_value) && $min_value) ? (int) $min_value - 1 : -1; - } - } - } - $search = array("\x00", "\x0a", "\x0d", "\x1a"); - $replace = array('\0', '\n', '\r', '\Z'); - - $where_array = apply_filters('updraftplus_backup_table_sql_where', array(), $table, $this); - $where = ''; - if (!empty($where_array) && is_array($where_array)) { - $where = 'WHERE '.implode(' AND ', $where_array); - } - - // Experimentation here shows that on large tables (we tested with 180,000 rows) on MyISAM, 1000 makes the table dump out 3x faster than the previous value of 100. After that, the benefit diminishes (increasing to 4000 only saved another 12%) - - $fetch_rows = $this->number_of_rows_to_fetch($table, $use_primary_key || $start_record < 500000, true === $original_start_record); - - $select = $bit_field_exists ? implode(', ', $fields) : '*'; - - $enough_for_now = false; - - $began_writing_at = time(); - - $enough_data_after = 104857600; - $enough_time_after = ($fetch_rows > 250) ? 15 : 9; - - // Loop which retrieves data - do { - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - // Reset back to that which has constructed before the loop began - $final_where = $where; - - if ($use_primary_key) { - - // The point of this is to leverage the indexing on the private key to make the SELECT much faster than index-less paging - $final_where = $where . ($where ? ' AND ' : 'WHERE '); - - // If it's -1, then we avoid mentioning a negative value, as the value may be unsigned - $final_where .= UpdraftPlus_Manipulation_Functions::backquote($primary_key).((-1 === $start_record) ? ' >= 0' : " > $start_record"); - - $limit_statement = sprintf('LIMIT %d', $fetch_rows); - - $order_by = 'ORDER BY '.UpdraftPlus_Manipulation_Functions::backquote($primary_key).' ASC'; - - } else { - $order_by = ''; - if (true === $start_record) $start_record = 0; - $limit_statement = sprintf('LIMIT %d, %d', $start_record, $fetch_rows); - } - - // $this->wpdb_obj->prepare() not needed (will throw a notice) as there are no parameters - - $select_sql = "SELECT $select FROM ".UpdraftPlus_Manipulation_Functions::backquote($table)." $final_where $order_by $limit_statement"; - - // Allow the data to be filtered (e.g. anonymisation) - $table_data = apply_filters('updraftplus_backup_table_results', $this->wpdb_obj->get_results($select_sql, ARRAY_A), $table, $this->table_prefix, $this->whichdb); - - if (!$table_data) continue; - $entries = 'INSERT INTO '.UpdraftPlus_Manipulation_Functions::backquote($dump_as_table).' VALUES '; - - // \x08\\x09, not required - - $thisentry = ''; - foreach ($table_data as $row) { - $total_rows++; - if ($thisentry) $thisentry .= ",\n "; - $thisentry .= '('; - $key_count = 0; - foreach ($row as $key => $value) { - - if ($key_count) $thisentry .= ', '; - $key_count++; - - if ($use_primary_key && strtolower($primary_key) == strtolower($key) && $value > $start_record) { - $start_record = $value; - } - - if (isset($integer_fields[strtolower($key)])) { - // make sure there are no blank spots in the insert syntax, - // yet try to avoid quotation marks around integers - $value = (null === $value || '' === $value) ? $defs[strtolower($key)] : $value; - $value = ('' === $value) ? "''" : $value; - $thisentry .= $value; - } elseif (isset($binary_fields[strtolower($key)])) { - if (null === $value) { - $thisentry .= 'NULL'; - } elseif ('' === $value) { - $thisentry .= "''"; - } else { - $thisentry .= "0x" . bin2hex(str_repeat("0", floor(strspn($value, "0") / 4)).$value); - } - } elseif (isset($bit_fields[$key])) { - mbstring_binary_safe_encoding(); - $val_len = strlen($value); - reset_mbstring_encoding(); - $hex = ''; - for ($i=0; $i<$val_len; $i++) { - $hex .= sprintf('%02X', ord($value[$i])); - } - $thisentry .= "b'".str_pad($this->hex2bin($hex), $bit_fields[$key], '0', STR_PAD_LEFT)."'"; - } else { - $thisentry .= (null === $value) ? 'NULL' : "'" . str_replace($search, $replace, str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value))) . "'"; - } - } - $thisentry .= ')'; - - // Flush every 512KB - if (strlen($thisentry) > 524288) { - $thisentry .= ';'; - if (strlen($thisentry) > 10485760) { - // This is an attempt to prevent avoidable duplication of long strings in-memory, at the cost of one extra write - $this->stow(" \n".$entries); - $this->stow($thisentry); - } else { - $this->stow(" \n".$entries.$thisentry); - } - $thisentry = ''; - // Potentially indicate that enough has been done to loop - if ($this->db_current_raw_bytes > $enough_data_after || time() - $began_writing_at > $enough_time_after) { - $enough_for_now = true; - } - } - - } - if ($thisentry) { - $thisentry .= ';'; - if (strlen($thisentry) > 10485760) { - // This is an attempt to prevent avoidable duplication of long strings in-memory, at the cost of one extra write - $this->stow(" \n".$entries); - $this->stow($thisentry); - } else { - $this->stow(" \n".$entries.$thisentry); - } - } - - if (!$use_primary_key) { - $start_record += $fetch_rows; - } - - if ($process_pages > 0) $process_pages--; - - } while (!$enough_for_now && count($table_data) > 0 && (-1 == $process_pages || $process_pages > 0)); - } - - $updraftplus->log("Table $table: Rows added in this batch (next record: $start_record): $total_rows (uncompressed bytes in this segment=".$this->db_current_raw_bytes.") in ".sprintf("%.02f", max(microtime(true)-$microtime, 0.00001))." seconds"); - - // If all data has been fetched, then write out the closing comment - if (-1 == $process_pages || 0 == count($table_data)) { - $this->stow("\n# End of data contents of table ".UpdraftPlus_Manipulation_Functions::backquote($table)."\n\n"); - return is_numeric($start_record) ? array('next_record' => (int) $start_record) : array(); - } - - return is_numeric($start_record) ? (int) $start_record : $start_record; - - } - - /** - * Convert hexadecimal (base16) number into binary (base2) and no need to worry about the platform-dependent of 32bit/64bit size limitation - * - * @param String $hex Hexadecimal number - * @return String a base2 format of the given hexadecimal number - */ - public function hex2bin($hex) { - $table = array( - '0' => '0000', - '1' => '0001', - '2' => '0010', - '3' => '0011', - '4' => '0100', - '5' => '0101', - '6' => '0110', - '7' => '0111', - '8' => '1000', - '9' => '1001', - 'a' => '1010', - 'b' => '1011', - 'c' => '1100', - 'd' => '1101', - 'e' => '1110', - 'f' => '1111' - ); - $bin = ''; - - if (!preg_match('/^[0-9a-f]+$/i', $hex)) return ''; - - for ($i = 0; $i < strlen($hex); $i++) { - $bin .= $table[strtolower(substr($hex, $i, 1))]; - } - - return $bin; - } - - /** - * Encrypts the file if the option is set; returns the basename of the file (according to whether it was encrypted or nto) - * - * @param String $file - file to encrypt - * - * @return array - */ - public function encrypt_file($file) { - global $updraftplus; - $encryption = $updraftplus->get_job_option('updraft_encryptionphrase'); - if (strlen($encryption) > 0) { - $updraftplus->log("Attempting to encrypt backup file"); - try { - $result = apply_filters('updraft_encrypt_file', null, $file, $encryption, $this->whichdb, $this->whichdb_suffix); - } catch (Exception $e) { - $log_message = 'Exception ('.get_class($e).') occurred during encryption: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - // @codingStandardsIgnoreLine - if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary(); - $updraftplus->log($log_message); - $updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during encryption. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - // @codingStandardsIgnoreLine - if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary(); - $updraftplus->log($log_message); - $updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - } - if (null === $result) return basename($file); - return $result; - } else { - return basename($file); - } - } - - /** - * Close the database file currently being written - * - * @return Boolean - */ - private function backup_db_close() { - return $this->dbhandle_isgz ? gzclose($this->dbhandle) : fclose($this->dbhandle); - } - - /** - * Open a file, store its filehandle - * - * @param String $file Full path to the file to open - * @param Boolean $allow_gz Use gzopen() if available, instead of fopen() - * - * @return Resource - the opened file handle - */ - public function backup_db_open($file, $allow_gz = true) { - if (function_exists('gzopen') && true == $allow_gz) { - $this->dbhandle = gzopen($file, 'w'); - $this->dbhandle_isgz = true; - } else { - $this->dbhandle = fopen($file, 'w'); - $this->dbhandle_isgz = false; - } - if (false === $this->dbhandle) { - global $updraftplus; - $updraftplus->log("ERROR: $file: Could not open the backup file for writing"); - $updraftplus->log($file.": ".__("Could not open the backup file for writing", 'updraftplus'), 'error'); - } - $this->db_current_raw_bytes = 0; - return $this->dbhandle; - } - - /** - * Adds a line to the database backup - * - * @param String $query_line - the line to log - * - * @return Integer|Boolean - the number of octets written, or false for a failure (as returned by gzwrite() / fwrite) - */ - public function stow($query_line) { - if ($this->dbhandle_isgz) { - if (false == ($ret = @gzwrite($this->dbhandle, $query_line))) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // $updraftplus->log(__('There was an error writing a line to the backup script:', 'updraftplus').' '.$query_line.' '.$php_errormsg, 'error'); - } - } else { - if (false == ($ret = @fwrite($this->dbhandle, $query_line))) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // $updraftplus->log(__('There was an error writing a line to the backup script:', 'updraftplus').' '.$query_line.' '.$php_errormsg, 'error'); - } - } - $this->db_current_raw_bytes += strlen($query_line); - return $ret; - } - - /** - * Stow the database backup header - */ - private function backup_db_header() { - - global $updraftplus; - $wp_version = $updraftplus->get_wordpress_version(); - $mysql_version = $this->wpdb_obj->get_var('SELECT VERSION()'); - if ('' == $mysql_version) $mysql_version = $this->wpdb_obj->db_version(); - - if ('wp' == $this->whichdb) { - $wp_upload_dir = wp_upload_dir(); - $this->stow("# WordPress MySQL database backup\n"); - $this->stow("# Created by UpdraftPlus version ".$updraftplus->version." (https://updraftplus.com)\n"); - $this->stow("# WordPress Version: $wp_version, running on PHP ".phpversion()." (".$_SERVER["SERVER_SOFTWARE"]."), MySQL $mysql_version\n"); - $this->stow("# Backup of: ".untrailingslashit(site_url())."\n"); - $this->stow("# Home URL: ".untrailingslashit(home_url())."\n"); - $this->stow("# Content URL: ".untrailingslashit(content_url())."\n"); - $this->stow("# Uploads URL: ".untrailingslashit($wp_upload_dir['baseurl'])."\n"); - $this->stow("# Table prefix: ".$this->table_prefix_raw."\n"); - $this->stow("# Filtered table prefix: ".$this->table_prefix."\n"); - $this->stow("# Site info: multisite=".(is_multisite() ? '1' : '0')."\n"); - $this->stow("# Site info: sql_mode=".$this->wpdb_obj->get_var('SELECT @@SESSION.sql_mode')."\n"); - $this->stow("# Site info: end\n"); - } else { - $this->stow("# MySQL database backup (supplementary database ".$this->whichdb.")\n"); - $this->stow("# Created by UpdraftPlus version ".$updraftplus->version." (https://updraftplus.com)\n"); - $this->stow("# WordPress Version: $wp_version, running on PHP ".phpversion()." (".$_SERVER["SERVER_SOFTWARE"]."), MySQL $mysql_version\n"); - $this->stow("# ".sprintf('External database: (%s)', $this->dbinfo['user'].'@'.$this->dbinfo['host'].'/'.$this->dbinfo['name'])."\n"); - $this->stow("# Backup created by: ".untrailingslashit(site_url())."\n"); - $this->stow("# Table prefix: ".$this->table_prefix_raw."\n"); - $this->stow("# Filtered table prefix: ".$this->table_prefix."\n"); - } - - $label = $updraftplus->jobdata_get('label'); - if (!empty($label)) $this->stow("# Label: $label\n"); - - $this->stow("\n# Generated: ".date("l j. F Y H:i T")."\n"); - $this->stow("# Hostname: ".$this->dbinfo['host']."\n"); - $this->stow("# Database: ".UpdraftPlus_Manipulation_Functions::backquote($this->dbinfo['name'])."\n"); - - if (!empty($this->skipped_tables[$this->whichdb])) { - if ('wp' == $this->whichdb) { - $this->stow("# Skipped tables: " . implode(', ', $this->skipped_tables['wp'])."\n"); - } elseif (isset($this->skipped_tables[$this->dbinfo['name']])) { - $this->stow("# Skipped tables: " . implode(', ', $this->skipped_tables[$this->dbinfo['name']])."\n"); - } - } - - $this->stow("# --------------------------------------------------------\n"); - - $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"); - $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"); - $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"); - $this->stow("/*!40101 SET NAMES ".$updraftplus->get_connection_charset($this->wpdb_obj)." */;\n"); - $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n\n"); - - } - - /** - * This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking - * - * @param String $fullpath Full path - * @param String $use_path_when_storing Controls the path to use when storing in the zip file - * @param String $original_fullpath Original path - * @param Integer $startlevels How deep within the directory structure the recursive operation has gone - * @param Array $exclude passed by reference so that we can remove elements as they are matched - saves time checking against already-dealt-with objects] - * @return Boolean - */ - private function makezip_recursive_add($fullpath, $use_path_when_storing, $original_fullpath, $startlevels, &$exclude) { - -// $zipfile = $this->zip_basename.(($this->index == 0) ? '' : ($this->index+1)).'.zip.tmp'; - - global $updraftplus; - - // Only BinZip supports symlinks. This means that as a consistent outcome, the only think that can be done with directory symlinks is either a) potentially duplicate the data or b) skip it. Whilst with internal WP entities (e.g. plugins) we definitely want the data, in the case of user-selected directories, we assume the user knew what they were doing when they chose the directory - i.e. we can skip symlink-accessed data that's outside. - if (is_link($fullpath) && is_dir($fullpath) && 'more' == $this->whichone) { - $updraftplus->log("Directory symlink encounted in more files backup: $use_path_when_storing -> ".readlink($fullpath).": skipping"); - return true; - } - - // De-reference. Important to do to both, because on Windows only doing it to one can make them non-equal, where they were previously equal - something which we later rely upon - $fullpath = realpath($fullpath); - $original_fullpath = realpath($original_fullpath); - - // Is the place we've ended up above the original base? That leads to infinite recursion - if (($fullpath !== $original_fullpath && strpos($original_fullpath, $fullpath) === 0) || ($original_fullpath == $fullpath && ((1== $startlevels && strpos($use_path_when_storing, '/') !== false) || (2 == $startlevels && substr_count($use_path_when_storing, '/') >1)))) { - $updraftplus->log("Infinite recursion: symlink led us to $fullpath, which is within $original_fullpath"); - $updraftplus->log(__("Infinite recursion: consult your log for more information", 'updraftplus'), 'error'); - return false; - } - - // This is sufficient for the ones we have exclude options for - uploads, others, wpcore - $stripped_storage_path = (1 == $startlevels) ? $use_path_when_storing : substr($use_path_when_storing, strpos($use_path_when_storing, '/') + 1); - if (false !== ($fkey = array_search($stripped_storage_path, $exclude))) { - $updraftplus->log("Entity excluded by configuration option: $stripped_storage_path"); - unset($exclude[$fkey]); - return true; - } - - $if_altered_since = $this->makezip_if_altered_since; - - if (is_file($fullpath)) { - if (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($fullpath)) { - $updraftplus->log("Entity excluded by configuration option (extension): ".basename($fullpath)); - } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($fullpath)) { - $updraftplus->log("Entity excluded by configuration option (prefix): ".basename($fullpath)); - } elseif (apply_filters('updraftplus_exclude_file', false, $fullpath)) { - $updraftplus->log("Entity excluded by filter: ".basename($fullpath)); - } elseif (is_readable($fullpath)) { - $mtime = filemtime($fullpath); - $key = ($fullpath == $original_fullpath) ? ((2 == $startlevels) ? $use_path_when_storing : $this->basename($fullpath)) : $use_path_when_storing.'/'.$this->basename($fullpath); - if ($mtime > 0 && $mtime > $if_altered_since) { - $this->zipfiles_batched[$fullpath] = $key; - $this->makezip_recursive_batchedbytes += @filesize($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // @touch($zipfile); - } else { - $this->zipfiles_skipped_notaltered[$fullpath] = $key; - } - } else { - $updraftplus->log("$fullpath: unreadable file"); - $updraftplus->log(sprintf(__("%s: unreadable file - could not be backed up (check the file permissions and ownership)", 'updraftplus'), $fullpath), 'warning'); - } - } elseif (is_dir($fullpath)) { - if ($fullpath == $this->updraft_dir_realpath) { - $updraftplus->log("Skip directory (UpdraftPlus backup directory): $use_path_when_storing"); - return true; - } - - if (apply_filters('updraftplus_exclude_directory', false, $fullpath, $use_path_when_storing)) { - $updraftplus->log("Skip filtered directory: $use_path_when_storing"); - return true; - } - - if (file_exists($fullpath.'/.donotbackup')) { - $updraftplus->log("Skip directory (.donotbackup file found): $use_path_when_storing"); - return true; - } - - if (!isset($this->existing_files[$use_path_when_storing])) $this->zipfiles_dirbatched[] = $use_path_when_storing; - - if (!$dir_handle = @opendir($fullpath)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $updraftplus->log("Failed to open directory: $fullpath"); - $updraftplus->log(sprintf(__("Failed to open directory (check the file permissions and ownership): %s", 'updraftplus'), $fullpath), 'error'); - return false; - } - - while (false !== ($e = readdir($dir_handle))) { - if ('.' == $e || '..' == $e) continue; - - if (is_link($fullpath.'/'.$e)) { - $deref = realpath($fullpath.'/'.$e); - if (is_file($deref)) { - if (is_readable($deref)) { - $use_stripped = $stripped_storage_path.'/'.$e; - if (false !== ($fkey = array_search($use_stripped, $exclude))) { - $updraftplus->log("Entity excluded by configuration option: $use_stripped"); - unset($exclude[$fkey]); - } elseif (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($e)) { - $updraftplus->log("Entity excluded by configuration option (extension): $use_stripped"); - } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($e)) { - $updraftplus->log("Entity excluded by configuration option (prefix): $use_stripped"); - } elseif (apply_filters('updraftplus_exclude_file', false, $deref, $use_stripped)) { - $updraftplus->log("Entity excluded by filter: $use_stripped"); - } else { - $mtime = filemtime($deref); - if ($mtime > 0 && $mtime > $if_altered_since) { - $this->zipfiles_batched[$deref] = $use_path_when_storing.'/'.$e; - $this->makezip_recursive_batchedbytes += @filesize($deref);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // @touch($zipfile); - } else { - $this->zipfiles_skipped_notaltered[$deref] = $use_path_when_storing.'/'.$e; - } - } - } else { - $updraftplus->log("$deref: unreadable file"); - $updraftplus->log(sprintf(__("%s: unreadable file - could not be backed up"), $deref), 'warning'); - } - } elseif (is_dir($deref)) { - -// $link_target = readlink($deref); -// $updraftplus->log("Symbolic link $use_path_when_storing/$e -> $link_target"); - - $this->makezip_recursive_add($deref, $use_path_when_storing.'/'.$e, $original_fullpath, $startlevels, $exclude); - } - } elseif (is_file($fullpath.'/'.$e)) { - if (is_readable($fullpath.'/'.$e)) { - $use_stripped = $stripped_storage_path.'/'.$e; - if (false !== ($fkey = array_search($use_stripped, $exclude))) { - $updraftplus->log("Entity excluded by configuration option: $use_stripped"); - unset($exclude[$fkey]); - } elseif (!empty($this->excluded_extensions) && $this->is_entity_excluded_by_extension($e)) { - $updraftplus->log("Entity excluded by configuration option (extension): $use_stripped"); - } elseif (!empty($this->excluded_prefixes) && $this->is_entity_excluded_by_prefix($e)) { - $updraftplus->log("Entity excluded by configuration option (prefix): $use_stripped"); - } elseif (apply_filters('updraftplus_exclude_file', false, $fullpath.'/'.$e)) { - $updraftplus->log("Entity excluded by filter: $use_stripped"); - } else { - $mtime = filemtime($fullpath.'/'.$e); - if ($mtime > 0 && $mtime > $if_altered_since) { - $this->zipfiles_batched[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e; - $this->makezip_recursive_batchedbytes += @filesize($fullpath.'/'.$e);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - $this->zipfiles_skipped_notaltered[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e; - } - } - } else { - $updraftplus->log("$fullpath/$e: unreadable file"); - $updraftplus->log(sprintf(__("%s: unreadable file - could not be backed up", 'updraftplus'), $use_path_when_storing.'/'.$e), 'warning', "unrfile-$e"); - } - } elseif (is_dir($fullpath.'/'.$e)) { - if ('wpcore' == $this->whichone && 'updraft' == $e && basename($use_path_when_storing) == 'wp-content' && (!defined('UPDRAFTPLUS_WPCORE_INCLUDE_UPDRAFT_DIRS') || !UPDRAFTPLUS_WPCORE_INCLUDE_UPDRAFT_DIRS)) { - // This test, of course, won't catch everything - it just aims to make things better by default - $updraftplus->log("Directory excluded for looking like a sub-site's internal UpdraftPlus directory (enable by defining UPDRAFTPLUS_WPCORE_INCLUDE_UPDRAFT_DIRS): ".$use_path_when_storing.'/'.$e); - } else { - // no need to add_empty_dir here, as it gets done when we recurse - $this->makezip_recursive_add($fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath, $startlevels, $exclude); - } - } - } - closedir($dir_handle); - } else { - $updraftplus->log("Unexpected: path ($use_path_when_storing) fails both is_file() and is_dir()"); - } - - return true; - - } - - private function get_excluded_extensions($exclude) { - if (!is_array($exclude)) $exclude = array(); - $exclude_extensions = array(); - foreach ($exclude as $ex) { - if (preg_match('/^ext:(.+)$/i', $ex, $matches)) { - $exclude_extensions[] = strtolower($matches[1]); - } - } - - if (defined('UPDRAFTPLUS_EXCLUDE_EXTENSIONS')) { - $exclude_from_define = explode(',', UPDRAFTPLUS_EXCLUDE_EXTENSIONS); - foreach ($exclude_from_define as $ex) { - $exclude_extensions[] = strtolower(trim($ex)); - } - } - - return $exclude_extensions; - } - - private function get_excluded_prefixes($exclude) { - if (!is_array($exclude)) $exclude = array(); - $exclude_prefixes = array(); - foreach ($exclude as $pref) { - if (preg_match('/^prefix:(.+)$/i', $pref, $matches)) { - $exclude_prefixes[] = strtolower($matches[1]); - } - } - - return $exclude_prefixes; - } - - private function is_entity_excluded_by_extension($entity) { - foreach ($this->excluded_extensions as $ext) { - if (!$ext) continue; - $eln = strlen($ext); - if (strtolower(substr($entity, -$eln, $eln)) == $ext) return true; - } - return false; - } - - private function is_entity_excluded_by_prefix($entity) { - $entity = basename($entity); - foreach ($this->excluded_prefixes as $pref) { - if (!$pref) continue; - $eln = strlen($pref); - if (strtolower(substr($entity, 0, $eln)) == $pref) return true; - } - return false; - } - - private function unserialize_gz_cache_file($file) { - if (!$whandle = gzopen($file, 'r')) return false; - global $updraftplus; - $emptimes = 0; - $var = ''; - while (!gzeof($whandle)) { - $bytes = @gzread($whandle, 1048576);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (empty($bytes)) { - $emptimes++; - $updraftplus->log("Got empty gzread ($emptimes times)"); - if ($emptimes>2) return false; - } else { - $var .= $bytes; - } - } - gzclose($whandle); - return unserialize($var); - } - - - - /** - * Make Zip File. - * - * @param Array|String $source Caution: $source is allowed to be an array, not just a filename - * @param String $backup_file_basename Name of backup file - * @param String $whichone Backup entity type (e.g. 'plugins') - * @param Boolean $retry_on_error Set to retry upon error - * @return Boolean - */ - private function make_zipfile($source, $backup_file_basename, $whichone, $retry_on_error = true) { - - global $updraftplus; - - $original_index = $this->index; - - $itext = (empty($this->index)) ? '' : ($this->index+1); - $destination_base = $backup_file_basename.'-'.$whichone.$itext.'.zip.tmp'; - // $destination is the temporary file (ending in .tmp) - $destination = $this->updraft_dir.'/'.$destination_base; - - // When to prefer PCL: - // - We were asked to - // - No zip extension present and no relevant method present - // The zip extension check is not redundant, because method_exists segfaults some PHP installs, leading to support requests - - // We need meta-info about $whichone - $backupable_entities = $updraftplus->get_backupable_file_entities(true, false); - // This is only used by one corner-case in BinZip - // $this->make_zipfile_source = (isset($backupable_entities[$whichone])) ? $backupable_entities[$whichone] : $source; - $this->make_zipfile_source = (is_array($source) && isset($backupable_entities[$whichone])) ? (('uploads' == $whichone) ? dirname($backupable_entities[$whichone]) : $backupable_entities[$whichone]) : dirname($source); - - $this->existing_files = array(); - // Used for tracking compression ratios - $this->existing_files_rawsize = 0; - $this->existing_zipfiles_size = 0; - - // Enumerate existing files - // Usually first_linked_index is zero; the exception being with more files, where previous zips' contents are irrelevant - for ($j = $this->first_linked_index; $j <= $this->index; $j++) { - $jtext = (0 == $j) ? '' : $j+1; - // This is, in a non-obvious way, compatible with filenames which indicate increments - // $j does not need to start at zero; it should start at the index which the current entity split at. However, this is not directly known, and can only be deduced from examining the filenames. And, for other indexes from before the current increment, the searched-for filename won't exist (even if there is no cloud storage). So, this indirectly results in the desired outcome when we start from $j=0. - $examine_zip = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$jtext.'.zip'.(($j == $this->index) ? '.tmp' : ''); - - // This comes from https://wordpress.org/support/topic/updraftplus-not-moving-all-files-to-remote-server - where it appears that the jobdata's record of the split was done (i.e. database write), but the *earlier* rename of the .tmp file was not done (i.e. I/O lost). i.e. In theory, this should be impossible; but, the sychnronicity apparently cannot be fully relied upon in some setups. The check for the index being one behind is being conservative - there's no inherent reason why it couldn't be done for other indexes. - // Note that in this 'impossible' case, no backup data was being lost - the design still ensures that the on-disk backup is fine. The problem was a gap in the sequence numbering of the zip files, leading to user confusion. - // Other examples of this appear to be in HS#1001 and #1047 - if ($j != $this->index && !file_exists($examine_zip)) { - $alt_examine_zip = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$jtext.'.zip'.(($j == $this->index - 1) ? '.tmp' : ''); - if ($alt_examine_zip != $examine_zip && file_exists($alt_examine_zip) && is_readable($alt_examine_zip) && filesize($alt_examine_zip)>0) { - $updraftplus->log("Looked-for zip file not found; but non-zero .tmp zip was, despite not being current index ($j != ".$this->index." - renaming zip (assume previous resumption's IO was lost before kill)"); - if (rename($alt_examine_zip, $examine_zip)) { - clearstatcache(); - } else { - $updraftplus->log("Rename failed - backup zips likely to not have sequential numbers (does not affect backup integrity, but can cause user confusion)"); - } - } - } - - // If the file exists, then we should grab its index of files inside, and sizes - // Then, when we come to write a file, we should check if it's already there, and only add if it is not - if (file_exists($examine_zip) && is_readable($examine_zip) && filesize($examine_zip)>0) { - $this->existing_zipfiles_size += filesize($examine_zip); - $zip = new $this->use_zip_object; - if (true !== $zip->open($examine_zip)) { - $updraftplus->log("Could not open zip file to examine (".$zip->last_error."); will remove: ".basename($examine_zip)); - @unlink($examine_zip);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - - // Don't put this in the for loop, or the magic __get() method gets repeatedly called every time the loop goes round - $numfiles = $zip->numFiles; - - for ($i=0; $i < $numfiles; $i++) { - $si = $zip->statIndex($i); - $name = $si['name']; - // Exclude folders - if ('/' == substr($name, -1)) continue; - $this->existing_files[$name] = $si['size']; - $this->existing_files_rawsize += $si['size']; - } - - @$zip->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - $updraftplus->log(basename($examine_zip).": Zip file already exists, with ".count($this->existing_files)." files"); - - // try_split is set if there have been no check-ins recently - or if it needs to be split anyway - if ($j == $this->index) { - if (isset($this->try_split)) { - if (filesize($examine_zip) > 50*1048576) { - // We could, as a future enhancement, save this back to the job data, if we see a case that needs it - $this->zip_split_every = max( - (int) $this->zip_split_every/2, - UPDRAFTPLUS_SPLIT_MIN*1048576, - min(filesize($examine_zip)-1048576, $this->zip_split_every) - ); - $updraftplus->jobdata_set('split_every', (int) ($this->zip_split_every/1048576)); - $updraftplus->log("No check-in on last two runs; bumping index and reducing zip split to: ".round($this->zip_split_every/1048576, 1)." MB"); - $do_bump_index = true; - } - unset($this->try_split); - } elseif (filesize($examine_zip) > $this->zip_split_every) { - $updraftplus->log(sprintf("Zip size is at/near split limit (%s MB / %s MB) - bumping index (from: %d)", filesize($examine_zip), round($this->zip_split_every/1048576, 1), $this->index)); - $do_bump_index = true; - } - } - - } elseif (file_exists($examine_zip)) { - $updraftplus->log("Zip file already exists, but is not readable or was zero-sized; will remove: ".basename($examine_zip)); - @unlink($examine_zip);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - } - - $this->zip_last_ratio = ($this->existing_files_rawsize > 0) ? ($this->existing_zipfiles_size/$this->existing_files_rawsize) : 1; - - $this->zipfiles_added = 0; - $this->zipfiles_added_thisrun = 0; - $this->zipfiles_dirbatched = array(); - $this->zipfiles_batched = array(); - $this->zipfiles_skipped_notaltered = array(); - $this->zipfiles_lastwritetime = time(); - $this->zip_basename = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone; - - if (!empty($do_bump_index)) $this->bump_index(); - - $error_occurred = false; - - // Store this in its original form - $this->source = $source; - - // Reset. This counter is used only with PcLZip, to decide if it's better to do it all-in-one - $this->makezip_recursive_batchedbytes = 0; - if (!is_array($source)) $source = array($source); - - $exclude = $updraftplus->get_exclude($whichone); - - $files_enumerated_at = $updraftplus->jobdata_get('files_enumerated_at'); - if (!is_array($files_enumerated_at)) $files_enumerated_at = array(); - $files_enumerated_at[$whichone] = time(); - $updraftplus->jobdata_set('files_enumerated_at', $files_enumerated_at); - - $this->makezip_if_altered_since = is_array($this->altered_since) ? (isset($this->altered_since[$whichone]) ? $this->altered_since[$whichone] : -1) : -1; - - // Reset - $got_uploads_from_cache = false; - - // Uploads: can/should we get it back from the cache? - // || 'others' == $whichone - if (('uploads' == $whichone || 'others' == $whichone) && function_exists('gzopen') && function_exists('gzread')) { - $use_cache_files = false; - $cache_file_base = $this->zip_basename.'-cachelist-'.$this->makezip_if_altered_since; - // Cache file suffixes: -zfd.gz.tmp, -zfb.gz.tmp, -info.tmp, (possible)-zfs.gz.tmp - if (file_exists($cache_file_base.'-zfd.gz.tmp') && file_exists($cache_file_base.'-zfb.gz.tmp') && file_exists($cache_file_base.'-info.tmp')) { - // Cache files exist; shall we use them? - $mtime = filemtime($cache_file_base.'-zfd.gz.tmp'); - // Require < 30 minutes old - if (time() - $mtime < 1800) { - $use_cache_files = true; - } - $any_failures = false; - if ($use_cache_files) { - $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfd.gz.tmp'); - if (is_array($var)) { - $this->zipfiles_dirbatched = $var; - $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfb.gz.tmp'); - if (is_array($var)) { - $this->zipfiles_batched = $var; - if (file_exists($cache_file_base.'-info.tmp')) { - $var = maybe_unserialize(file_get_contents($cache_file_base.'-info.tmp')); - if (is_array($var) && isset($var['makezip_recursive_batchedbytes'])) { - $this->makezip_recursive_batchedbytes = $var['makezip_recursive_batchedbytes']; - if (file_exists($cache_file_base.'-zfs.gz.tmp')) { - $var = $this->unserialize_gz_cache_file($cache_file_base.'-zfs.gz.tmp'); - if (is_array($var)) { - $this->zipfiles_skipped_notaltered = $var; - } else { - $any_failures = true; - } - } else { - $this->zipfiles_skipped_notaltered = array(); - } - } else { - $any_failures = true; - } - } - } else { - $any_failures = true; - } - } else { - $any_failures = true; - } - if ($any_failures) { - $updraftplus->log("Failed to recover file lists from existing cache files"); - // Reset it all - $this->zipfiles_skipped_notaltered = array(); - $this->makezip_recursive_batchedbytes = 0; - $this->zipfiles_batched = array(); - $this->zipfiles_dirbatched = array(); - } else { - $updraftplus->log("File lists recovered from cache files; sizes: ".count($this->zipfiles_batched).", ".count($this->zipfiles_batched).", ".count($this->zipfiles_skipped_notaltered).")"); - $got_uploads_from_cache = true; - } - } - } - } - - $time_counting_began = time(); - - $this->excluded_extensions = $this->get_excluded_extensions($exclude); - $this->excluded_prefixes = $this->get_excluded_prefixes($exclude); - - foreach ($source as $element) { - // makezip_recursive_add($fullpath, $use_path_when_storing, $original_fullpath, $startlevels = 1, $exclude_array) - if ('uploads' == $whichone) { - if (empty($got_uploads_from_cache)) { - $dirname = dirname($element); - $basename = $this->basename($element); - $add_them = $this->makezip_recursive_add($element, basename($dirname).'/'.$basename, $element, 2, $exclude); - } else { - $add_them = true; - } - } else { - if (empty($got_uploads_from_cache)) { - $add_them = $this->makezip_recursive_add($element, $this->basename($element), $element, 1, $exclude); - } else { - $add_them = true; - } - } - if (is_wp_error($add_them) || false === $add_them) $error_occurred = true; - } - - $time_counting_ended = time(); - - // Cache the file scan, if it looks like it'll be useful - // We use gzip to reduce the size as on hosts which limit disk I/O, the cacheing may make things worse - // || 'others' == $whichone - if (('uploads' == $whichone || 'others' == $whichone) && !$error_occurred && function_exists('gzopen') && function_exists('gzwrite')) { - $cache_file_base = $this->zip_basename.'-cachelist-'.$this->makezip_if_altered_since; - - // Just approximate - we're trying to avoid an otherwise-unpredictable PHP fatal error. Cacheing only happens if file enumeration took a long time - so presumably there are very many. - $memory_needed_estimate = 0; - foreach ($this->zipfiles_batched as $k => $v) { - $memory_needed_estimate += strlen($k)+strlen($v)+12; - } - - // We haven't bothered to check if we just fetched the files from cache, as that shouldn't take a long time and so shouldn't trigger this - // Let us suppose we need 15% overhead for gzipping - - $memory_limit = ini_get('memory_limit'); - $memory_usage = round(@memory_get_usage(false)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if ($time_counting_ended-$time_counting_began > 20 && $updraftplus->verify_free_memory($memory_needed_estimate*0.15) && $whandle = gzopen($cache_file_base.'-zfb.gz.tmp', 'w')) { - $updraftplus->log("File counting took a long time (".($time_counting_ended - $time_counting_began)."s); will attempt to cache results (memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M), estimated uncompressed bytes: ".round($memory_needed_estimate/1024, 1)." Kb)"); - - $buf = 'a:'.count($this->zipfiles_batched).':{'; - foreach ($this->zipfiles_batched as $file => $add_as) { - $k = addslashes($file); - $v = addslashes($add_as); - $buf .= 's:'.strlen($k).':"'.$k.'";s:'.strlen($v).':"'.$v.'";'; - if (strlen($buf) > 1048576) { - gzwrite($whandle, $buf, strlen($buf)); - $buf = ''; - } - } - $buf .= '}'; - $final = gzwrite($whandle, $buf); - unset($buf); - - if (!$final) { - @unlink($cache_file_base.'-zfb.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @gzclose($whandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - gzclose($whandle); - if (!empty($this->zipfiles_skipped_notaltered)) { - if ($shandle = gzopen($cache_file_base.'-zfs.gz.tmp', 'w')) { - if (!gzwrite($shandle, serialize($this->zipfiles_skipped_notaltered))) { - $aborted_on_skipped = true; - } - gzclose($shandle); - } else { - $aborted_on_skipped = true; - } - } - if (!empty($aborted_on_skipped)) { - @unlink($cache_file_base.'-zfs.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($cache_file_base.'-zfb.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } else { - $info_array = array('makezip_recursive_batchedbytes' => $this->makezip_recursive_batchedbytes); - if (!file_put_contents($cache_file_base.'-info.tmp', serialize($info_array))) { - @unlink($cache_file_base.'-zfs.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($cache_file_base.'-zfb.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - if ($dhandle = gzopen($cache_file_base.'-zfd.gz.tmp', 'w')) { - if (!gzwrite($dhandle, serialize($this->zipfiles_dirbatched))) { - $aborted_on_dirbatched = true; - } - gzclose($dhandle); - } else { - $aborted_on_dirbatched = true; - } - if (!empty($aborted_on_dirbatched)) { - @unlink($cache_file_base.'-zfs.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($cache_file_base.'-zfd.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($cache_file_base.'-zfb.gz.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($cache_file_base.'-info.tmp');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // @codingStandardsIgnoreLine - } else { - // Success. - } - } - } - } - -/* - Class variables that get altered: - zipfiles_batched - makezip_recursive_batchedbytes - zipfiles_skipped_notaltered - zipfiles_dirbatched - Class variables that the result depends upon (other than the state of the filesystem): - makezip_if_altered_since - existing_files - */ - - } - - // Any not yet dispatched? Under our present scheme, at this point nothing has yet been despatched. And since the enumerating of all files can take a while, we can at this point do a further modification check to reduce the chance of overlaps. - // This relies on us *not* touch()ing the zip file to indicate to any resumption 'behind us' that we're already here. Rather, we're relying on the combined facts that a) if it takes us a while to search the directory tree, then it should do for the one behind us too (though they'll have the benefit of cache, so could catch very fast) and b) we touch *immediately* after finishing the enumeration of the files to add. - // $retry_on_error is here being used as a proxy for 'not the second time around, when there might be the remains of the file on the first time around' - if ($retry_on_error) $updraftplus->check_recent_modification($destination); - // Here we're relying on the fact that both PclZip and ZipArchive will happily operate on an empty file. Note that BinZip *won't* (for that, may need a new strategy - e.g. add the very first file on its own, in order to 'lay down a marker') - if (empty($do_bump_index)) @touch($destination);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if (count($this->zipfiles_dirbatched) > 0 || count($this->zipfiles_batched) > 0) { - - $updraftplus->log(sprintf("Total entities for the zip file: %d directories, %d files (%d skipped as non-modified), %s MB", count($this->zipfiles_dirbatched), count($this->zipfiles_batched), count($this->zipfiles_skipped_notaltered), round($this->makezip_recursive_batchedbytes/1048576, 1))); - - // No need to warn if we're going to retry anyway. (And if we get killed, the zip will be rescanned for its contents upon resumption). - $warn_on_failures = ($retry_on_error) ? false : true; - $add_them = $this->makezip_addfiles($warn_on_failures); - - if (is_wp_error($add_them)) { - foreach ($add_them->get_error_messages() as $msg) { - $updraftplus->log("Error returned from makezip_addfiles: ".$msg); - } - $error_occurred = true; - } elseif (false === $add_them) { - $updraftplus->log("Error: makezip_addfiles returned false"); - $error_occurred = true; - } - - } - - // Reset these variables because the index may have changed since we began - - $itext = empty($this->index) ? '' : $this->index+1; - $destination_base = $backup_file_basename.'-'.$whichone.$itext.'.zip.tmp'; - $destination = $this->updraft_dir.'/'.$destination_base; - - // ZipArchive::addFile sometimes fails - there's nothing when we expected something. - // Did not used to have || $error_occured here. But it is better to retry, than to simply warn the user to check his logs. - if (((file_exists($destination) || $this->index == $original_index) && @filesize($destination) < 90 && 'UpdraftPlus_ZipArchive' == $this->use_zip_object) || ($error_occurred && $retry_on_error)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // This can be made more sophisticated if feedback justifies it. Currently we just switch to PclZip. But, it may have been a BinZip failure, so we could then try ZipArchive if that is available. If doing that, make sure that an infinite recursion isn't made possible. - $updraftplus->log("makezip_addfiles(".$this->use_zip_object.") apparently failed (file=".basename($destination).", type=$whichone, size=".filesize($destination).") - retrying with PclZip"); - $saved_zip_object = $this->use_zip_object; - $this->use_zip_object = 'UpdraftPlus_PclZip'; - $ret = $this->make_zipfile($source, $backup_file_basename, $whichone, false); - $this->use_zip_object = $saved_zip_object; - return $ret; - } - - // zipfiles_added > 0 means that $zip->close() has been called. i.e. An attempt was made to add something: something _should_ be there. - // Why return true even if $error_occurred may be set? 1) Because in that case, a warning has already been logged. 2) Because returning false causes an error to be logged, which means it'll all be retried again. Also 3) this has been the pattern of the code for a long time, and the algorithm has been proven in the real-world: don't change what's not broken. - // (file_exists($destination) || $this->index == $original_index) might be an alternative to $this->zipfiles_added > 0 - ? But, don't change what's not broken. - if (false == $error_occurred || $this->zipfiles_added > 0) { - return true; - } else { - $updraftplus->log("makezip failure: zipfiles_added=".$this->zipfiles_added.", error_occurred=".$error_occurred." (method=".$this->use_zip_object.")"); - return false; - } - - } - - private function basename($element) { - // This function is an ugly, conservative workaround for https://bugs.php.net/bug.php?id=62119. It does not aim to always work-around, but to ensure that nothing is made worse. - $dirname = dirname($element); - $basename_manual = preg_replace('#^[\\/]+#', '', substr($element, strlen($dirname))); - $basename = basename($element); - if ($basename_manual != $basename) { - $locale = setlocale(LC_CTYPE, "0"); - if ('C' == $locale) { - setlocale(LC_CTYPE, 'en_US.UTF8'); - $basename_new = basename($element); - if ($basename_new == $basename_manual) $basename = $basename_new; - setlocale(LC_CTYPE, $locale); - } - } - return $basename; - } - - /** - * Determine if a file should be stored without compression - * - * @param String $file - the filename - * - * @return Boolean - */ - private function file_should_be_stored_without_compression($file) { - if (!is_array($this->extensions_to_not_compress)) return false; - foreach ($this->extensions_to_not_compress as $ext) { - $ext_len = strlen($ext); - if (strtolower(substr($file, -$ext_len, $ext_len)) == $ext) return true; - } - return false; - } - - /** - * This method will add a manifest file to the backup zip - * - * @param String $whichone - the type of backup (e.g. 'plugins', 'themes') - * - * @return Boolean - success/failure status - */ - private function updraftplus_include_manifest($whichone) { - global $updraftplus; - - $manifest_name = "updraftplus-manifest.json"; - $manifest = trailingslashit($this->updraft_dir).$manifest_name; - - $updraftplus->log(sprintf("Creating file manifest ($manifest_name) for incremental backup (included: %d, skipped: %d)", count($this->zipfiles_batched), count($this->zipfiles_skipped_notaltered))); - - if (false === ($handle = fopen($manifest, 'w+'))) return $updraftplus->log("Failed to open manifest file ($manifest_name)"); - - $this->manifest_path = $manifest; - - $version = 1; - - $go_to_levels = array( - 'plugins' => 2, - 'themes' => 2, - 'uploads' => 3, - 'others' => 3 - ); - - $go_to_levels = apply_filters('updraftplus_manifest_go_to_level', $go_to_levels, $whichone); - - $go_to_level = isset($go_to_levels[$whichone]) ? $go_to_levels[$whichone] : 'all'; - - $directory = ''; - - if ('more' == $whichone) { - foreach ($this->zipfiles_batched as $index => $dir) { - $directory = '"directory":"' . dirname($index) . '",'; - } - } - - if (false === fwrite($handle, '{"version":'.$version.',"type":"'.$whichone.'",'.$directory.'"listed_levels":"'.$go_to_level.'","contents":{"directories":[')) $updraftplus->log("First write to manifest file failed ($manifest_name)"); - - // First loop: find out which is the last entry, so that we don't write the comma after it - $last_dir_index = false; - foreach ($this->zipfiles_dirbatched as $index => $dir) { - if ('all' !== $go_to_level && substr_count($dir, '/') > $go_to_level - 1) continue; - $last_dir_index = $index; - } - - // Second loop: write out the entry - foreach ($this->zipfiles_dirbatched as $index => $dir) { - if ('all' !== $go_to_level && substr_count($dir, '/') > $go_to_level - 1) continue; - fwrite($handle, json_encode($dir).(($index != $last_dir_index) ? ',' : '')); - } - - // Now do the same for files - fwrite($handle, '],"files":['); - - $last_file_index = false; - foreach ($this->zipfiles_batched as $store_as) { - if ('all' !== $go_to_level && substr_count($store_as, '/') > $go_to_level - 1) continue; - $last_file_index = $store_as; - } - foreach ($this->zipfiles_skipped_notaltered as $store_as) { - if ('all' !== $go_to_level && substr_count($store_as, '/') > $go_to_level - 1) continue; - $last_file_index = $store_as; - } - - foreach ($this->zipfiles_batched as $store_as) { - if ('all' !== $go_to_level && substr_count($store_as, '/') > $go_to_level - 1) continue; - fwrite($handle, json_encode($store_as).(($store_as != $last_file_index) ? ',' : '')); - } - - foreach ($this->zipfiles_skipped_notaltered as $store_as) { - if ('all' !== $go_to_level && substr_count($store_as, '/') > $go_to_level - 1) continue; - fwrite($handle, json_encode($store_as).(($store_as != $last_file_index) ? ',' : '')); - } - - fwrite($handle, ']}}'); - fclose($handle); - - $this->zipfiles_batched[$manifest] = $manifest_name; - - $updraftplus->log("Successfully created file manifest (size: ".filesize($manifest).")"); - - return true; - } - - // Q. Why don't we only open and close the zip file just once? - // A. Because apparently PHP doesn't write out until the final close, and it will return an error if anything file has vanished in the meantime. So going directory-by-directory reduces our chances of hitting an error if the filesystem is changing underneath us (which is very possible if dealing with e.g. 1GB of files) - - /** - * We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close. - * To call into here, the array $this->zipfiles_batched must be populated (keys=paths, values=add-to-zip-as values). It gets reset upon exit from here. - * - * @param Boolean $warn_on_failures See if it warns on faliures or not - * - * @return Boolean|WP_Error - */ - private function makezip_addfiles($warn_on_failures) { - - global $updraftplus; - - // Used to detect requests to bump the size - $bump_index = false; - $ret = true; - - $zipfile = $this->zip_basename.((0 == $this->index) ? '' : ($this->index+1)).'.zip.tmp'; - - $maxzipbatch = $updraftplus->jobdata_get('maxzipbatch', 26214400); - if ((int) $maxzipbatch < 1024) $maxzipbatch = 26214400; - - // Short-circuit the null case, because we want to detect later if something useful happenned - if (count($this->zipfiles_dirbatched) == 0 && count($this->zipfiles_batched) == 0) return true; - - // If on PclZip, then if possible short-circuit to a quicker method (makes a huge time difference - on a folder of 1500 small files, 2.6s instead of 76.6) - // This assumes that makezip_addfiles() is only called once so that we know about all needed files (the new style) - // This is rather conservative - because it assumes zero compression. But we can't know that in advance. - $force_allinone = false; - if (0 == $this->index && $this->makezip_recursive_batchedbytes < $this->zip_split_every) { - // So far, we only have a processor for this for PclZip; but that check can be removed - need to address the below items - // TODO: Is this really what we want? Always go all-in-one for < 500MB???? Should be more conservative? Or, is it always faster to go all-in-one? What about situations where we might want to auto-split because of slowness - check that that is still working. - // TODO: Test this new method for PclZip - are we still getting the performance gains? Test for ZipArchive too. - if ('UpdraftPlus_PclZip' == $this->use_zip_object && ($this->makezip_recursive_batchedbytes < 512*1048576 || (defined('UPDRAFTPLUS_PCLZIP_FORCEALLINONE') && UPDRAFTPLUS_PCLZIP_FORCEALLINONE == true && 'UpdraftPlus_PclZip' == $this->use_zip_object))) { - $updraftplus->log("Only one archive required (".$this->use_zip_object.") - will attempt to do in single operation (data: ".round($this->makezip_recursive_batchedbytes/1024, 1)." KB, split: ".round($this->zip_split_every/1024, 1)." KB)"); -// $updraftplus->log("PclZip, and only one archive required - will attempt to do in single operation (data: ".round($this->makezip_recursive_batchedbytes/1024, 1)." KB, split: ".round($this->zip_split_every/1024, 1)." KB)"); - $force_allinone = true; -// if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php'); -// $zip = new PclZip($zipfile); -// $remove_path = ($this->whichone == 'wpcore') ? untrailingslashit(ABSPATH) : WP_CONTENT_DIR; -// $add_path = false; -// Remove prefixes -// $backupable_entities = $updraftplus->get_backupable_file_entities(true); -// if (isset($backupable_entities[$this->whichone])) { -// if ('plugins' == $this->whichone || 'themes' == $this->whichone || 'uploads' == $this->whichone) { -// $remove_path = dirname($backupable_entities[$this->whichone]); -// To normalise instead of removing (which binzip doesn't support, so we don't do it), you'd remove the dirname() in the above line, and uncomment the below one. -// #$add_path = $this->whichone; -// } else { -// $remove_path = $backupable_entities[$this->whichone]; -// } -// } -// if ($add_path) { -// $zipcode = $zip->create($this->source, PCLZIP_OPT_REMOVE_PATH, $remove_path, PCLZIP_OPT_ADD_PATH, $add_path); -// } else { -// $zipcode = $zip->create($this->source, PCLZIP_OPT_REMOVE_PATH, $remove_path); -// } -// if ($zipcode == 0) { -// $updraftplus->log("PclZip Error: ".$zip->errorInfo(true), 'warning'); -// return $zip->errorCode(); -// } else { -// UpdraftPlus_Job_Scheduler::something_useful_happened(); -// return true; -// } - } - } - - // 05-Mar-2013 - added a new check on the total data added; it appears that things fall over if too much data is contained in the cumulative total of files that were addFile'd without a close-open cycle; presumably data is being stored in memory. In the case in question, it was a batch of MP3 files of around 100MB each - 25 of those equals 2.5GB! - - $data_added_since_reopen = 0; - // static $data_added_this_resumption = 0; - // $max_data_added_any_resumption = $updraftplus->jobdata_get('max_data_added_any_resumption', 0); - - // The following array is used only for error reporting if ZipArchive::close fails (since that method itself reports no error messages - we have to track manually what we were attempting to add) - $files_zipadded_since_open = array(); - - $zip = new $this->use_zip_object; - if (file_exists($zipfile)) { - $opencode = $zip->open($zipfile); - $original_size = filesize($zipfile); - clearstatcache(); - } else { - $create_code = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1; - $opencode = $zip->open($zipfile, $create_code); - $original_size = 0; - } - - if (true !== $opencode) return new WP_Error('no_open', sprintf(__('Failed to open the zip file (%s) - %s', 'updraftplus'), $zipfile, $zip->last_error)); - - if (apply_filters('updraftplus_include_manifest', false, $this->whichone, $this)) { - $this->updraftplus_include_manifest($this->whichone); - } - - // Make sure all directories are created before we start creating files - while ($dir = array_pop($this->zipfiles_dirbatched)) { - $zip->addEmptyDir($dir); - } - $zipfiles_added_thisbatch = 0; - - // Go through all those batched files - foreach ($this->zipfiles_batched as $file => $add_as) { - - if (!file_exists($file)) { - $updraftplus->log("File has vanished from underneath us; dropping: $add_as"); - continue; - } - - $fsize = filesize($file); - - if (defined('UPDRAFTPLUS_SKIP_FILE_OVER_SIZE') && UPDRAFTPLUS_SKIP_FILE_OVER_SIZE && $fsize > UPDRAFTPLUS_SKIP_FILE_OVER_SIZE) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $updraftplus->log("File is larger than the user-configured (UPDRAFTPLUS_SKIP_FILE_OVER_SIZE) maximum (is: ".round($fsize/1024, 1)." KB); will skip: ".$add_as); - continue; - } elseif ($fsize > UPDRAFTPLUS_WARN_FILE_SIZE) { - $updraftplus->log(sprintf(__('A very large file was encountered: %s (size: %s Mb)', 'updraftplus'), $add_as, round($fsize/1048576, 1)), 'warning', 'vlargefile_'.md5($this->whichone.'#'.$add_as)); - } - - // Skips files that are already added - if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != $fsize) { - - @touch($zipfile);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $zip->addFile($file, $add_as); - $zipfiles_added_thisbatch++; - - if (method_exists($zip, 'setCompressionName') && $this->file_should_be_stored_without_compression($add_as)) { - if (false == ($set_compress = $zip->setCompressionName($add_as, ZipArchive::CM_STORE))) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $updraftplus->log("Zip: setCompressionName failed on: $add_as"); - } - } - - // N.B., Since makezip_addfiles() can get called more than once if there were errors detected, potentially $zipfiles_added_thisrun can exceed the total number of batched files (if they get processed twice). - $this->zipfiles_added_thisrun++; - $files_zipadded_since_open[] = array('file' => $file, 'addas' => $add_as); - $updraftplus->log_remove_warning('vlargefile_'.md5($this->whichone.'#'.$add_as)); - - $data_added_since_reopen += $fsize; - // $data_added_this_resumption += $fsize; - /* Conditions for forcing a write-out and re-open: - - more than $maxzipbatch bytes have been batched - - more than 2.0 seconds have passed since the last time we wrote - - that adding this batch of data is likely already enough to take us over the split limit (and if that happens, then do actually split - to prevent a scenario of progressively tinier writes as we approach but don't actually reach the limit) - - more than 500 files batched (should perhaps intelligently lower this as the zip file gets bigger - not yet needed) - */ - - // Add 10% margin. It only really matters when the OS has a file size limit, exceeding which causes failure (e.g. 2GB on 32-bit) - // Since we don't test before the file has been created (so that zip_last_ratio has meaningful data), we rely on max_zip_batch being less than zip_split_every - which should always be the case - $reaching_split_limit = ($this->zip_last_ratio > 0 && $original_size>0 && ($original_size + 1.1*$data_added_since_reopen*$this->zip_last_ratio) > $this->zip_split_every) ? true : false; - - if (!$force_allinone && ($zipfiles_added_thisbatch > UPDRAFTPLUS_MAXBATCHFILES || $reaching_split_limit || $data_added_since_reopen > $maxzipbatch || (time() - $this->zipfiles_lastwritetime) > 2)) { - - // We are coming towards a limit and about to close the zip, check if this is a more file backup and the manifest file has made it into this zip if not add it - if (apply_filters('updraftplus_include_manifest', false, $this->whichone, $this)) { - - $manifest = false; - - foreach ($files_zipadded_since_open as $info) { - if ('updraftplus-manifest.json' == $info['file']) $manifest = true; - } - - if (!$manifest) { - @touch($zipfile);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $path = array_search('updraftplus-manifest.json', $this->zipfiles_batched); - $zip->addFile($path, 'updraftplus-manifest.json'); - $zipfiles_added_thisbatch++; - - if (method_exists($zip, 'setCompressionName') && $this->file_should_be_stored_without_compression($this->zipfiles_batched[$path])) { - if (false == ($set_compress = $zip->setCompressionName($this->zipfiles_batched[$path], ZipArchive::CM_STORE))) { - $updraftplus->log("Zip: setCompressionName failed on: $this->zipfiles_batched[$path]"); - } - } - - // N.B., Since makezip_addfiles() can get called more than once if there were errors detected, potentially $zipfiles_added_thisrun can exceed the total number of batched files (if they get processed twice). - $this->zipfiles_added_thisrun++; - $files_zipadded_since_open[] = array('file' => $path, 'addas' => 'updraftplus-manifest.json'); - $data_added_since_reopen += filesize($path); - // $data_added_this_resumption += filesize($path); - } - } - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $something_useful_sizetest = false; - - if ($data_added_since_reopen > $maxzipbatch) { - $something_useful_sizetest = true; - $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over ".round($maxzipbatch/1048576, 1)." MB added on this batch (".round($data_added_since_reopen/1048576, 1)." MB, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024, 1).' KB)'); - } elseif ($zipfiles_added_thisbatch > UPDRAFTPLUS_MAXBATCHFILES) { - $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over ".UPDRAFTPLUS_MAXBATCHFILES." files added on this batch (".round($data_added_since_reopen/1048576, 1)." MB, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024, 1).' KB)'); - } elseif (!$reaching_split_limit) { - $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over 2.0 seconds have passed since the last write (".round($data_added_since_reopen/1048576, 1)." MB, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); re-opening (prior size: ".round($original_size/1024, 1).' KB)'); - } else { - $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): possibly approaching split limit (".round($data_added_since_reopen/1048576, 1)." MB, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); last ratio: ".round($this->zip_last_ratio, 4)."; re-opening (prior size: ".round($original_size/1024, 1).' KB)'); - } - - if (!$zip->close()) { - // Though we will continue processing the files we've got, the final error code will be false, to allow a second attempt on the failed ones. This also keeps us consistent with a negative result for $zip->close() further down. We don't just retry here, because we have seen cases (with BinZip) where upon failure, the existing zip had actually been deleted. So, to be safe we need to re-scan the existing zips. - $ret = false; - $this->record_zip_error($files_zipadded_since_open, $zip->last_error, $warn_on_failures); - } - - // if ($data_added_this_resumption > $max_data_added_any_resumption) { - // $max_data_added_any_resumption = $data_added_this_resumption; - // $updraftplus->jobdata_set('max_data_added_any_resumption', $max_data_added_any_resumption); - // } - - $zipfiles_added_thisbatch = 0; - - // This triggers a re-open, later - unset($zip); - $files_zipadded_since_open = array(); - // Call here, in case we've got so many big files that we don't complete the whole routine - if (filesize($zipfile) > $original_size) { - - // It is essential that this does not go above 1, even though in reality (and this can happen at the start, if just 1 file is added (e.g. due to >2.0s detection) the 'compressed' zip file may be *bigger* than the files stored in it. When that happens, if the ratio is big enough, it can then fire the "approaching split limit" detection (very) prematurely - $this->zip_last_ratio = ($data_added_since_reopen > 0) ? min((filesize($zipfile) - $original_size)/$data_added_since_reopen, 1) : 1; - - // We need a rolling update of this - $original_size = filesize($zipfile); - - // Move on to next zip? - if ($reaching_split_limit || filesize($zipfile) > $this->zip_split_every) { - $bump_index = true; - // Take the filesize now because later we wanted to know we did clearstatcache() - $bumped_at = round(filesize($zipfile)/1048576, 1); - } - - // Need to make sure that something_useful_happened() is always called - - // How long since the current run began? If it's taken long (and we're in danger of not making it at all), or if that is forseeable in future because of general slowness, then we should reduce the parameters. - if (!$something_useful_sizetest) { - UpdraftPlus_Job_Scheduler::something_useful_happened(); - } else { - - // Do this as early as possible - UpdraftPlus_Job_Scheduler::something_useful_happened(); - - $time_since_began = max(microtime(true)- $this->zipfiles_lastwritetime, 0.000001); - $normalised_time_since_began = $time_since_began*($maxzipbatch/$data_added_since_reopen); - - // Don't measure speed until after ZipArchive::close() - $rate = round($data_added_since_reopen/$time_since_began, 1); - - $updraftplus->log(sprintf("A useful amount of data was added after this amount of zip processing: %s s (normalised: %s s, rate: %s KB/s)", round($time_since_began, 1), round($normalised_time_since_began, 1), round($rate/1024, 1))); - - // We want to detect not only that we need to reduce the size of batches, but also the capability to increase them. This is particularly important because of ZipArchive()'s (understandable, given the tendency of PHP processes being terminated without notice) practice of first creating a temporary zip file via copying before acting on that zip file (so the information is atomic). Unfortunately, once the size of the zip file gets over 100MB, the copy operation beguns to be significant. By the time you've hit 500MB on many web hosts the copy is the majority of the time taken. So we want to do more in between these copies if possible. - - /* "Could have done more" - detect as: - - A batch operation would still leave a "good chunk" of time in a run - - "Good chunk" means that the time we took to add the batch is less than 50% of a run time - - We can do that on any run after the first (when at least one ceiling on the maximum time is known) - - But in the case where a max_execution_time is long (so that resumptions are never needed), and we're always on run 0, we will automatically increase chunk size if the batch took less than 6 seconds. - */ - - // At one stage we had a strategy of not allowing check-ins to have more than 20s between them. However, once the zip file got to a certain size, PHP's habit of copying the entire zip file first meant that it *always* went over 18s, and thence a drop in the max size was inevitable - which was bad, because with the copy time being something that only grew, the outcome was less data being copied every time - - // Gather the data. We try not to do this unless necessary (may be time-sensitive) - if ($updraftplus->current_resumption >= 1) { - $time_passed = $updraftplus->jobdata_get('run_times'); - if (!is_array($time_passed)) $time_passed = array(); - list($max_time, $timings_string, $run_times_known) = UpdraftPlus_Manipulation_Functions::max_time_passed($time_passed, $updraftplus->current_resumption-1, $this->first_run);// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - } else { - // $run_times_known = 0; - // $max_time = -1; - $run_times_known = 1; - $max_time = microtime(true)-$updraftplus->opened_log_time; - } - - if ($normalised_time_since_began < 6 || ($updraftplus->current_resumption >= 1 && $run_times_known >= 1 && $time_since_began < 0.6*$max_time) || (0 == $updraftplus->current_resumption && $max_time > 240)) { - - // How much can we increase it by? - if ($normalised_time_since_began < 6 || 0 == $updraftplus->current_resumption) { - if ($run_times_known > 0 && $max_time > 0) { - $new_maxzipbatch = min(floor(max($maxzipbatch*6/$normalised_time_since_began, $maxzipbatch*((0.6*$max_time)/$normalised_time_since_began))), $this->zip_batch_ceiling); - } else { - // Maximum of 200MB in a batch - $new_maxzipbatch = min(floor($maxzipbatch*6/$normalised_time_since_began), $this->zip_batch_ceiling); - } - } else { - // Use up to 60% of available time - $new_maxzipbatch = min(floor($maxzipbatch*((0.6*$max_time)/$normalised_time_since_began)), $this->zip_batch_ceiling); - } - - // Throttle increases - don't increase by more than 2x in one go - ??? - // $new_maxzipbatch = floor(min(2*$maxzipbatch, $new_maxzipbatch)); - // Also don't allow anything that is going to be more than 18 seconds - actually, that's harmful because of the basically fixed time taken to copy the file - // $new_maxzipbatch = floor(min(18*$rate ,$new_maxzipbatch)); - - // Don't go above the split amount (though we expect that to be higher anyway, unless sending via email) - $new_maxzipbatch = min($new_maxzipbatch, $this->zip_split_every); - - // Don't raise it above a level that failed on a previous run - $maxzipbatch_ceiling = $updraftplus->jobdata_get('maxzipbatch_ceiling'); - if (is_numeric($maxzipbatch_ceiling) && $maxzipbatch_ceiling > 20*1048576 && $new_maxzipbatch > $maxzipbatch_ceiling) { - $updraftplus->log("Was going to raise maxzipbytes to $new_maxzipbatch, but this is too high: a previous failure led to the ceiling being set at $maxzipbatch_ceiling, which we will use instead"); - $new_maxzipbatch = $maxzipbatch_ceiling; - } - - // Final sanity check - if ($new_maxzipbatch > 1048576) $updraftplus->jobdata_set('maxzipbatch', $new_maxzipbatch); - - if ($new_maxzipbatch <= 1048576) { - $updraftplus->log("Unexpected new_maxzipbatch value obtained (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)"); - } elseif ($new_maxzipbatch > $maxzipbatch) { - $updraftplus->log("Performance is good - will increase the amount of data we attempt to batch (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)"); - } elseif ($new_maxzipbatch < $maxzipbatch) { - // Ironically, we thought we were speedy... - $updraftplus->log("Adjust: Reducing maximum amount of batched data (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, new_max_bytes=$new_maxzipbatch, old_max_bytes=$maxzipbatch)"); - } else { - $updraftplus->log("Performance is good - but we will not increase the amount of data we batch, as we are already at the present limit (time=$time_since_began, normalised_time=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, max_bytes=$maxzipbatch)"); - } - - if ($new_maxzipbatch > 1048576) $maxzipbatch = $new_maxzipbatch; - } - - // Detect excessive slowness - // Don't do this until we're on at least resumption 7, as we want to allow some time for things to settle down and the maxiumum time to be accurately known (since reducing the batch size unnecessarily can itself cause extra slowness, due to PHP's usage of temporary zip files) - - // We use a percentage-based system as much as possible, to avoid the various criteria being in conflict with each other (i.e. a run being both 'slow' and 'fast' at the same time, which is increasingly likely as max_time gets smaller). - - if (!$updraftplus->something_useful_happened && $updraftplus->current_resumption >= 7) { - - UpdraftPlus_Job_Scheduler::something_useful_happened(); - - if ($run_times_known >= 5 && ($time_since_began > 0.8 * $max_time || $time_since_began + 7 > $max_time)) { - - $new_maxzipbatch = max(floor($maxzipbatch*0.8), 20971520); - if ($new_maxzipbatch < $maxzipbatch) { - $maxzipbatch = $new_maxzipbatch; - $updraftplus->jobdata_set("maxzipbatch", $new_maxzipbatch); - $updraftplus->log("We are within a small amount of the expected maximum amount of time available; the zip-writing thresholds will be reduced (time_passed=$time_since_began, normalised_time_passed=$normalised_time_since_began, max_time=$max_time, data points known=$run_times_known, old_max_bytes=$maxzipbatch, new_max_bytes=$new_maxzipbatch)"); - } else { - $updraftplus->log("We are within a small amount of the expected maximum amount of time available, but the zip-writing threshold is already at its lower limit (20MB), so will not be further reduced (max_time=$max_time, data points known=$run_times_known, max_bytes=$maxzipbatch)"); - } - } - - } else { - UpdraftPlus_Job_Scheduler::something_useful_happened(); - } - } - $data_added_since_reopen = 0; - } else { - // ZipArchive::close() can take a very long time, which we want to know about - UpdraftPlus_Job_Scheduler::record_still_alive(); - } - - clearstatcache(); - $this->zipfiles_lastwritetime = time(); - } - } elseif (0 == $this->zipfiles_added_thisrun) { - // Update lastwritetime, because otherwise the 2.0-second-activity detection can fire prematurely (e.g. if it takes >2.0 seconds to process the previously-written files, then the detector fires after 1 file. This then can have the knock-on effect of having something_useful_happened() called, but then a subsequent attempt to write out a lot of meaningful data fails, and the maximum batch is not then reduced. - // Testing shows that calling time() 1000 times takes negligible time - $this->zipfiles_lastwritetime = time(); - } - - $this->zipfiles_added++; - - // Don't call something_useful_happened() here - nothing necessarily happens until close() is called - if (0 == $this->zipfiles_added % 100) { - $skip_dblog = ($this->zipfiles_added_thisrun > 0 || 0 == $this->zipfiles_added % 1000) ? false : true; - $updraftplus->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (on-disk size: ".round(@filesize($zipfile)/1024, 1)." KB)", 'notice', false, $skip_dblog);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - if ($bump_index) { - $updraftplus->log(sprintf("Zip size is at/near split limit (%s MB / %s MB) - bumping index (from: %d)", $bumped_at, round($this->zip_split_every/1048576, 1), $this->index)); - $bump_index = false; - $this->bump_index(); - $zipfile = $this->zip_basename.($this->index+1).'.zip.tmp'; - } - - if (empty($zip)) { - $zip = new $this->use_zip_object; - - if (file_exists($zipfile)) { - $opencode = $zip->open($zipfile); - $original_size = filesize($zipfile); - clearstatcache(); - } else { - $create_code = defined('ZIPARCHIVE::CREATE') ? ZIPARCHIVE::CREATE : 1; - $opencode = $zip->open($zipfile, $create_code); - $original_size = 0; - } - - if (true !== $opencode) return new WP_Error('no_open', sprintf(__('Failed to open the zip file (%s) - %s', 'updraftplus'), $zipfile, $zip->last_error)); - } - - } - - // Reset array - $this->zipfiles_batched = array(); - $this->zipfiles_skipped_notaltered = array(); - - if (false == ($nret = $zip->close())) $this->record_zip_error($files_zipadded_since_open, $zip->last_error, $warn_on_failures); - - if (apply_filters('updraftplus_include_manifest', false, $this->whichone, $this)) { - if (!empty($this->manifest_path) && file_exists($this->manifest_path)) { - $updraftplus->log('Removing manifest file: '.basename($this->manifest_path).': '.(@unlink($this->manifest_path) ? 'OK' : 'failed'));// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - } - - $this->zipfiles_lastwritetime = time(); - // May not exist if the last thing we did was bump - if (file_exists($zipfile) && filesize($zipfile) > $original_size) UpdraftPlus_Job_Scheduler::something_useful_happened(); - - // Move on to next archive? - if (file_exists($zipfile) && filesize($zipfile) > $this->zip_split_every) { - $updraftplus->log(sprintf("Zip size has gone over split limit (%s, %s) - bumping index (%d)", round(filesize($zipfile)/1048576, 1), round($this->zip_split_every/1048576, 1), $this->index)); - $this->bump_index(); - } - - clearstatcache(); - - return (false == $ret) ? false : $nret; - } - - private function record_zip_error($files_zipadded_since_open, $msg, $warn = true) { - global $updraftplus; - - if (!empty($updraftplus->cpanel_quota_readable)) { - $hosting_bytes_free = $updraftplus->get_hosting_disk_quota_free(); - if (is_array($hosting_bytes_free)) { - $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1); - $quota_free_msg = sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %"); - $updraftplus->log($quota_free_msg); - if ($hosting_bytes_free[3] < 1048576*50) { - $quota_low = true; - $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1); - $updraftplus->log(sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'updraftplus'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb); - } - } - } - - // Always warn of this - if (strpos($msg, 'File Size Limit Exceeded') !== false && 'UpdraftPlus_BinZip' == $this->use_zip_object) { - $updraftplus->log(sprintf(__('The zip engine returned the message: %s.', 'updraftplus'), 'File Size Limit Exceeded'). __('Go here for more information.', 'updraftplus').' https://updraftplus.com/what-should-i-do-if-i-see-the-message-file-size-limit-exceeded/', 'warning', 'zipcloseerror-filesizelimit'); - } elseif ($warn) { - $warn_msg = __('A zip error occurred', 'updraftplus').' - '; - if (!empty($quota_low)) { - $warn_msg = sprintf(__('your web hosting account appears to be full; please see: %s', 'updraftplus'), 'https://updraftplus.com/faqs/how-much-free-disk-space-do-i-need-to-create-a-backup/'); - } else { - $warn_msg .= __('check your log for more details.', 'updraftplus'); - } - $updraftplus->log($warn_msg, 'warning', 'zipcloseerror-'.$this->whichone); - } - - $updraftplus->log("The attempt to close the zip file returned an error ($msg). List of files we were trying to add follows (check their permissions)."); - - foreach ($files_zipadded_since_open as $ffile) { - $updraftplus->log("File: ".$ffile['addas']." (exists: ".(int) @file_exists($ffile['file']).", is_readable: ".(int) @is_readable($ffile['file'])." size: ".@filesize($ffile['file']).')', 'notice', false, true);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - } - - private function bump_index() { - global $updraftplus; - $youwhat = $this->whichone; - - $timetaken = max(microtime(true)-$this->zip_microtime_start, 0.000001); - - $itext = (0 == $this->index) ? '' : ($this->index+1); - $full_path = $this->zip_basename.$itext.'.zip'; - - $checksums = $updraftplus->which_checksums(); - - $checksum_description = ''; - - foreach ($checksums as $checksum) { - - $cksum = hash_file($checksum, $full_path.'.tmp'); - $updraftplus->jobdata_set($checksum.'-'.$youwhat.$this->index, $cksum); - if ($checksum_description) $checksum_description .= ', '; - $checksum_description .= "$checksum: $cksum"; - - } - - $next_full_path = $this->zip_basename.($this->index+2).'.zip'; - // We touch the next zip before renaming the temporary file; this indicates that the backup for the entity is not *necessarily* finished - touch($next_full_path.'.tmp'); - - if (file_exists($full_path.'.tmp') && filesize($full_path.'.tmp') > 0) { - if (!rename($full_path.'.tmp', $full_path)) { - $updraftplus->log("Rename failed for $full_path.tmp"); - } else { - UpdraftPlus_Job_Scheduler::something_useful_happened(); - } - } - - $kbsize = filesize($full_path)/1024; - $rate = round($kbsize/$timetaken, 1); - $updraftplus->log("Created ".$this->whichone." zip (".$this->index.") - ".round($kbsize, 1)." KB in ".round($timetaken, 1)." s ($rate KB/s) (checksums: $checksum_description)"); - $this->zip_microtime_start = microtime(true); - - // No need to add $itext here - we can just delete any temporary files for this zip - UpdraftPlus_Filesystem_Functions::clean_temporary_files('_'.$updraftplus->file_nonce."-".$youwhat, 600); - - $this->index++; - $this->job_file_entities[$youwhat]['index'] = $this->index; - $updraftplus->jobdata_set('job_file_entities', $this->job_file_entities); - } - - /** - * Returns the member of the array with key (int)0, as a new array. This function is used as a callback for array_map(). - * - * @param Array $a - the array - * - * @return Array - with keys 'name' and 'type' - */ - private function cb_get_name_base_type($a) { - return array('name' => $a[0], 'type' => 'BASE TABLE'); - } - - /** - * Returns the members of the array with keys (int)0 and (int)1, as part of a new array. - * - * @param Array $a - the array - * - * @return Array - keys are 'name' and 'type' - */ - private function cb_get_name_type($a) { - return array('name' => $a[0], 'type' => $a[1]); - } - - /** - * Returns the member of the array with key (string)'name'. This function is used as a callback for array_map(). - * - * @param Array $a - the array - * - * @return Mixed - the value with key (string)'name' - */ - private function cb_get_name($a) { - return $a['name']; - } - - /** - * Exclude files from backup - * - * @param Boolean $filter initial boolean value of whether the given file is excluded or not - * @param String $file the full path of the filename to be checked - * @return Boolean true if the specified file will be excluded, false otherwise - */ - public function backup_exclude_file($filter, $file) { - foreach ($this->backup_excluded_patterns as $pattern) { - if (0 === stripos($file, $pattern['directory']) && preg_match($pattern['regex'], $file)) return true; - } - return $filter; - } -} - -class UpdraftPlus_WPDB_OtherDB extends wpdb { - /** - * This adjusted bail() does two things: 1) Never dies and 2) logs in the UD log - * - * @param string $message Error message - * @param string $error_code Error Code - * @return boolean - */ - public function bail($message, $error_code = '500') { - global $updraftplus; - if ('db_connect_fail' == $error_code) $message = 'Connection failed: check your access details, that the database server is up, and that the network connection is not firewalled.'; - $updraftplus->log("WPDB_OtherDB error: $message ($error_code)"); - // Now do the things that would have been done anyway - if (class_exists('WP_Error')) { - $this->error = new WP_Error($error_code, $message); - } else { - $this->error = $message; - } - return false; - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/bootstrap.php b/wordpress/wp-content/plugins/updraftplus/central/bootstrap.php deleted file mode 100644 index 3a2d5e45a6f1160bdbe69e139b4a8d0b5282f658..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/bootstrap.php +++ /dev/null @@ -1,634 +0,0 @@ - 'UpdraftCentral_Core_Commands', - 'updates' => 'UpdraftCentral_Updates_Commands', - 'users' => 'UpdraftCentral_Users_Commands', - 'comments' => 'UpdraftCentral_Comments_Commands', - 'analytics' => 'UpdraftCentral_Analytics_Commands', - 'plugin' => 'UpdraftCentral_Plugin_Commands', - 'theme' => 'UpdraftCentral_Theme_Commands', - 'posts' => 'UpdraftCentral_Posts_Commands', - 'media' => 'UpdraftCentral_Media_Commands', - 'pages' => 'UpdraftCentral_Pages_Commands' - )); - - // If nothing was sent, then there is no incoming message, so no need to set up a listener (or CORS request, etc.). This avoids a DB SELECT query on the option below in the case where it didn't get autoloaded, which is the case when there are no keys. - if (!empty($_SERVER['REQUEST_METHOD']) && ('GET' == $_SERVER['REQUEST_METHOD'] || 'POST' == $_SERVER['REQUEST_METHOD']) && (empty($_REQUEST['action']) || 'updraft_central' !== $_REQUEST['action']) && empty($_REQUEST['udcentral_action']) && empty($_REQUEST['udrpc_message'])) return; - - // Remote control keys - // These are different from the remote send keys, which are set up in the Migrator add-on - $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys'); - if (is_array($our_keys) && !empty($our_keys)) { - new UpdraftCentral_Listener($our_keys, $command_classes); - } - - } - - /** - * Receive a new public key in $_GET, and echo a response. Will die() if called. - */ - public function wp_ajax_updraftcentral_receivepublickey() { - - // The actual nonce check is done in the method below - if (empty($_GET['_wpnonce']) || empty($_GET['public_key']) || !isset($_GET['updraft_key_index'])) die; - - $result = $this->receive_public_key(); - if (!is_array($result) || empty($result['responsetype'])) die; - - echo 'UpdraftCentral

    '.__('UpdraftCentral Connection', 'updraftplus').'

    '.htmlspecialchars(network_site_url()).'

    '; - - if ('ok' == $result['responsetype']) { - _e('An UpdraftCentral connection has been made successfully.', 'updraftplus'); - } else { - echo ''.__('A new UpdraftCentral connection has not been made.', 'updraftplus').'
    '; - switch ($result['code']) { - case 'unknown_key': - _e('The key referred to was unknown.', 'updraftplus'); - break; - case 'not_logged_in': - echo __('You are not logged into this WordPress site in your web browser.', 'updraftplus').' '.__('You must visit this URL in the same browser and login session as you created the key in.', 'updraftplus'); - break; - case 'nonce_failure': - echo 'Security check. '; - _e('You must visit this link in the same browser and login session as you created the key in.', 'updraftplus'); - break; - case 'already_have': - _e('This connection appears to already have been made.', 'updraftplus'); - break; - default: - echo htmlspecialchars(print_r($result, true)); - break; - } - } - - echo '

    '.__('Close...', 'updraftplus').'

    '; - die; - } - - /** - * Checks _wpnonce, and if successful, saves the public key found in $_GET - * - * @return Array - with keys responsetype (can be 'error' or 'ok') and code, indicating whether the parse was successful - */ - private function receive_public_key() { - - if (!is_user_logged_in()) { - return array('responsetype' => 'error', 'code' => 'not_logged_in'); - } - - if (!wp_verify_nonce($_GET['_wpnonce'], 'updraftcentral_receivepublickey')) return array('responsetype' => 'error', 'code' => 'nonce_failure'); - - $updraft_key_index = $_GET['updraft_key_index']; - - $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys'); - if (!is_array($our_keys)) $our_keys = array(); - - if (!isset($our_keys[$updraft_key_index])) { - return array('responsetype' => 'error', 'code' => 'unknown_key'); - } - - if (!empty($our_keys[$updraft_key_index]['publickey_remote'])) { - return array('responsetype' => 'error', 'code' => 'already_have'); - } - - $our_keys[$updraft_key_index]['publickey_remote'] = base64_decode($_GET['public_key']); - UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys, true, 'no'); - - return array('responsetype' => 'ok', 'code' => 'ok'); - } - - /** - * Action parameters, from udrpc: $message, $level, $this->key_name_indicator, $this->debug, $this - * - * @param string $message The log message - * @param string $level Log level - * @param string $key_name_indicator This indicates the key name - */ - public function udrpc_log($message, $level, $key_name_indicator) { - $udrpc_log = get_site_option('updraftcentral_client_log'); - if (!is_array($udrpc_log)) $udrpc_log = array(); - - $new_item = array( - 'time' => time(), - 'level' => $level, - 'message' => $message, - 'key_name_indicator' => $key_name_indicator - ); - - if (!empty($_SERVER['REMOTE_ADDR'])) { - $new_item['remote_ip'] = $_SERVER['REMOTE_ADDR']; - } - if (!empty($_SERVER['HTTP_USER_AGENT'])) { - $new_item['http_user_agent'] = $_SERVER['HTTP_USER_AGENT']; - } - if (!empty($_SERVER['HTTP_X_SECONDARY_USER_AGENT'])) { - $new_item['http_secondary_user_agent'] = $_SERVER['HTTP_X_SECONDARY_USER_AGENT']; - } - - $udrpc_log[] = $new_item; - - if (count($udrpc_log) > 50) array_shift($udrpc_log); - - update_site_option('updraftcentral_client_log', $udrpc_log); - } - - /** - * Delete UpdraftCentral Key - * - * @param array $key_id key_id of UpdraftCentral - * @return array which contains deleted flag and key table. If error, Returns array which contains fatal_error flag and fatal_error_message - */ - public function delete_key($key_id) { - $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys'); - if (!is_array($our_keys)) $our_keys = array(); - if (isset($our_keys[$key_id])) { - unset($our_keys[$key_id]); - UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys); - } - return array('deleted' => 1, 'keys_table' => $this->get_keys_table()); - } - - /** - * Get UpdraftCentral Log - * - * @return array which contains log_contents. If error, Returns array which contains fatal_error flag and fatal_error_message - */ - public function get_log() { - - $udrpc_log = get_site_option('updraftcentral_client_log'); - if (!is_array($udrpc_log)) $udrpc_log = array(); - - $log_contents = ''; - - // Events are appended to the array in the order they happen. So, reversing the order gets them into most-recent-first order. - rsort($udrpc_log); - - if (empty($udrpc_log)) { - $log_contents = ''.__('(Nothing yet logged)', 'updraftplus').''; - } - - foreach ($udrpc_log as $m) { - - // Skip invalid data - if (!isset($m['time'])) continue; - - $time = gmdate('Y-m-d H:i:s O', $m['time']); - // $level is not used yet. We could put the message in different colours for different levels, if/when it becomes used. - - $key_name_indicator = empty($m['key_name_indicator']) ? '' : $m['key_name_indicator']; - - $log_contents .= ''."$time "; - - if (!empty($m['remote_ip'])) $log_contents .= '['.htmlspecialchars($m['remote_ip']).'] '; - - $log_contents .= "[".htmlspecialchars($key_name_indicator)."] ".htmlspecialchars($m['message'])."\n"; - } - - return array('log_contents' => $log_contents); - - } - - public function create_key($params) { - // Use the site URL - this means that if the site URL changes, communication ends; which is the case anyway - $user = wp_get_current_user(); - - $where_send = empty($params['where_send']) ? '' : (string) $params['where_send']; - - if ('__updraftpluscom' != $where_send) { - $purl = parse_url($where_send); - if (empty($purl) || !array($purl) || empty($purl['scheme']) || empty($purl['host'])) return array('error' => __('An invalid URL was entered', 'updraftplus')); - } - - // ENT_HTML5 exists only on PHP 5.4+ - // @codingStandardsIgnoreLine - $flags = defined('ENT_HTML5') ? ENT_QUOTES | ENT_HTML5 : ENT_QUOTES; - - $extra_info = array( - 'user_id' => $user->ID, - 'user_login' => $user->user_login, - 'ms_id' => get_current_blog_id(), - 'site_title' => html_entity_decode(get_bloginfo('name'), $flags), - ); - - if ($where_send) { - $extra_info['mothership'] = $where_send; - if (!empty($params['mothership_firewalled'])) { - $extra_info['mothership_firewalled'] = true; - } - } - - if (!empty($params['key_description'])) { - $extra_info['name'] = (string) $params['key_description']; - } - - $key_size = (empty($params['key_size']) || !is_numeric($params['key_size']) || $params['key_size'] < 512) ? 2048 : (int) $params['key_size']; - - $extra_info['key_size'] = $key_size; - - $created = $this->create_remote_control_key(false, $extra_info, $where_send); - - if (is_array($created)) { - $created['keys_table'] = $this->get_keys_table(); - - $created['keys_guide'] = '

    '. __('UpdraftCentral key created successfully') .'

    '; - - if ('__updraftpluscom' != $where_send) { - $created['keys_guide'] .= '

    '.sprintf(__('You now need to copy the key below and enter it at your %s.', 'updraftplus'), 'UpdraftCentral dashboard').'

    '.__('At your UpdraftCentral dashboard you should press the "Add Site" button then paste the key in the input box.', 'updraftplus').'

    '.sprintf(__('Detailed instructions for this can be found at %s', 'updraftplus'), 'UpdraftPlus.com').'

    '; - } else { - $created['keys_guide'] .= '

    '. sprintf(__('You can now control this site via your UpdraftCentral dashboard at %s.', 'updraftplus'), 'UpdraftPlus.com').'

    '; - } - } - - return $created; - } - - /** - * Given an index, return the indicator name - * - * @param String $index - * - * @return String - */ - private function indicator_name_from_index($index) { - return $index.'.central.updraftplus.com'; - } - - private function create_remote_control_key($index = false, $extra_info = array(), $post_it = false) { - - global $updraftplus; - - $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys'); - if (!is_array($our_keys)) $our_keys = array(); - - if (false === $index) { - if (empty($our_keys)) { - $index = 0; - } else { - $index = max(array_keys($our_keys))+1; - } - } - - $name_hash = $index; - - if (isset($our_keys[$name_hash])) { - unset($our_keys[$name_hash]); - } - - $indicator_name = $this->indicator_name_from_index($name_hash); - - $ud_rpc = $updraftplus->get_udrpc($indicator_name); - - if ('__updraftpluscom' == $post_it) { - $post_it = defined('UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION') ? UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION : 'https://updraftplus.com/?updraftcentral_action=receive_key'; - $post_it_description = 'UpdraftPlus.Com'; - } else { - $post_it_description = $post_it; - } - - // Normally, key generation takes seconds, even on a slow machine. However, some Windows machines appear to have a setup in which it takes a minute or more. And then, if you're on a double-localhost setup on slow hardware - even worse. It doesn't hurt to just raise the maximum execution time. - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $key_size = (empty($extra_info['key_size']) || !is_numeric($extra_info['key_size']) || $extra_info['key_size'] < 512) ? 2048 : (int) $extra_info['key_size']; - - if (is_object($ud_rpc) && $ud_rpc->generate_new_keypair($key_size)) { - - if ($post_it && empty($extra_info['mothership_firewalled'])) { - - $p_url = parse_url($post_it); - if (is_array($p_url) && !empty($p_url['user'])) { - $http_username = $p_url['user']; - $http_password = empty($p_url['pass']) ? '' : $p_url['pass']; - $post_it = $p_url['scheme'].'://'.$p_url['host']; - if (!empty($p_url['port'])) $post_it .= ':'.$p_url['port']; - $post_it .= $p_url['path']; - if (!empty($p_url['query'])) $post_it .= '?'.$p_url['query']; - } - - $post_options = array( - 'timeout' => 90, - 'body' => array( - 'updraftcentral_action' => 'receive_key', - 'key' => $ud_rpc->get_key_remote() - ) - ); - - if (!empty($http_username)) { - $post_options['headers'] = array( - 'Authorization' => 'Basic '.base64_encode($http_username.':'.$http_password) - ); - } - - // This option allows the key to be sent to the other side via a known-secure channel (e.g. http over SSL), rather than potentially allowing it to travel over an unencrypted channel (e.g. http back to the user's browser). As such, if specified, it is compulsory for it to work. - - $updraftplus->register_wp_http_option_hooks(); - - $sent_key = wp_remote_post( - $post_it, - $post_options - ); - - $updraftplus->register_wp_http_option_hooks(false); - - if (is_wp_error($sent_key) || empty($sent_key)) { - $err_msg = sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string) $post_it_description); - if (is_wp_error($sent_key)) $err_msg .= ' '.$sent_key->get_error_message().' ('.$sent_key->get_error_code().')'; - return array( - 'r' => $err_msg - ); - } - - $response = json_decode(wp_remote_retrieve_body($sent_key), true); - - if (!is_array($response) || !isset($response['key_id']) || !isset($response['key_public'])) { - return array( - 'r' => sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string) $post_it_description), - 'raw' => wp_remote_retrieve_body($sent_key) - ); - } - - $key_hash = hash('sha256', $ud_rpc->get_key_remote()); - - $local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => array('key_hash' => $key_hash, 'key_id' => $response['key_id']))); - - } elseif ($post_it) { - // Don't send; instead, include in the bundle info that the mothership is firewalled; this will then tell the mothership to try the reverse connection instead - - if (is_array($extra_info)) { - $extra_info['mothership_firewalled_callback_url'] = wp_nonce_url(admin_url('admin-ajax.php'), 'updraftcentral_receivepublickey'); - $extra_info['updraft_key_index'] = $index; - } - - - $local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => $ud_rpc->get_key_remote())); - } - - - if (isset($extra_info['name'])) { - $name = (string) $extra_info['name']; - unset($extra_info['name']); - } else { - $name = 'UpdraftCentral Remote Control'; - } - - $our_keys[$name_hash] = array( - 'name' => $name, - 'key' => $ud_rpc->get_key_local(), - 'extra_info' => $extra_info, - 'created' => time(), - ); - // Store the other side's public key - if (!empty($response) && is_array($response) && !empty($response['key_public'])) { - $our_keys[$name_hash]['publickey_remote'] = $response['key_public']; - } - UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys, true, 'no'); - - return array( - 'bundle' => $local_bundle, - 'r' => __('Key created successfully.', 'updraftplus').' '.__('You must copy and paste this key now - it cannot be shown again.', 'updraftplus'), - ); - } - - return false; - - } - - /** - * Get the HTML for the keys table - * - * @return String - */ - public function get_keys_table() { - - $ret = ''; - - $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys'); - if (!is_array($our_keys)) $our_keys = array(); - - if (empty($our_keys)) { - $ret .= ''.__('There are no UpdraftCentral dashboards that can currently control this site.', 'updraftplus').''; - } - - foreach ($our_keys as $i => $key) { - - if (empty($key['extra_info'])) continue; - - $user_id = $key['extra_info']['user_id']; - - if (!empty($key['extra_info']['mothership'])) { - - $mothership_url = $key['extra_info']['mothership']; - - if ('__updraftpluscom' == $mothership_url) { - $reconstructed_url = 'https://updraftplus.com'; - } else { - $purl = parse_url($mothership_url); - $path = empty($purl['path']) ? '' : $purl['path']; - - $reconstructed_url = $purl['scheme'].'://'.$purl['host'].(!empty($purl['port']) ? ':'.$purl['port'] : '').$path; - } - - } else { - $reconstructed_url = __('Unknown', 'updraftplus'); - } - - $name = $key['name']; - - $user = get_user_by('id', $user_id); - - $user_display = is_a($user, 'WP_User') ? $user->user_login.' ('.$user->user_email.')' : __('Unknown', 'updraftplus'); - - $ret .= ''.htmlspecialchars($name).' ('.htmlspecialchars($i).')'.__("Access this site as user:", 'updraftplus')." ".htmlspecialchars($user_display)."
    ".__('Public key was sent to:', 'updraftplus').' '.htmlspecialchars($reconstructed_url).'
    '; - - if (!empty($key['created'])) { - $ret .= __('Created:', 'updraftplus').' '.date_i18n(get_option('date_format').' '.get_option('time_format'), $key['created']).'.'; - if (!empty($key['extra_info']['key_size'])) { - $ret .= ' '.sprintf(__('Key size: %d bits', 'updraftplus'), $key['extra_info']['key_size']).'.'; - } - $ret .= '
    '; - } - - $ret .= ''.__('Delete...', 'updraftplus').''; - } - - - ob_start(); - ?> -
    - - - - - - - - - - - - - -
    -
    - -
    -

    - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    '.__('an account', 'updraftplus').''); ?>
    - -
    -
    -
    -
    UpdraftCentral'); ?>
    -
    - -
    -
    - -
    - ...
    -
    -				
    -
    - -
    -

    -

    - '.__('Read more about it here.', 'updraftplus').''; ?> -

    -
    - create_key_markup(); ?> - get_keys_table(); ?> - - get_log_markup(); ?> -
    -
    - options['context'] = $context; - } - // TODO: fix up request_filesystem_credentials(), or split it, to allow us to request a no-output version - // This will output a credentials form in event of failure, We don't want that, so just hide with a buffer - ob_start(); - $result = parent::request_filesystem_credentials($error, $context, $allow_relaxed_file_ownership); - ob_end_clean(); - return $result; - } - - /** - * Get update message - * - * @return array reti=urns an array of messages - */ - public function get_upgrade_messages() { - return $this->messages; - } - - /** - * Feedback - * - * @param string|array|WP_Error $data THis is the data to be used for the feedback - */ - protected function updraft_feedback($data) { - if (is_wp_error($data)) { - $string = $data->get_error_message(); - } elseif (is_array($data)) { - return; - } else { - $string = $data; - } - if (!empty($this->upgrader->strings[$string])) - $string = $this->upgrader->strings[$string]; - - if (false !== strpos($string, '%')) { - $args = func_get_args(); - $args = array_splice($args, 1); - if (!empty($args)) - $string = vsprintf($string, $args); - } - - $string = trim($string); - - // Only allow basic HTML in the messages, as it'll be used in emails/logs rather than direct browser output. - $string = wp_kses($string, array( - 'a' => array( - 'href' => true - ), - 'br' => true, - 'em' => true, - 'strong' => true, - )); - - if (empty($string)) - return; - - $this->messages[] = $string; - } - - public function header() { - ob_start(); - } - - public function footer() { - $output = ob_get_clean(); - if (!empty($output)) - $this->feedback($output); - } - - /** - * @access public - */ - public function bulk_header() {} - - public function bulk_footer() { - } -} - -global $updraftplus; -$wp_version = $updraftplus->get_wordpress_version(); - -if (version_compare($wp_version, '5.3', '>=')) { - if (!class_exists('Automatic_Upgrader_Skin')) require_once(UPDRAFTPLUS_DIR.'/central/classes/automatic-upgrader-skin-compatibility.php'); -} else { - class Automatic_Upgrader_Skin extends Automatic_Upgrader_Skin_Main { - - public function feedback($string) { - parent::updraft_feedback($string); - } - } -} \ No newline at end of file diff --git a/wordpress/wp-content/plugins/updraftplus/central/commands.php b/wordpress/wp-content/plugins/updraftplus/central/commands.php deleted file mode 100644 index 897eeb7319324f0afa0ff81671afdc06dca7c5e0..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/commands.php +++ /dev/null @@ -1,359 +0,0 @@ - (string - a code), 'data' => (mixed)); - * - * RPC commands are not allowed to begin with an underscore. So, any private methods can be prefixed with an underscore. - */ -abstract class UpdraftCentral_Commands { - - protected $rc; - - protected $ud; - - protected $installed_data; - - /** - * Class constructor - * - * @param string $rc - */ - public function __construct($rc) { - $this->rc = $rc; - global $updraftplus; - $this->ud = $updraftplus; - $this->installed_data = array(); - } - - /** - * Include a file or files from wp-admin/includes - */ - final protected function _admin_include() { - $files = func_get_args(); - foreach ($files as $file) { - include_once(ABSPATH.'/wp-admin/includes/'.$file); - } - } - - /** - * Include a file or files from wp-includes - */ - final protected function _frontend_include() { - $files = func_get_args(); - foreach ($files as $file) { - include_once(ABSPATH.WPINC.'/'.$file); - } - } - - /** - * Return a response in the expected format - * - * @param Mixed $data - * @param String $code - * - * @return Array - */ - final protected function _response($data = null, $code = 'rpcok') { - return array( - 'response' => $code, - 'data' => $data - ); - } - - /** - * Return an error in the expected format - * - * @param String $code - * @param Mixed $data - * - * @return Array - */ - final protected function _generic_error_response($code = 'central_unspecified', $data = null) { - return $this->_response( - array( - 'code' => $code, - 'data' => $data - ), - 'rpcerror' - ); - } - - /** - * Checks whether a backup and a security credentials is required for the given request - * - * @param array $dir The directory location to check - * @return array - */ - final protected function _get_backup_credentials_settings($dir) { - // Do we need to ask the user for filesystem credentials? when installing and/or deleting items in the given directory - $filesystem_method = get_filesystem_method(array(), $dir); - - ob_start(); - $filesystem_credentials_are_stored = request_filesystem_credentials(site_url(), $filesystem_method); - ob_end_clean(); - $request_filesystem_credentials = ('direct' != $filesystem_method && !$filesystem_credentials_are_stored); - - // Do we need to execute a backup process before installing/managing items - $automatic_backups = (class_exists('UpdraftPlus_Options') && class_exists('UpdraftPlus_Addon_Autobackup') && UpdraftPlus_Options::get_updraft_option('updraft_autobackup_default', true)) ? true : false; - - return array( - 'request_filesystem_credentials' => $request_filesystem_credentials, - 'automatic_backups' => $automatic_backups - ); - } - - /** - * Retrieves the information of the currently installed item (e.g. plugin or theme) through filter - * - * @param bool $response Indicates whether the installation was a success or failure - * @param array $args Extra argument for the hook - * @param array $data Contains paths used and other relevant information regarding the file - * @return array - */ - final public function get_install_data($response, $args, $data) { - - if ($response) { - switch ($args['type']) { - case 'plugin': - $plugin_data = get_plugins('/'.$data['destination_name']); - if (!empty($plugin_data)) { - $info = reset($plugin_data); - $key = key($plugin_data); - - $info['slug'] = $data['destination_name'].'/'.$key; - $this->installed_data = $info; - } - break; - case 'theme': - $theme = wp_get_theme($data['destination_name']); - if ($theme->exists()) { - // Minimalistic info here, if you need to add additional information - // you can add them here. For now, the "Name" and "slug" fields will suffice - // in the succeeding process. - $this->installed_data = array( - 'Name' => $theme->get('Name'), - 'slug' => $data['destination_name'], - 'template' => $theme->get_template() - ); - } - break; - default: - break; - } - } - - return $response; - } - - /** - * Installs and activates either a plugin or theme through zip file upload - * - * @param array $params Parameter array containing information pertaining the currently uploaded item - * @param string $type Indicates whether this current process is intended for a 'plugin' or a 'theme' item - * @return array - */ - final protected function process_chunk_upload($params, $type) { - - if (!in_array($type, array('plugin', 'theme'))) { - return $this->_generic_error_response('upload_type_not_supported'); - } - - $permission_error = false; - if ('plugin' === $type) { - if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) $permission_error = true; - } else { - if (!current_user_can('install_themes') || !current_user_can('switch_themes')) $permission_error = true; - } - - if ($permission_error) { - return $this->_generic_error_response($type.'_insufficient_permission'); - } - - // Pull any available and writable directory where we can store - // our data/file temporarily before running the installation process. - $upload_dir = untrailingslashit(get_temp_dir()); - if (!is_writable($upload_dir)) { - $upload_dir = WP_CONTENT_DIR.'/upgrade'; - - if (!is_dir($upload_dir)) { - $wp_dir = wp_upload_dir(); - if (!empty($wp_dir['basedir'])) $upload_dir = $wp_dir['basedir']; - } - } - - // If we haven't found any writable directory to temporarily store our file then - // we bail and send an error back to the caller. - if (!is_dir($upload_dir) || !is_writable($upload_dir)) { - return $this->_generic_error_response('upload_dir_not_available'); - } - - // Preloads the submitted credentials to the global $_POST variable - if (!empty($params) && isset($params['filesystem_credentials'])) { - parse_str($params['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. - $_POST[$key] = $value; - } - } - } - - // Save uploaded file - $filename = basename($params['filename']); - $is_chunked = false; - - if (isset($params['chunks']) && 1 < (int) $params['chunks']) { - $filename = basename($params['filename']).'.part'; - $is_chunked = true; - } - - if (empty($params['data'])) { - return $this->_generic_error_response('data_empty_or_invalid'); - } - - $result = file_put_contents($upload_dir.'/'.$filename, base64_decode($params['data']), FILE_APPEND | LOCK_EX); - - if (false === $result) { - return $this->_generic_error_response('unable_to_write_content'); - } - - // Set $install_now to true for single upload and for the last chunk of a multi-chunks upload process - $install_now = true; - - if ($is_chunked) { - if ($params['chunk'] == (int) $params['chunks'] - 1) { - // If this is the last chunk of the request, then we're going to restore the - // original filename of the file (without the '.part') since our upload is now complete. - $orig_filename = basename($filename, '.part'); - $success = rename($upload_dir.'/'.$filename, $upload_dir.'/'.$orig_filename); - - // If renaming the file was successful then restore the original name and override the $filename variable. - // Overriding the $filename variable makes it easy for us to use the same variable for both - // non-chunked and chunked zip file for the installation process. - if ($success) { - $filename = $orig_filename; - } else { - return $this->_generic_error_response('unable_to_rename_file'); - } - } else { - // Bypass installation for now since we're waiting for the last chunk to arrive - // to complete the uploading of the zip file. - $install_now = false; - } - } - - // Everything is already good (upload completed), thus, we proceed with the installation - if ($install_now) { - - // We have successfully uploaded the zip file in this location with its original filename intact. - $zip_filepath = $upload_dir.'/'.$filename; - - // Making sure that the file does actually exists, since we've just run through - // a renaming process above. - if (file_exists($zip_filepath)) { - add_filter('upgrader_post_install', array($this, 'get_install_data'), 10, 3); - - // WP < 3.7 - if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTPLUS_DIR.'/central/classes/class-automatic-upgrader-skin.php'); - - $skin = new Automatic_Upgrader_Skin(); - $upgrader = ('plugin' === $type) ? new Plugin_Upgrader($skin) : new Theme_Upgrader($skin); - - $install_result = $upgrader->install($zip_filepath); - remove_filter('upgrader_post_install', array($this, 'get_install_data'), 10, 3); - - // Remove zip file on success and on error (cleanup) - if ($install_result || is_null($install_result) || is_wp_error($install_result)) { - @unlink($zip_filepath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - if (false === $install_result || is_wp_error($install_result)) { - $message = __('Unable to connect to the filesystem', 'updraftplus'); - if (is_wp_error($install_result)) $message = $install_result->get_error_message(); - - return $this->_generic_error_response($type.'_install_failed', array('message' => $message)); - } else { - // Pull installed data - $data = $this->installed_data; - - // For WP 3.4 the intended filter hook isn't working or not available - // so we're going to pull the data manually. - if ($install_result && empty($data)) { - $result = $this->get_install_data($install_result, array('type' => $type), $skin->result); - if ($result) { - // Getting the installed data one more time after manually calling - // the "get_install_data" function. - $data = $this->installed_data; - } - } - - if (!empty($data)) { - // Activate item if set - $is_active = ('plugin' === $type) ? is_plugin_active($data['slug']) : ((wp_get_theme()->get('Name') === $data['Name']) ? true : false); - - if ((bool) $params['activate'] && !$is_active) { - if ('plugin' === $type) { - if (is_multisite()) { - $activate = activate_plugin($data['slug'], '', true); - } else { - $activate = activate_plugin($data['slug']); - } - } else { - // In order to make it compatible with older versions of switch_theme which takes two - // arguments we're going to pass two arguments instead of one. Latest versions have backward - // compatibility so it's safe to do it this way. - switch_theme($data['template'], $data['slug']); - $activate = (wp_get_theme()->get_stylesheet() === $data['slug']) ? true : false; - } - - if (false === $activate || is_wp_error($activate)) { - global $updraftplus; - $wp_version = $updraftplus->get_wordpress_version(); - - $message = is_wp_error($activate) ? array('message' => $activate->get_error_message()) : array('message' => sprintf(__('Unable to activate %s successfully. Make sure that this %s is compatible with your remote WordPress version. WordPress version currently installed in your remote website is %s.', 'updraftplus'), $type, $type, $wp_version)); - return $this->_generic_error_response('unable_to_activate_'.$type, $message); - } - } - - return $this->_response( - array( - 'installed' => true, - 'installed_data' => $data, - ) - ); - } - - if (is_wp_error($skin->result)) { - $code = $skin->result->get_error_code(); - $message = $skin->result->get_error_message(); - - $error_data = $skin->result->get_error_data($code); - if (!empty($error_data)) { - if (is_array($error_data)) $error_data = json_encode($error_data); - - $message .= ' '.$error_data; - } - - return $this->_generic_error_response($code, $message); - } else { - return $this->_response( - array( - 'installed' => false, - 'message' => sprintf(__('Unable to install %s. Make sure that the zip file is a valid %s file and a previous version of this %s does not exist. If you wish to overwrite an existing %s then you will have to manually delete it from the %s folder on the remote website and try uploading the file again.', 'updraftplus'), $type, $type, $type, $type, 'wp-content/'.$type.'s'), - ) - ); - } - } - } - } else { - // Returning response to a chunk requests while still processing and - // completing the file upload process. If we don't return a positive response - // for every chunk requests then the caller will assumed an error has occurred - // and will eventually stop the upload process. - return $this->_response(array('in_progress' => true)); - } - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/factory.php b/wordpress/wp-content/plugins/updraftplus/central/factory.php deleted file mode 100644 index c1dde91e2e983d367d11c79db74b3a2a69e71d79..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/factory.php +++ /dev/null @@ -1,40 +0,0 @@ - 'UpdraftPlus_Host', - // 'wp-optimize' => 'WPOptimize_Host' - ); - - if (!isset($mapped_classes[$plugin])) return null; - - $host_class = $mapped_classes[$plugin]; - if (!class_exists($host_class)) { - $central_folder = defined('UPDRAFTCENTRAL_CLIENT_DIR') ? UPDRAFTCENTRAL_CLIENT_DIR : dirname(__FILE__); - if (file_exists($central_folder.'/'.$plugin.'.php')) { - include_once($central_folder.'/'.$plugin.'.php'); - } else { - return null; - } - } - - return $host_class::instance();// phpcs:ignore PHPCompatibility.Syntax.NewDynamicAccessToStatic.Found - } -} - -global $updraftcentral_host_plugin; -$updraftcentral_host_plugin = UpdraftCentral_Factory::create_host(defined('UPDRAFTCENTRAL_HOST_PLUGIN') ? UPDRAFTCENTRAL_HOST_PLUGIN : 'updraftplus'); diff --git a/wordpress/wp-content/plugins/updraftplus/central/interface.php b/wordpress/wp-content/plugins/updraftplus/central/interface.php deleted file mode 100644 index 66084951f2311f4f0de9bb819ac61b2f462c1621..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/interface.php +++ /dev/null @@ -1,42 +0,0 @@ -ud = $updraftplus; - // It seems impossible for this condition to result in a return; but it seems Plesk can do something odd within the control panel that causes a problem - see HS#6276 - if (!is_a($this->ud, 'UpdraftPlus')) return; - - $this->command_classes = $command_classes; - - foreach ($keys as $name_hash => $key) { - // publickey_remote isn't necessarily set yet, depending on the key exchange method - if (!is_array($key) || empty($key['extra_info']) || empty($key['publickey_remote'])) continue; - $indicator = $name_hash.'.central.updraftplus.com'; - $ud_rpc = $this->ud->get_udrpc($indicator); - $this->udrpc_version = $ud_rpc->version; - - // Only turn this on if you are comfortable with potentially anything appearing in your PHP error log - if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) $ud_rpc->set_debug(true); - $this->receivers[$indicator] = $ud_rpc; - $this->extra_info[$indicator] = isset($key['extra_info']) ? $key['extra_info'] : null; - $ud_rpc->set_key_local($key['key']); - $ud_rpc->set_key_remote($key['publickey_remote']); - // Create listener (which causes WP actions to be fired when messages are received) - $ud_rpc->activate_replay_protection(); - if (!empty($key['extra_info']) && isset($key['extra_info']['mothership'])) { - $mothership = $key['extra_info']['mothership']; - $url = ''; - if ('__updraftpluscom' == $mothership) { - $url = 'https://updraftplus.com'; - } elseif (false != ($parsed = parse_url($key['extra_info']['mothership'])) && is_array($parsed)) { - $url = $parsed['scheme'].'://'.$parsed['host']; - } - if (!empty($url)) $ud_rpc->set_allow_cors_from(array($url)); - } - $ud_rpc->create_listener(); - } - - // If we ever need to expand beyond a single GET action, this can/should be generalised and put into the commands class - if (!empty($_GET['udcentral_action']) && 'login' == $_GET['udcentral_action']) { - // auth_redirect() does not return, according to the documentation; but the code shows that it can - // auth_redirect(); - - if (!empty($_GET['login_id']) && is_numeric($_GET['login_id']) && !empty($_GET['login_key'])) { - $login_user = get_user_by('id', $_GET['login_id']); - - // THis is included so we can get $wp_version - include_once(ABSPATH.WPINC.'/version.php'); - - if (is_a($login_user, 'WP_User') || (version_compare($wp_version, '3.5', '<') && !empty($login_user->ID))) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable - // Allow site implementers to disable this functionality - $allow_autologin = apply_filters('updraftcentral_allow_autologin', true, $login_user); - if ($allow_autologin) { - $login_key = get_user_meta($login_user->ID, 'updraftcentral_login_key', true); - if (is_array($login_key) && !empty($login_key['created']) && $login_key['created'] > time() - 60 && !empty($login_key['key']) && $login_key['key'] == $_GET['login_key']) { - $autologin = empty($login_key['redirect_url']) ? network_admin_url() : $login_key['redirect_url']; - } - } - } - } - if (!empty($autologin)) { - // Allow use once only - delete_user_meta($login_user->ID, 'updraftcentral_login_key'); - $this->autologin_user($login_user, $autologin); - } - } - - add_filter('udrpc_action', array($this, 'udrpc_action'), 10, 5); - add_filter('updraftcentral_get_command_info', array($this, 'updraftcentral_get_command_info'), 10, 2); - - } - - /** - * Retrieves command class information and includes class file if class - * is currently not available. - * - * @param mixed $response The default response to return if the submitted command does not exists - * @param string $command The command to parse and check - * @return array Contains the following command information "command_php_class", "class_prefix" and "command" - */ - public function updraftcentral_get_command_info($response, $command) { - if (!preg_match('/^([a-z0-9]+)\.(.*)$/', $command, $matches)) return $response; - $class_prefix = $matches[1]; - $command = $matches[2]; - - // We only handle some commands - the others, we let something else deal with - if (!isset($this->command_classes[$class_prefix])) return $response; - - $command_php_class = $this->command_classes[$class_prefix]; - $command_base_class_at = apply_filters('updraftcentral_command_base_class_at', UPDRAFTCENTRAL_CLIENT_DIR.'/commands.php'); - - if (!class_exists('UpdraftCentral_Commands')) include_once($command_base_class_at); - - // Second parameter has been passed since - do_action('updraftcentral_command_class_wanted', $command_php_class); - - if (!class_exists($command_php_class)) { - if (file_exists(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php')) { - include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php'); - } - } - - return array( - 'command_php_class' => $command_php_class, - 'class_prefix' => $class_prefix, - 'command' => $command - ); - } - - /** - * Do verification before calling this method - * - * @param WP_User|Object $user user object for autologin - * @param boolean $redirect_url Redirect URL - */ - private function autologin_user($user, $redirect_url = false) { - if (!is_user_logged_in()) { - // $user = get_user_by('id', $user_id); - // Don't check that it's a WP_User - that's WP 3.5+ only - if (!is_object($user) || empty($user->ID)) return; - wp_set_current_user($user->ID, $user->user_login); - wp_set_auth_cookie($user->ID); - do_action('wp_login', $user->user_login, $user); - } - if ($redirect_url) { - wp_safe_redirect($redirect_url); - exit; - } - } - - /** - * WP filter udrpc_action - * - * @param Array $response - the unfiltered response that will be returned - * @param String $command - the command being called - * @param Array $data - the parameters to the command - * @param String $key_name_indicator - the UC key that is in use - * @param Object $ud_rpc - the UDRP object - * - * @return Array - filtered response - */ - public function udrpc_action($response, $command, $data, $key_name_indicator, $ud_rpc) { - - try { - - if (empty($this->receivers[$key_name_indicator])) return $response; - - // This can be used to detect an UpdraftCentral context - if (!defined('UPDRAFTCENTRAL_COMMAND')) define('UPDRAFTCENTRAL_COMMAND', $command); - - $this->initialise_listener_error_handling(); - - $command_info = apply_filters('updraftcentral_get_command_info', false, $command); - if (!$command_info) return $response; - - $class_prefix = $command_info['class_prefix']; - $command = $command_info['command']; - $command_php_class = $command_info['command_php_class']; - - if (empty($this->commands[$class_prefix])) { - if (class_exists($command_php_class)) { - $this->commands[$class_prefix] = new $command_php_class($this); - } - } - - $command_class = isset($this->commands[$class_prefix]) ? $this->commands[$class_prefix] : new stdClass; - - if ('_' == substr($command, 0, 1) || !is_a($command_class, $command_php_class) || (!method_exists($command_class, $command) && !method_exists($command_class, '__call'))) { - if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) error_log("Unknown RPC command received: ".$command); - return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => array('prefix' => $class_prefix, 'command' => $command, 'class' => $command_php_class)))); - } - - $extra_info = isset($this->extra_info[$key_name_indicator]) ? $this->extra_info[$key_name_indicator] : null; - - // Make it so that current_user_can() checks can apply + work - if (!empty($extra_info['user_id'])) wp_set_current_user($extra_info['user_id']); - - $this->current_udrpc = $ud_rpc; - - do_action('updraftcentral_listener_pre_udrpc_action', $command, $command_class, $data, $extra_info); - - // Allow the command class to perform any boiler-plate actions. - if (is_callable(array($command_class, '_pre_action'))) call_user_func(array($command_class, '_pre_action'), $command, $data, $extra_info); - - // Despatch - $msg = apply_filters('updraftcentral_listener_udrpc_action', call_user_func(array($command_class, $command), $data, $extra_info), $command_class, $class_prefix, $command, $data, $extra_info); - - if (is_callable(array($command_class, '_post_action'))) call_user_func(array($command_class, '_post_action'), $command, $data, $extra_info); - - do_action('updraftcentral_listener_post_udrpc_action', $command, $command_class, $data, $extra_info); - - return $this->return_rpc_message($msg); - } catch (Exception $e) { - $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during UpdraftCentral command execution. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - - return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message)))); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during UpdraftCentral command execution. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - - return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'rpc_fatal_error', 'data' => array('command' => $command, 'message' => $log_message)))); - } - } - - public function get_current_udrpc() { - return $this->current_udrpc; - } - - private function initialise_listener_error_handling() { - $this->ud->error_reporting_stop_when_logged = true; - set_error_handler(array($this->ud, 'php_error'), E_ALL & ~E_STRICT); - $this->php_events = array(); - @ob_start();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here? - add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4); - if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) return; - } - - public function updraftplus_logline($line, $nonce, $level, $uniq_id) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found - if ('notice' === $level && 'php_event' === $uniq_id) { - $this->php_events[] = $line; - } - return $line; - } - - public function return_rpc_message($msg) { - if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) { - $this->ud->log('Unexpected response code in remote communications: '.serialize($msg)); - } - - $caught_output = @ob_get_contents();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here? - @ob_end_clean();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Might be a bigger picture that I am missing but do we need to silence errors here? - // If turning output-catching off, turn this on instead: - // $caught_output = ''; @ob_end_flush(); - - // If there's higher-level output buffering going on, then get rid of that - if (ob_get_level()) ob_end_clean(); - - if ($caught_output) { - if (!isset($msg['data'])) $msg['data'] = null; - $msg['data'] = array('caught_output' => $caught_output, 'previous_data' => $msg['data']); - $already_rearranged_data = true; - } - - if (!empty($this->php_events)) { - if (!isset($msg['data'])) $msg['data'] = null; - if (!empty($already_rearranged_data)) { - $msg['data']['php_events'] = array(); - } else { - $msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']); - } - foreach ($this->php_events as $logline) { - $msg['data']['php_events'][] = $logline; - } - } - restore_error_handler(); - - return $msg; - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/analytics.php b/wordpress/wp-content/plugins/updraftplus/central/modules/analytics.php deleted file mode 100644 index fe18d3ecaeb12a95988f94721a94a06ea304ae0e..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/analytics.php +++ /dev/null @@ -1,440 +0,0 @@ -auth_endpoint = defined('UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics'; - $this->client_id = defined('UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID') ? UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID : '306245874349-6s896c3tjpra26ns3dpplhqcl6rv6qlb.apps.googleusercontent.com'; - - // Set transient expiration - default for 24 hours - $this->expiration = 86400; - } - - /** - * Checks whether Google Analytics (GA) is installed or setup - * - * N.B. This check assumes GA is installed either using "wp_head" or "wp_footer" (e.g. attached - * to the or somewhere before ). It does not recursively check all the pages - * of the website to find if GA is installed on each or one of those pages, but only on the main/root page. - * - * @return array $result An array containing "ga_installed" property which returns "true" if GA (Google Analytics) is installed, "false" otherwise. - */ - public function ga_checker() { - - try { - - // Retrieves the tracking code/id if available - $tracking_id = $this->get_tracking_id(); - $installed = true; - - // If tracking code/id is currently not available then we - // parse the needed information from the buffered content through - // the "wp_head" and "wp_footer" hooks. - if (false === $tracking_id) { - $info = $this->extract_tracking_id(); - - $installed = $info['installed']; - $tracking_id = $info['tracking_id']; - } - - // Get access token to be use to generate the report. - $access_token = $this->_get_token(); - - if (empty($access_token)) { - // If we don't get a valid access token then that would mean - // the access has been revoked by the user or UpdraftCentral was not authorized yet - // to access the user's analytics data, thus, we're clearing - // any previously stored user access so we're doing some housekeeping here. - $this->clear_user_access(); - } - - // Wrap and combined information for the requesting - // client's consumption - $result = array( - 'ga_installed' => $installed, - 'tracking_id' => $tracking_id, - 'client_id' => $this->client_id, - 'redirect_uri' => $this->auth_endpoint, - 'scope' => $this->scope, - 'access_token' => $access_token, - 'endpoint' => $this->endpoint - ); - - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } - - /** - * Extracts Google Tracking ID from contents rendered through the "wp_head" and "wp_footer" action hooks - * - * @internal - * @return array $result An array containing the result of the extraction. - */ - private function extract_tracking_id() { - - // Define result array - $result = array(); - - // Retrieve header content - ob_start(); - do_action('wp_head'); - $header_content = ob_get_clean(); - - // Extract analytics information if available. - $output = $this->parse_content($header_content); - $result['installed'] = $output['installed']; - $result['tracking_id'] = $output['tracking_id']; - - // If it was not found, then now try the footer - if (empty($result['tracking_id'])) { - // Retrieve footer content - ob_start(); - do_action('wp_footer'); - $footer_content = ob_get_clean(); - $output = $this->parse_content($footer_content); - $result['installed'] = $output['installed']; - $result['tracking_id'] = $output['tracking_id']; - } - - if (!empty($result['tracking_id'])) { - set_transient($this->tracking_id_key, $result['tracking_id'], $this->expiration); - } - - return $result; - } - - /** - * Gets access token - * - * Validates whether the system currently have a valid token to use when connecting to Google Analytics API. - * If not, then it will send a token request based on the authorization code we stored during the - * authorization phase. Otherwise, it will return an empty token. - * - * @return array $result An array containing the Google Analytics API access token. - */ - public function get_access_token() { - - try { - - // Loads or request a valid token to use - $access_token = $this->_get_token(); - - if (!empty($access_token)) { - $result = array('access_token' => $access_token); - } else { - $result = array('error' => true, 'message' => 'ga_token_retrieval_failed', 'values' => array()); - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } - - /** - * Clears any previously stored user access - * - * @return bool - */ - public function clear_user_access() { - return delete_option($this->access_key); - } - - /** - * Saves user is and access token received from the auth server - * - * @param array $query Parameter array containing the user id and access token from the auth server. - * @return array $result An array containing a "success" or "failure" message as a result of the current process. - */ - public function save_user_access($query) { - - try { - - $token = get_option($this->access_key, false); - $result = array(); - - if (false === $token) { - $token = array( - 'user_id' => base64_decode(urldecode($query['user_id'])), - 'access_token' => base64_decode(urldecode($query['access_token'])) - ); - - if (false !== update_option($this->access_key, $token)) { - $result = array('error' => false, 'message' => 'ga_access_saved', 'values' => array()); - } else { - $result = array('error' => true, 'message' => 'ga_access_saving_failed', 'values' => array($query['access_token'])); - } - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } - - /** - * Saves the tracking code/id manually (user input) - * - * @param array $query Parameter array containing the tracking code/id to save. - * @return array $result An array containing the result of the process. - */ - public function save_tracking_id($query) { - try { - $tracking_id = $query['tracking_id']; - $saved = false; - - if (!empty($tracking_id)) { - $saved = set_transient($this->tracking_id_key, $tracking_id, $this->expiration); - } - - $result = array('saved' => $saved); - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } - - /** - * Retrieves any available access token either previously saved info or - * from a new request from the Google Server. - * - * @internal - * @return string $authorization_code - */ - private function _get_token() { - - // Retrieves the tracking code/id if available - $tracking_id = $this->get_tracking_id(); - $access_token = ''; - - $token = get_option($this->access_key, false); - if (false !== $token) { - $access_token = isset($token['access_token']) ? $token['access_token'] : ''; - $user_id = isset($token['user_id']) ? $token['user_id'] : ''; - - if ((!empty($access_token) && !$this->_token_valid($access_token)) || (!empty($user_id) && empty($access_token) && !empty($tracking_id))) { - if (!empty($user_id)) { - $args = array( - 'headers' => apply_filters('updraftplus_auth_headers', array()) - ); - - $response = wp_remote_get($this->auth_endpoint.'?user_id='.$user_id.'&code=ud_googleanalytics_code', $args); - if (is_wp_error($response)) { - throw new Exception($response->get_error_message()); - } else { - if (is_array($response)) { - - $body = json_decode($response['body'], true); - $token_response = array(); - - if (is_array($body) && !isset($body['error'])) { - $token_response = json_decode(base64_decode($body[0]), true); - } - - if (is_array($token_response) && isset($token_response['access_token'])) { - $access_token = $token_response['access_token']; - } else { - // If we don't get any valid response then that would mean that the - // permission was already revoked. Thus, we need to re-authorize the - // user before using the analytics feature once again. - $access_token = ''; - } - - $token['access_token'] = $access_token; - update_option($this->access_key, $token); - } - } - } - } - } - - return $access_token; - } - - /** - * Verifies whether the access token is still valid for use - * - * @internal - * @param string $token The access token to be check and validated - * @return bool - * @throws Exception If an error has occurred while connecting to the Google Server. - */ - private function _token_valid($token) { - - $response = wp_remote_get($this->token_info_endpoint.'?access_token='.$token); - if (is_wp_error($response)) { - throw new Exception($response->get_error_message()); - } else { - if (is_array($response)) { - $response = json_decode($response['body'], true); - if (!empty($response)) { - if (!isset($response['error']) && !isset($response['error_description'])) { - return true; - } - } - } - } - - return false; - } - - /** - * Parses and extracts the google analytics information (NEEDED) - * - * @internal - * @param string $content The content to parse - * @return array An array containing the status of the process along with the tracking code/id - */ - private function parse_content($content) { - - $installed = false; - $gtm_installed = false; - $tracking_id = ''; - $script_file_found = false; - $tracking_id_found = false; - - // Pull google analytics script file(s) - preg_match_all('/]*>([\s\S]*?)<\/script>/i', $content, $scripts); - for ($i=0; $i < count($scripts[0]); $i++) { - // Check for Google Analytics file - if (stristr($scripts[0][$i], 'ga.js') || stristr($scripts[0][$i], 'analytics.js')) { - $script_file_found = true; - } - - // Check for Google Tag Manager file - // N.B. We are not checking for GTM but this check will be useful when - // showing the notice to the user if we haven't found Google Analytics - // directly being installed on the page. - if (stristr($scripts[0][$i], 'gtm.js')) { - $gtm_installed = true; - } - } - - // Pull tracking code - preg_match_all('/UA-[0-9]{5,}-[0-9]{1,}/i', $content, $codes); - if (count($codes) > 0) { - if (!empty($codes[0])) { - $tracking_id_found = true; - $tracking_id = $codes[0][0]; - } - } - - // If we found both the script and the tracking code then it is safe - // to say that Google Analytics (GA) is installed. Thus, we're returning - // "true" as a response. - if ($script_file_found && $tracking_id_found) { - $installed = true; - } - - // Return result of process. - return array( - 'installed' => $installed, - 'gtm_installed' => $gtm_installed, - 'tracking_id' => $tracking_id - ); - } - - /** - * Retrieves the "analytics_tracking_id" transient - * - * @internal - * @return mixed Returns the value of the saved transient. Returns "false" if the transient does not exist. - */ - private function get_tracking_id() { - return get_transient($this->tracking_id_key); - } - - - /** - * Returns the current tracking id - * - * @return array $result An array containing the Google Tracking ID. - */ - public function get_current_tracking_id() { - try { - - // Get current site transient stored for this key - $tracking_id = get_transient($this->tracking_id_key); - - // Checks whether we have a valid token - $access_token = $this->_get_token(); - if (empty($access_token)) { - $tracking_id = ''; - } - - if (false === $tracking_id) { - $result = $this->extract_tracking_id(); - } else { - $result = array( - 'installed' => true, - 'tracking_id' => $tracking_id - ); - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } - - /** - * Clears user access from database - * - * @return array $result An array containing the "Remove" confirmation whether the action succeeded or not. - */ - public function remove_user_access() { - try { - - // Clear user access - $is_cleared = $this->clear_user_access(); - - if (false !== $is_cleared) { - $result = array('removed' => true); - } else { - $result = array('error' => true, 'message' => 'user_access_remove_failed', 'values' => array()); - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage())); - } - - return $this->_response($result); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/comments.php b/wordpress/wp-content/plugins/updraftplus/central/modules/comments.php deleted file mode 100644 index 77ca7aabf6227fd37d02810a0be45c2a92fbfcac..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/comments.php +++ /dev/null @@ -1,834 +0,0 @@ - 'ID', - 'order' => 'DESC', - 'type' => $query['type'], - 'status' => $query['status'], - 'search' => esc_attr($query['search']), - ); - - $query = new WP_Comment_Query; - $found_comments = $query->query($args); - - $comments = array(); - foreach ($found_comments as $comment) { - - // We're returning a collection of comment in an array, - // in sync with the originator of the request on the ui side - // so, we're pulling it one by one into the array before - // returning it. - - if (!in_array($comment, $comments)) { - array_push($comments, $comment); - } - } - - return $comments; - } - - /** - * The _calculate_pages function generates and builds the pagination links - * based on the current search parameters/filters. Please see _search_comments - * for the breakdown of these parameters. - * - * @param array $query Query to generate pagination links - * @return array - */ - private function _calculate_pages($query) { - $per_page_options = array(10, 20, 30, 40, 50); - - if (!empty($query)) { - if (!empty($query['search'])) { - return array( - 'page_count' => 1, - 'page_no' => 1 - ); - } - - $pages = array(); - $page_query = new WP_Comment_Query; - - // Here, we're pulling the comments based on the - // two parameters namely type and status. - // - // The number of results/comments found will then - // be use to compute for the number of pages to be - // displayed as navigation links when browsing all - // comments from the frontend. - - $comments = $page_query->query(array( - 'type' => $query['type'], - 'status' => $query['status'] - )); - - $total_comments = count($comments); - $page_count = ceil($total_comments / $query['per_page']); - - if ($page_count > 1) { - for ($i = 0; $i < $page_count; $i++) { - if ($i + 1 == $query['page_no']) { - $paginator_item = array( - 'value' => $i+1, - 'setting' => 'disabled' - ); - } else { - $paginator_item = array( - 'value' => $i+1 - ); - } - array_push($pages, $paginator_item); - } - - if ($query['page_no'] >= $page_count) { - $page_next = array( - 'value' => $page_count, - 'setting' => 'disabled' - ); - } else { - $page_next = array( - 'value' => $query['page_no'] + 1 - ); - } - - if (1 === $query['page_no']) { - $page_prev = array( - 'value' => 1, - 'setting' => 'disabled' - ); - } else { - $page_prev = array( - 'value' => $query['page_no'] - 1 - ); - } - - return array( - 'page_no' => $query['page_no'], - 'per_page' => $query['per_page'], - 'page_count' => $page_count, - 'pages' => $pages, - 'page_next' => $page_next, - 'page_prev' => $page_prev, - 'total_results' => $total_comments, - 'per_page_options' => $per_page_options - ); - - } else { - return array( - 'page_no' => $query['page_no'], - 'per_page' => $query['per_page'], - 'page_count' => $page_count, - 'total_results' => $total_comments, - 'per_page_options' => $per_page_options - ); - } - } else { - return array( - 'per_page_options' => $per_page_options - ); - } - } - - /** - * The get_blog_sites function pulls blog sites available for the current WP instance. - * If Multisite is enabled on the server, then sites under the network will be pulled, otherwise, it will return an empty array. - * - * @return array - */ - private function get_blog_sites() { - - if (!is_multisite()) return array(); - - // Initialize array container - $sites = $network_sites = array(); - - // Check to see if latest get_sites (available on WP version >= 4.6) function is - // available to pull any available sites from the current WP instance. If not, then - // we're going to use the fallback function wp_get_sites (for older version). - - if (function_exists('get_sites') && class_exists('WP_Site_Query')) { - $network_sites = get_sites(); - } else { - if (function_exists('wp_get_sites')) { - $network_sites = wp_get_sites(); - } - } - - // We only process if sites array is not empty, otherwise, bypass - // the next block. - - if (!empty($network_sites)) { - foreach ($network_sites as $site) { - - // Here we're checking if the site type is an array, because - // we're pulling the blog_id property based on the type of - // site returned. - // get_sites returns an array of object, whereas the wp_get_sites - // function returns an array of array. - - $blog_id = (is_array($site)) ? $site['blog_id'] : $site->blog_id; - - - // We're saving the blog_id and blog name as an associative item - // into the sites array, that will be used as "Sites" option in - // the frontend. - - $sites[$blog_id] = get_blog_details($blog_id)->blogname; - } - } - - return $sites; - } - - /** - * The get_wp_option function pulls current blog options - * from the database using either following functions: - * - get_blog_option (for multisite) - * - get_option (for ordinary blog) - * - * @param array $blog_id This is the specific blog ID - * @param array $setting specifies settings - * @return array - */ - private function _get_wp_option($blog_id, $setting) { - return is_multisite() ? get_blog_option($blog_id, $setting) : get_option($setting); - } - - /** - * The get_comments function pull all the comments from the database - * based on the current search parameters/filters. Please see _search_comments - * for the breakdown of these parameters. - * - * @param array $query Specific query to pull comments - * @return array - */ - public function get_comments($query) { - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($query['blog_id'])) $blog_id = $query['blog_id']; - - - // Here, we're switching to the actual blog that we need - // to pull comments from. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - if (!empty($query['search'])) { - // If a search keyword is present, then we'll call the _search_comments - // function to process the query. - - $comments = $this->_search_comments($query); - } else { - // Set default parameter values if the designated - // parameters are empty. - - if (empty($query['per_page'])) { - $query['per_page'] = 10; - } - if (empty($query['page_no'])) { - $query['page_no'] = 1; - } - if (empty($query['type'])) { - $query['type'] = ''; - } - if (empty($query['status'])) { - $query['status'] = ''; - } - - // Since WP_Comment_Query parameters doesn't have a "page" attribute, we - // need to compute for the offset to get the exact content based on the - // current page and the number of items per page. - - $offset = ((int) $query['page_no'] - 1) * (int) $query['per_page']; - $args = array( - 'orderby' => 'ID', - 'order' => 'DESC', - 'number' => $query['per_page'], - 'offset' => $offset, - 'type' => $query['type'], - 'status' => $query['status'] - ); - - $comments_query = new WP_Comment_Query; - $comments = $comments_query->query($args); - } - - // If no comments are found based on the current query then - // we return with error. - - if (empty($comments)) { - $result = array('message' => 'comments_not_found'); - return $this->_response($result); - } - - // Otherwise, we're going to process each comment - // before we return it to the one issuing the request. - // - // Process in the sense that we add additional related info - // such as the post tile where the comment belongs to, the - // comment status, a formatted date field, and to which parent comment - // does the comment was intended to be as a reply. - - foreach ($comments as &$comment) { - $comment = get_comment($comment->comment_ID, ARRAY_A); - if ($comment) { - $post = get_post($comment['comment_post_ID']); - - if ($post) $comment['in_response_to'] = $post->post_title; - if (!empty($comment['comment_parent'])) { - $parent_comment = get_comment($comment['comment_parent'], ARRAY_A); - if ($parent_comment) $comment['in_reply_to'] = $parent_comment['comment_author']; - } - - // We're formatting the comment_date to be exactly the same - // with that of WP Comments table (e.g. 2016/12/21 at 10:30 PM) - - $comment['comment_date'] = date('Y/m/d \a\t g:i a', strtotime($comment['comment_date'])); - - $status = wp_get_comment_status($comment['comment_ID']); - if ($status) { - $comment['comment_status'] = $status; - } - } - } - - // We return the following to the one issuing - // the request. - - $result = array( - 'comments' => $comments, - 'paging' => $this->_calculate_pages($query) - ); - - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * The get_comment_filters function builds a array of options - * to be use as filters for the search function on the frontend. - */ - public function get_comment_filters() { - // Options for comment_types field - $comment_types = apply_filters('admin_comment_types_dropdown', array( - 'comment' => __('Comments'), - 'pings' => __('Pings'), - )); - - // Options for comment_status field - $comment_statuses = array( - 'approve' => __('Approve'), - 'hold' => __('Hold or Unapprove'), - 'trash' => __('Trash'), - 'spam' => __('Spam'), - ); - - // Pull sites options if available. - $sites = $this->get_blog_sites(); - - $result = array( - 'sites' => $sites, - 'types' => $comment_types, - 'statuses' => $comment_statuses, - 'paging' => $this->_calculate_pages(null), - ); - - return $this->_response($result); - } - - /** - * The get_settings function pulls the current discussion settings - * option values. - * - * @param array $params Passing specific params for getting current discussion settings - * @return array - */ - public function get_settings($params) { - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to manage and edit - // WP options then we return with error. - - if (!current_user_can_for_blog($blog_id, 'manage_options')) { - $result = array('error' => true, 'message' => 'insufficient_permission'); - return $this->_response($result); - } - - // Pull sites options if available. - $sites = $this->get_blog_sites(); - - // Wrap current discussion settings values into an array item - // named settings. - - $result = array( - 'settings' => array( - 'default_pingback_flag' => $this->_get_wp_option($blog_id, 'default_pingback_flag'), - 'default_ping_status' => $this->_get_wp_option($blog_id, 'default_ping_status'), - 'default_comment_status' => $this->_get_wp_option($blog_id, 'default_comment_status'), - 'require_name_email' => $this->_get_wp_option($blog_id, 'require_name_email'), - 'comment_registration' => $this->_get_wp_option($blog_id, 'comment_registration'), - 'close_comments_for_old_posts' => $this->_get_wp_option($blog_id, 'close_comments_for_old_posts'), - 'close_comments_days_old' => $this->_get_wp_option($blog_id, 'close_comments_days_old'), - 'thread_comments' => $this->_get_wp_option($blog_id, 'thread_comments'), - 'thread_comments_depth' => $this->_get_wp_option($blog_id, 'thread_comments_depth'), - 'page_comments' => $this->_get_wp_option($blog_id, 'page_comments'), - 'comments_per_page' => $this->_get_wp_option($blog_id, 'comments_per_page'), - 'default_comments_page' => $this->_get_wp_option($blog_id, 'default_comments_page'), - 'comment_order' => $this->_get_wp_option($blog_id, 'comment_order'), - 'comments_notify' => $this->_get_wp_option($blog_id, 'comments_notify'), - 'moderation_notify' => $this->_get_wp_option($blog_id, 'moderation_notify'), - 'comment_moderation' => $this->_get_wp_option($blog_id, 'comment_moderation'), - 'comment_whitelist' => $this->_get_wp_option($blog_id, 'comment_whitelist'), - 'comment_max_links' => $this->_get_wp_option($blog_id, 'comment_max_links'), - 'moderation_keys' => $this->_get_wp_option($blog_id, 'moderation_keys'), - 'blacklist_keys' => $this->_get_wp_option($blog_id, 'blacklist_keys'), - ), - 'sites' => $sites, - ); - - return $this->_response($result); - } - - /** - * The update_settings function updates the discussion settings - * basing on the user generated content/option from the frontend - * form. - * - * @param array $params Specific params to update settings based on discussion - * @return array - */ - public function update_settings($params) { - - // Extract settings values from passed parameters. - $settings = $params['settings']; - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to manage and edit - // WP options then we return with error. - - if (!current_user_can_for_blog($blog_id, 'manage_options')) { - $result = array('error' => true, 'message' => 'insufficient_permission'); - return $this->_response($result); - } - - // Here, we're sanitizing the input fields before we save them to the database - // for safety and security reason. The "explode" and "implode" functions are meant - // to maintain the line breaks associated with a textarea input/value. - - foreach ($settings as $key => $value) { - - // We're using update_blog_option and update_option altogether to update the current - // discussion settings. - - if (is_multisite()) { - update_blog_option($blog_id, $key, implode("\n", array_map('sanitize_text_field', explode("\n", $value)))); - } else { - update_option($key, implode("\n", array_map('sanitize_text_field', explode("\n", $value)))); - } - } - - // We're not checking for errors here, but instead we're directly returning a success (error = false) - // status always, because WP's update_option will return fail if values were not changed, meaning - // previous values were not changed by the user's current request, not an actual exception thrown. - // Thus, giving a false positive message or report to the frontend. - - $result = array('error' => false, 'message' => 'settings_updated', 'values' => array()); - return $this->_response($result); - } - - /** - * The get_comment function pulls a single comment based - * on a comment ID. - * - * @param array $params Specific params for getting a single comment - * @return array - */ - public function get_comment($params) { - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to moderate or edit - // a comment then we return with error. - - if (!current_user_can_for_blog($blog_id, 'moderate_comments')) { - $result = array('error' => true, 'message' => 'insufficient_permission'); - return $this->_response($result); - } - - // Here, we're switching to the actual blog that we need - // to pull comments from. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - // Get comment by comment_ID parameter and return result as an array. - $result = array( - 'comment' => get_comment($params['comment_id'], ARRAY_A) - ); - - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * The reply_comment function creates a new comment as a reply - * to a certain/selected comment. - * - * @param array $params Specific params to create a new comment reply - * @return array - */ - public function reply_comment($params) { - - // Extract reply info from the passed parameters - $reply = $params['comment']; - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to moderate or edit - // a comment then we return with error. - - if (!current_user_can_for_blog($blog_id, 'moderate_comments')) { - $result = array('error' => true, 'message' => 'comment_reply_no_permission'); - return $this->_response($result); - } - - // Here, we're switching to the actual blog that we need - // to apply our changes. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - - // Get comment by comment_ID parameter. - $comment = get_comment($reply['comment_id']); - if ($comment) { - - // Get the currently logged in user - $user = wp_get_current_user(); - - // If the current comment was not approved yet then - // we need to approve it before we create a reply to - // to the comment, mimicking exactly the WP behaviour - // in terms of creating a reply to a comment. - - if (empty($comment->comment_approved)) { - $update_data = array( - 'comment_ID' => $reply['comment_id'], - 'comment_approved' => 1 - ); - wp_update_comment($update_data); - } - - // Build new comment parameters based on current user info and - // the target comment for the reply. - $data = array( - 'comment_post_ID' => $comment->comment_post_ID, - 'comment_author' => $user->display_name, - 'comment_author_email' => $user->user_email, - 'comment_author_url' => $user->user_url, - 'comment_content' => $reply['message'], - 'comment_parent' => $reply['comment_id'], - 'user_id' => $user->ID, - 'comment_date' => current_time('mysql'), - 'comment_approved' => 1 - ); - - // Create new comment based on the parameters above, and return - // the status accordingly. - - if (wp_insert_comment($data)) { - $result = array('error' => false, 'message' => 'comment_replied_with_comment_author', 'values' => array($comment->comment_author)); - } else { - $result = array('error' => true, 'message' => 'comment_reply_failed_with_error', 'values' => array($comment->comment_ID)); - } - } else { - $result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($reply['comment_id'])); - } - - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * The edit_comment function saves new information for the - * currently selected comment. - * - * @param array $params Specific params for editing a coment - * @return array - */ - public function edit_comment($params) { - - // Extract new comment info from the passed parameters - $comment = $params['comment']; - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to moderate or edit - // a comment then we return with error. - - if (!current_user_can_for_blog($blog_id, 'moderate_comments')) { - $result = array('error' => true, 'message' => 'comment_edit_no_permission'); - return $this->_response($result); - } - - // Here, we're switching to the actual blog that we need - // to apply our changes. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - - // Get current comment details - $original_comment = get_comment($comment['comment_id']); - if ($original_comment) { - $data = array(); - - // Replace "comment_id" with "comment_ID" since WP does not recognize - // the small case "id". - $comment['comment_ID'] = $original_comment->comment_ID; - unset($comment['comment_id']); - - // Here, we're sanitizing the input fields before we save them to the database - // for safety and security reason. The "explode" and "implode" functions are meant - // to maintain the line breaks associated with a textarea input/value. - - foreach ($comment as $key => $value) { - $data[$key] = implode("\n", array_map('sanitize_text_field', explode("\n", $value))); - } - - // Update existing comment based on the passed parameter fields and - // return the status accordingly. - - if (wp_update_comment($data)) { - $result = array('error' => false, 'message' => 'comment_edited_with_comment_author', 'values' => array($original_comment->comment_author)); - } else { - $result = array('error' => true, 'message' => 'comment_edit_failed_with_error', 'values' => array($original_comment->comment_ID)); - } - } else { - $result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($comment['comment_id'])); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * The update_comment_status function is a generic handler for the following - * comment actions: - * - * - approve comment - * - unapprove comment - * - set comment as spam - * - move commment to trash - * - delete comment permanently - * - unset comment as spam - * - restore comment - * - * @param array $params Specific params to update comment status - * @return array - */ - public function update_comment_status($params) { - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['blog_id'])) $blog_id = $params['blog_id']; - - - // If user does not have sufficient privileges to moderate or edit - // a comment then we return with error. - - if (!current_user_can_for_blog($blog_id, 'moderate_comments')) { - $result = array('error' => true, 'message' => 'comment_change_status_no_permission'); - return $this->_response($result); - } - - // Here, we're switching to the actual blog that we need - // to apply our changes. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - - // We make sure that we still have a valid comment from the server - // before we apply the currently selected action. - - $comment = get_comment($params['comment_id']); - if ($comment) { - $post = get_post($comment->comment_post_ID); - - if ($post) $comment->in_response_to = $post->post_title; - if (!empty($comment->comment_parent)) { - $parent_comment = get_comment($comment->comment_parent); - if ($parent_comment) $comment->in_reply_to = $parent_comment->comment_author; - } - - // We're formatting the comment_date to be exactly the same - // with that of WP Comments table (e.g. 2016/12/21 at 10:30 PM) - - $comment->comment_date = date('Y/m/d \a\t g:i a', strtotime($comment->comment_date)); - - $status = wp_get_comment_status($comment->comment_ID); - if ($status) { - $comment->comment_status = $status; - } - - $succeeded = false; - $message = ''; - - // Here, we're using WP's wp_set_comment_status function to change the state - // of the selected comment based on the current action, except for the "delete" action - // where we use the wp_delete_comment to delete the comment permanently by passing - // "true" to the second argument. - - switch ($params['action']) { - case 'approve': - $succeeded = wp_set_comment_status($params['comment_id'], 'approve'); - $message = 'comment_approve_with_comment_author'; - break; - case 'unapprove': - $succeeded = wp_set_comment_status($params['comment_id'], 'hold'); - $message = 'comment_unapprove_with_comment_author'; - break; - case 'spam': - $succeeded = wp_set_comment_status($params['comment_id'], 'spam'); - $message = 'comment_spam_with_comment_author'; - break; - case 'trash': - $succeeded = wp_set_comment_status($params['comment_id'], 'trash'); - $message = 'comment_trash_with_comment_author'; - break; - case 'delete': - $succeeded = wp_delete_comment($params['comment_id'], true); - $message = 'comment_delete_with_comment_author'; - break; - case 'notspam': - $succeeded = wp_set_comment_status($params['comment_id'], 'hold'); - $message = 'comment_not_spam_with_comment_author'; - break; - case 'restore': - $succeeded = wp_set_comment_status($params['comment_id'], 'hold'); - $message = 'comment_restore_with_comment_author'; - break; - } - - // If the current action succeeded, then we return a success message, otherwise, - // we return an error message to the user issuing the request. - - if ($succeeded) { - $result = array('error' => false, 'message' => $message, 'values' => array($comment->comment_author), 'status' => $comment->comment_status, 'approved' => $comment->comment_approved); - } else { - $result = array('error' => true, 'message' => 'comment_change_status_failed_with_error', 'values' => array($comment->comment_ID)); - } - } else { - $result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($params['comment_id'])); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/core.php b/wordpress/wp-content/plugins/updraftplus/central/modules/core.php deleted file mode 100644 index a5e5f94b21517f629591e6eaa2125ed91e2f8ff4..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/core.php +++ /dev/null @@ -1,429 +0,0 @@ - (string - a code), 'data' => (mixed)); - * - * RPC commands are not allowed to begin with an underscore. So, any private methods can be prefixed with an underscore. - */ -class UpdraftCentral_Core_Commands extends UpdraftCentral_Commands { - - /** - * Executes a list of submitted commands (multiplexer) - * - * @param Array $query An array containing the commands to execute and a flag to indicate how to handle command execution failure. - * @return Array An array containing the results of the process. - */ - public function execute_commands($query) { - - try { - - $commands = $query['commands']; - $command_results = array(); - $error_count = 0; - - /** - * Should be one of the following options: - * 1 = Abort on first failure - * 2 = Abort if any command fails - * 3 = Abort if all command fails (default) - */ - $error_flag = isset($query['error_flag']) ? (int) $query['error_flag'] : 3; - - - foreach ($commands as $command => $params) { - $command_info = apply_filters('updraftcentral_get_command_info', false, $command); - if (!$command_info) { - list($_prefix, $_command) = explode('.', $command); - $command_results[$_prefix][$_command] = array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => $command)); - - $error_count++; - if (1 === $error_flag) break; - } else { - - $action = $command_info['command']; - $command_php_class = $command_info['command_php_class']; - - // Instantiate the command class and execute the needed action - if (class_exists($command_php_class)) { - $instance = new $command_php_class($this->rc); - - if (method_exists($instance, $action)) { - $params = empty($params) ? array() : $params; - $call_result = call_user_func_array(array($instance, $action), $params); - - $command_results[$command] = $call_result; - if ('rpcerror' === $call_result['response'] || (isset($call_result['data']['error']) && $call_result['data']['error'])) { - $error_count++; - if (1 === $error_flag) break; - } - } - } - } - } - - if (0 !== $error_count) { - // N.B. These error messages should be defined in UpdraftCentral's translation file (dashboard-translations.php) - // before actually using this multiplexer function. - $message = 'general_command_execution_error'; - - switch ($error_flag) { - case 1: - $message = 'command_execution_aborted'; - break; - case 2: - $message = 'failed_to_execute_some_commands'; - break; - case 3: - if (count($commands) === $error_count) { - $message = 'failed_to_execute_all_commands'; - } - break; - default: - break; - } - - $result = array('error' => true, 'message' => $message, 'values' => $command_results); - } else { - $result = $command_results; - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => $e->getMessage()); - } - - return $this->_response($result); - } - - /** - * Validates the credentials entered by the user - * - * @param array $creds an array of filesystem credentials - * @return array An array containing the result of the validation process. - */ - public function validate_credentials($creds) { - - try { - - $entity = $creds['entity']; - if (isset($creds['filesystem_credentials'])) { - parse_str($creds['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. - $_POST[$key] = $value; - } - } - } - - // Include the needed WP Core file(s) - // template.php needed for submit_button() which is called by request_filesystem_credentials() - $this->_admin_include('file.php', 'template.php'); - - // Directory entities that we currently need permissions - // to update. - $entity_directories = array( - 'plugins' => WP_PLUGIN_DIR, - 'themes' => WP_CONTENT_DIR.'/themes', - 'core' => untrailingslashit(ABSPATH) - ); - - $url = wp_nonce_url(site_url()); - $directory = $entity_directories[$entity]; - - // Check if credentials are valid and have sufficient - // privileges to create and delete (e.g. write) - ob_start(); - $credentials = request_filesystem_credentials($url, '', false, $directory); - ob_end_clean(); - - // The "WP_Filesystem" will suffice in validating the inputted credentials - // from UpdraftCentral, as it is already attempting to connect to the filesystem - // using the chosen transport (e.g. ssh, ftp, etc.) - if (WP_Filesystem($credentials, $directory)) { - $result = array('error' => false, 'message' => 'credentials_ok', 'values' => array()); - } else { - // We're adding some useful error information to help troubleshooting any problems - // that may arise in the future. If the user submitted a wrong password or username - // it usually falls through here. - global $wp_filesystem; - - $errors = array(); - if (isset($wp_filesystem->errors) && is_wp_error($wp_filesystem->errors)) { - $errors = $wp_filesystem->errors->errors; - } - - $result = array('error' => true, 'message' => 'failed_credentials', 'values' => array('errors' => $errors)); - } - - } catch (Exception $e) { - $result = array('error' => true, 'message' => $e->getMessage(), 'values' => array()); - } - - return $this->_response($result); - } - - /** - * Gets the FileSystem Credentials - * - * Extract the needed filesystem credentials (permissions) to be used - * to update/upgrade the plugins, themes and the WP core. - * - * @return array $result - An array containing the creds form and some flags - * to determine whether we need to extract the creds - * manually from the user. - */ - public function get_credentials() { - - try { - - // Check whether user has enough permission to update entities - if (!current_user_can('update_plugins') && !current_user_can('update_themes') && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied'); - - // Include the needed WP Core file(s) - $this->_admin_include('file.php', 'template.php'); - - // A container that will hold the state (in this case, either true or false) of - // each directory entities (plugins, themes, core) that will be used to determine - // whether or not there's a need to show a form that will ask the user for their credentials - // manually. - $request_filesystem_credentials = array(); - - // A container for the filesystem credentials form if applicable. - $filesystem_form = ''; - - // Directory entities that we currently need permissions - // to update. - $check_fs = array( - 'plugins' => WP_PLUGIN_DIR, - 'themes' => WP_CONTENT_DIR.'/themes', - 'core' => untrailingslashit(ABSPATH) - ); - - // Here, we're looping through each entities and find output whether - // we have sufficient permissions to update objects belonging to them. - foreach ($check_fs as $entity => $dir) { - - // We're determining which method to use when updating - // the files in the filesystem. - $filesystem_method = get_filesystem_method(array(), $dir); - - // Buffering the output to pull the actual credentials form - // currently being used by this WP instance if no sufficient permissions - // is found. - $url = wp_nonce_url(site_url()); - - ob_start(); - $filesystem_credentials_are_stored = request_filesystem_credentials($url, $filesystem_method); - $form = strip_tags(ob_get_contents(), '

    '); - - if (!empty($form)) { - $filesystem_form = $form; - } - ob_end_clean(); - - // Save the state whether or not there's a need to show the - // credentials form to the user. - $request_filesystem_credentials[$entity] = ('direct' !== $filesystem_method && !$filesystem_credentials_are_stored); - } - - // Wrapping the credentials info before passing it back - // to the client issuing the request. - $result = array( - 'request_filesystem_credentials' => $request_filesystem_credentials, - 'filesystem_form' => $filesystem_form - ); - - } catch (Exception $e) { - $result = array('error' => true, 'message' => $e->getMessage(), 'values' => array()); - } - - return $this->_response($result); - } - - /** - * Fetches a browser-usable URL which will automatically log the user in to the site - * - * @param String $redirect_to - the URL to got to after logging in - * @param Array $extra_info - valid keys are user_id, which should be a numeric user ID to log in as. - */ - public function get_login_url($redirect_to, $extra_info) { - if (is_array($extra_info) && !empty($extra_info['user_id']) && is_numeric($extra_info['user_id'])) { - - $user_id = $extra_info['user_id']; - - if (false == ($login_key = $this->_get_autologin_key($user_id))) return $this->_generic_error_response('user_key_failure'); - - // Default value - $redirect_url = network_admin_url(); - if (is_array($redirect_to) && !empty($redirect_to['module'])) { - switch ($redirect_to['module']) { - case 'updraftplus': - if ('initiate_restore' == $redirect_to['action'] && class_exists('UpdraftPlus_Options')) { - $redirect_url = UpdraftPlus_Options::admin_page_url().'?page=updraftplus&udaction=initiate_restore&entities='.urlencode($redirect_to['data']['entities']).'&showdata='.urlencode($redirect_to['data']['showdata']).'&backup_timestamp='.(int) $redirect_to['data']['backup_timestamp']; - } elseif ('download_file' == $redirect_to['action']) { - $findex = empty($redirect_to['data']['findex']) ? 0 : (int) $redirect_to['data']['findex']; - // e.g. ?udcentral_action=dl&action=updraftplus_spool_file&backup_timestamp=1455101696&findex=0&what=plugins - $redirect_url = site_url().'?udcentral_action=spool_file&action=updraftplus_spool_file&findex='.$findex.'&what='.urlencode($redirect_to['data']['what']).'&backup_timestamp='.(int) $redirect_to['data']['backup_timestamp']; - } - break; - case 'direct_url': - $redirect_url = $redirect_to['url']; - break; - } - } - - $login_key = apply_filters('updraftplus_remotecontrol_login_key', array( - 'key' => $login_key, - 'created' => time(), - 'redirect_url' => $redirect_url - ), $redirect_to, $extra_info); - - // Over-write any previous value - only one can be valid at a time) - update_user_meta($user_id, 'updraftcentral_login_key', $login_key); - - return $this->_response(array( - 'login_url' => network_site_url('?udcentral_action=login&login_id='.$user_id.'&login_key='.$login_key['key']) - )); - - } else { - return $this->_generic_error_response('user_unknown'); - } - } - - /** - * Get information derived from phpinfo() - * - * @return Array - */ - public function phpinfo() { - $phpinfo = $this->_get_phpinfo_array(); - - if (!empty($phpinfo)) { - return $this->_response($phpinfo); - } - - return $this->_generic_error_response('phpinfo_fail'); - } - - /** - * The key obtained is only intended to be short-lived. Hence, there's no intention other than that it is random and only used once - only the most recent one is valid. - * - * @param Integer $user_id Specific user ID to get the autologin key - * @return Array - */ - public function _get_autologin_key($user_id) { - $secure_auth_key = defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : hash('sha256', DB_PASSWORD).'_'.rand(0, 999999999); - if (!defined('SECURE_AUTH_KEY')) return false; - $hash_it = $user_id.'_'.microtime(true).'_'.rand(0, 999999999).'_'.$secure_auth_key; - $hash = hash('sha256', $hash_it); - return $hash; - } - - public function site_info() { - - global $wpdb; - - // THis is included so we can get $wp_version - @include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $ud_version = is_a($this->ud, 'UpdraftPlus') ? $this->ud->version : 'none'; - - return $this->_response(array( - 'versions' => array( - 'ud' => $ud_version, - 'php' => PHP_VERSION, - 'wp' => $wp_version,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable - 'mysql' => $wpdb->db_version(), - 'udrpc_php' => $this->rc->udrpc_version, - ), - 'bloginfo' => array( - 'url' => network_site_url(), - 'name' => get_bloginfo('name'), - ) - )); - } - - /** - * This calls the WP_Action within WP - * - * @param array $data Array of Data to be used within call_wp_action - * @return array - */ - public function call_wordpress_action($data) { - if (false === ($updraftplus_admin = $this->_load_ud_admin())) return $this->_generic_error_response('no_updraftplus'); - - $response = $updraftplus_admin->call_wp_action($data); - - if (empty($data["wpaction"])) { - return $this->_generic_error_response("error", "no command sent"); - } - - return $this->_response(array( - "response" => $response['response'], - "status" => $response['status'], - "log" => $response['log'] - )); - } - - /** - * Get disk space used - * - * @uses UpdraftPlus_Filesystem_Functions::get_disk_space_used() - * - * @param String $entity - the entity to count (e.g. 'plugins', 'themes') - * - * @return Array - response - */ - public function count($entity) { - - if (!class_exists('UpdraftPlus_Filesystem_Functions')) return $this->_generic_error_response('no_updraftplus'); - - $response = UpdraftPlus_Filesystem_Functions::get_disk_space_used($entity); - - return $this->_response($response); - } - - /** - * https://secure.php.net/phpinfo - * - * @return null|array - */ - private function _get_phpinfo_array() { - ob_start(); - phpinfo(INFO_GENERAL|INFO_CREDITS|INFO_MODULES); - $phpinfo = array('phpinfo' => array()); - - if (preg_match_all('#(?:

    (?:)?(.*?)(?:)?

    )|(?:(.*?)\s*(?:(.*?)\s*(?:(.*?)\s*)?)?)#s', ob_get_clean(), $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - if (strlen($match[1])) { - $phpinfo[$match[1]] = array(); - } elseif (isset($match[3])) { - $keys1 = array_keys($phpinfo); - $phpinfo[end($keys1)][$match[2]] = isset($match[4]) ? array($match[3], $match[4]) : $match[3]; - } else { - $keys1 = array_keys($phpinfo); - $phpinfo[end($keys1)][] = $match[2]; - - } - - } - return $phpinfo; - } - return false; - } - - /** - * Return an UpdraftPlus_Admin object - * - * @return UpdraftPlus_Admin|Boolean - false in case of failure - */ - private function _load_ud_admin() { - if (!defined('UPDRAFTPLUS_DIR') || !is_file(UPDRAFTPLUS_DIR.'/admin.php')) return false; - include_once(UPDRAFTPLUS_DIR.'/admin.php'); - global $updraftplus_admin; - return $updraftplus_admin; - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/media.php b/wordpress/wp-content/plugins/updraftplus/central/modules/media.php deleted file mode 100644 index 78df93477d1f47978fea270d6e35fe938b2c9756..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/media.php +++ /dev/null @@ -1,567 +0,0 @@ -switched = switch_to_blog($blog_id); - } - } - - /** - * Function that gets called after every action - * - * @param string $command a string that corresponds to UDC command to call a certain method for this class. - * @param array $data an array of data post or get fields - * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id - * - * link to udrpc_action main function in class UpdraftCentral_Listener - */ - public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Here, we're restoring to the current (default) blog before we switched - if ($this->switched) restore_current_blog(); - } - - /** - * Fetch and retrieves posts based from the submitted parameters - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_media_items($params) { - $error = $this->_validate_capabilities(array('upload_files', 'edit_posts')); - if (!empty($error)) return $error; - - // check paged parameter; if empty set to defaults - $paged = !empty($params['paged']) ? (int) $params['paged'] : 1; - $numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10; - $offset = ($paged - 1) * $numberposts; - - $args = array( - 'posts_per_page' => $numberposts, - 'paged' => $paged, - 'offset' => $offset, - 'post_type' => 'attachment', - 'post_status' => 'inherit', - ); - - if (!empty($params['keyword'])) { - $args['s'] = $params['keyword']; - } - - if (!empty($params['category'])) { - if (in_array($params['category'], array('detached', 'unattached'))) { - $attachment_ids = $this->get_unattached_ids(); - } else { - $attachment_ids = $this->get_type_ids($params['category']); - } - - $args['post__in'] = $attachment_ids; - } - - if (!empty($params['date'])) { - list($monthnum, $year) = explode(':', $params['date']); - - $args['monthnum'] = $monthnum; - $args['year'] = $year; - } - - $query = new WP_Query($args); - $result = $query->posts; - - $count_posts = (int) $query->found_posts; - $page_count = 0; - - if ($count_posts > 0) { - $page_count = absint($count_posts / $numberposts); - $remainder = absint($count_posts % $numberposts); - $page_count = ($remainder > 0) ? ++$page_count : $page_count; - } - - $info = array( - 'page' => $paged, - 'pages' => $page_count, - 'results' => $count_posts, - 'items_from' => (($paged * $numberposts) - $numberposts) + 1, - 'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts, - ); - - $media_items = array(); - if (!empty($result)) { - foreach ($result as $item) { - $media = $this->get_media_item($item, null, true); - if (!empty($media)) { - array_push($media_items, $media); - } - } - } - - $response = array( - 'items' => $media_items, - 'info' => $info, - 'options' => array( - 'date' => $this->get_date_options(), - 'type' => $this->get_type_options() - ) - ); - - return $this->_response($response); - } - - /** - * Fetch a single media item information - * - * @param array $params Containing all the needed information to filter the results of the current request - * @param array|null $extra_info Additional information from the current request - * @param boolean $raw If set, returns the result of the fetch process unwrapped by the response array - * @return array - */ - public function get_media_item($params, $extra_info = null, $raw = false) { - $error = $this->_validate_capabilities(array('upload_files', 'edit_posts')); - if (!empty($error)) return $error; - - // Raw means that we need to return the result without wrapping it - // with the "$this->_response" function which indicates that the call - // was done locally (within the class) and not directly from UpdraftCentral. - if ($raw && is_object($params) && isset($params->ID)) { - $media = $params; - } elseif (is_array($params) && !empty($params['id'])) { - $media = get_post($params['id']); - } - - if (!function_exists('get_post_mime_types')) { - global $updraftplus; - // For a much later version of WP the "get_post_mime_types" is located - // in a different folder. So, we make sure that we have it loaded before - // actually using it. - if (version_compare($updraftplus->get_wordpress_version(), '3.5', '>=')) { - require_once(ABSPATH.WPINC.'/post.php'); - } else { - // For WP 3.4, the "get_post_mime_types" is located in the location provided below. - require_once(ABSPATH.'wp-admin/includes/post.php'); - } - } - - if (!function_exists('wp_image_editor')) { - require_once(ABSPATH.'wp-admin/includes/image-edit.php'); - } - - if (!function_exists('get_media_item')) { - require_once(ABSPATH.'wp-admin/includes/template.php'); - require_once(ABSPATH.'wp-admin/includes/media.php'); - } - - - if ($media) { - $thumb = wp_get_attachment_image_src($media->ID, 'thumbnail', true); - if (!empty($thumb)) $media->thumb_url = $thumb[0]; - - $media->url = wp_get_attachment_url($media->ID); - $media->parent_post_title = get_the_title($media->post_parent); - $media->author = get_the_author_meta('display_name', $media->post_author); - $media->filename = basename($media->url); - $media->date = date('Y/m/d', strtotime($media->post_date)); - $media->upload_date = mysql2date(get_option('date_format'), $media->post_date); - - $media->filesize = 0; - $file = get_attached_file($media->ID); - if (!empty($file) && file_exists($file)) { - $media->filesize = size_format(filesize($file)); - } - - $media->nonce = wp_create_nonce('image_editor-'.$media->ID); - if (false !== strpos($media->post_mime_type, 'image/')) { - $meta = wp_get_attachment_metadata($media->ID); - - $thumb = image_get_intermediate_size($media->ID, 'thumbnail'); - $sub_sizes = isset($meta['sizes']) && is_array($meta['sizes']); - - // Pulling details - $sizer = 1; - if (isset($meta['width'], $meta['height'])) { - $big = max($meta['width'], $meta['height']); - $sizer = $big > 400 ? 400 / $big : 1; - } - - $constrained_dims = array(); - if ($thumb && $sub_sizes) { - $constrained_dims = wp_constrain_dimensions($thumb['width'], $thumb['height'], 160, 120); - } - - $rotate_supported = false; - if (function_exists('imagerotate') || wp_image_editor_supports(array('mime_type' => get_post_mime_type($media->ID), 'methods' => array('rotate')))) { - $rotate_supported = true; - } - - // Check for alternative text if present - $alt = get_post_meta($media->ID, '_wp_attachment_image_alt', true); - $media->alt = !empty($alt) ? $alt : ''; - - // Check whether edited images are restorable - $backup_sizes = get_post_meta($media->ID, '_wp_attachment_backup_sizes', true); - $can_restore = !empty($backup_sizes) && isset($backup_sizes['full-orig']) && basename($meta['file']) != $backup_sizes['full-orig']['file']; - - $image_edit_overwrite = (!defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE) ? 0 : 1; - $media->misc = array( - 'sizer' => $sizer, - 'rand' => rand(1, 99999), - 'constrained_dims' => $constrained_dims, - 'rotate_supported' => (int) $rotate_supported, - 'thumb' => $thumb, - 'meta' => $meta, - 'alt_text' => $alt, - 'can_restore' => $can_restore, - 'image_edit_overwrite' => $image_edit_overwrite - ); - } - } - - return $raw ? $media : $this->_response(array('item' => $media)); - } - - /** - * Fetch and retrieves posts based from the submitted parameters - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_posts($params) { - $error = $this->_validate_capabilities(array('edit_posts')); - if (!empty($error)) return $error; - - // check paged parameter; if empty set to defaults - $paged = !empty($params['paged']) ? (int) $params['paged'] : 1; - $numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10; - $offset = ($paged - 1) * $numberposts; - - $args = array( - 'posts_per_page' => $numberposts, - 'paged' => $paged, - 'offset' => $offset, - 'post_type' => 'post', - 'post_status' => 'publish,private,draft,pending,future', - ); - - if (!empty($params['keyword'])) { - $args['s'] = $params['keyword']; - } - - $query = new WP_Query($args); - $result = $query->posts; - - $count_posts = (int) $query->found_posts; - $page_count = 0; - - if ($count_posts > 0) { - $page_count = absint($count_posts / $numberposts); - $remainder = absint($count_posts % $numberposts); - $page_count = ($remainder > 0) ? ++$page_count : $page_count; - } - - $info = array( - 'page' => $paged, - 'pages' => $page_count, - 'results' => $count_posts, - 'items_from' => (($paged * $numberposts) - $numberposts) + 1, - 'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts, - ); - - $posts = array(); - if (!empty($result)) { - foreach ($result as $post) { - array_push($posts, array('ID' => $post->ID, 'title' => $post->post_title)); - } - } - - $response = array( - 'posts' => $posts, - 'info' => $info - ); - return $this->_response($response); - } - - /** - * Saves media changes from UpdraftCentral - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function save_media_item($params) { - $error = $this->_validate_capabilities(array('upload_files', 'edit_posts')); - if (!empty($error)) return $error; - - $args = array( - 'post_title' => $params['image_title'], - 'post_excerpt' => $params['image_caption'], - 'post_content' => $params['image_description'] - ); - - if (!empty($params['new'])) { - $args['post_type'] = 'attachment'; - $media_id = wp_insert_post($args, true); - } else { - $args['ID'] = $params['id']; - $args['post_modified'] = date('Y-m-d H:i:s'); - $args['post_modified_gmt'] = gmdate('Y-m-d H:i:s'); - - $media_id = wp_update_post($args, true); - } - - if (!empty($media_id)) { - // Update alternative text if not empty - if (!empty($params['image_alternative_text'])) { - update_post_meta($media_id, '_wp_attachment_image_alt', $params['image_alternative_text']); - } - - $result = array( - 'status' => 'success', - 'item' => $this->get_media_item(array('id' => $media_id), null, true) - ); - } else { - $result = array('status' => 'failed'); - } - - return $this->_response($result); - } - - /** - * Executes media action (e.g. attach, detach and delete) - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function execute_media_action($params) { - $error = $this->_validate_capabilities(array('upload_files', 'edit_posts')); - if (!empty($error)) return $error; - - $result = array(); - switch ($params['do']) { - case 'attach': - global $wpdb; - $query_result = $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET `post_parent` = %d WHERE `post_type` = 'attachment' AND ID = %d", $params['parent_id'], $params['id'])); - - if (false === $query_result) { - $result['error'] = __('Failed to attach media.', 'updraftplus'); - } else { - $result['msg'] = __('Media has been attached to post.', 'updraftplus'); - } - break; - case 'detach': - global $wpdb; - $query_result = $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET `post_parent` = 0 WHERE `post_type` = 'attachment' AND ID = %d", $params['id'])); - - if (false === $query_result) { - $result['error'] = __('Failed to detach media.', 'updraftplus'); - } else { - $result['msg'] = __('Media has been detached from post.', 'updraftplus'); - } - break; - case 'delete': - $failed_items = array(); - foreach ($params['ids'] as $id) { - // Delete permanently - if (false === wp_delete_attachment($id, true)) { - $failed_items[] = $id; - } - } - - if (!empty($failed_items)) { - $result['error'] = __('Failed to delete selected media.', 'updraftplus'); - $result['items'] = $failed_items; - } else { - $result['msg'] = __('Selected media has been deleted successfully.', 'updraftplus'); - } - break; - default: - break; - } - - return $this->_response($result); - } - - /** - * Retrieves a collection of formatted dates found for the given post statuses. - * It will be used as options for the date filter when managing the media items in UpdraftCentral. - * - * @return array - */ - private function get_date_options() { - global $wpdb; - $options = array(); - - $date_options = $wpdb->get_col("SELECT DATE_FORMAT(`post_date`, '%M %Y') as `formatted_post_date` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' GROUP BY `formatted_post_date` ORDER BY `post_date` DESC"); - - if (!empty($date_options)) { - foreach ($date_options as $monthyear) { - $timestr = strtotime($monthyear); - $options[] = array('label' => date('F Y', $timestr), 'value' => date('n:Y', $timestr)); - } - } - - return $options; - } - - /** - * Retrieves mime types that will be use as filter option in UpdraftCentral - * - * @return array - */ - private function get_type_options() { - global $wpdb; - $options = array(); - - if (!function_exists('get_post_mime_types')) { - global $updraftplus; - // For a much later version of WP the "get_post_mime_types" is located - // in a different folder. So, we make sure that we have it loaded before - // actually using it. - if (version_compare($updraftplus->get_wordpress_version(), '3.5', '>=')) { - require_once(ABSPATH.WPINC.'/post.php'); - } else { - // For WP 3.4, the "get_post_mime_types" is located in the location provided below. - require_once(ABSPATH.'wp-admin/includes/post.php'); - } - } - - $post_mime_types = get_post_mime_types(); - $type_options = $wpdb->get_col("SELECT `post_mime_type` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' GROUP BY `post_mime_type` ORDER BY `post_mime_type` DESC"); - - foreach ($post_mime_types as $mime_type => $label) { - if (!wp_match_mime_types($mime_type, $type_options)) continue; - $options[] = array('label' => $label[0], 'value' => esc_attr($mime_type)); - } - - $options[] = array('label' => __('Unattached', 'updraftplus'), 'value' => 'detached'); - return $options; - } - - /** - * Retrieves media items that haven't been attached to any posts - * - * @return array - */ - private function get_unattached_ids() { - global $wpdb; - return $wpdb->get_col("SELECT `ID` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' AND `post_parent` = '0'"); - } - - /** - * Retrieves IDs of media items that has the given mime type - * - * @param string $type The mime type to search for - * @return array - */ - private function get_type_ids($type) { - global $wpdb; - return $wpdb->get_col($wpdb->prepare("SELECT `ID` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' AND `post_mime_type` LIKE '%s/%%'", $type)); - } - - /** - * Checks whether we have the required fields submitted and the user has - * the capabilities to execute the requested action - * - * @param array $capabilities The capabilities to check and validate - * - * @return array|void - */ - private function _validate_capabilities($capabilities) { - foreach ($capabilities as $capability) { - if (!current_user_can($capability)) { - return $this->_generic_error_response('insufficient_permission'); - } - } - } - - /** - * Populates the $_REQUEST global variable with the submitted data - * - * @param array $params Submitted data received from UpdraftCentral - * @return array - */ - private function populate_request($params) { - if (!empty($params)) { - foreach ($params as $key => $value) { - $_REQUEST[$key] = $value; - } - } - } - - /** - * Handles image editing requests coming from UpdraftCentral - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function image_editor($params) { - $error = $this->_validate_capabilities(array('edit_posts')); - if (!empty($error)) return $error; - - $attachment_id = (int) $params['postid']; - $this->populate_request($params); - - if (!function_exists('load_image_to_edit')) { - require_once(ABSPATH.'wp-admin/includes/image.php'); - } - - include_once(ABSPATH.'wp-admin/includes/image-edit.php'); - $msg = false; - switch ($params['do']) { - case 'save': - case 'scale': - $msg = wp_save_image($attachment_id); - break; - case 'restore': - $msg = wp_restore_image($attachment_id); - break; - } - - $msg = (false !== $msg) ? json_encode($msg) : $msg; - return $this->_response(array('content' => $msg)); - } - - /** - * Handles image preview requests coming from UpdraftCentral - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function image_preview($params) { - $error = $this->_validate_capabilities(array('edit_posts')); - if (!empty($error)) return $error; - - if (!function_exists('load_image_to_edit')) { - require_once(ABSPATH.'wp-admin/includes/image.php'); - } - - include_once(ABSPATH.'wp-admin/includes/image-edit.php'); - $this->populate_request($params); - $post_id = (int) $params['postid']; - - ob_start(); - stream_preview_image($post_id); - $content = ob_get_contents(); - ob_end_clean(); - - return $this->_response(array('content' => base64_encode($content))); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/pages.php b/wordpress/wp-content/plugins/updraftplus/central/modules/pages.php deleted file mode 100644 index 7ff769a6158007ec55b9933b6fc598352155a828..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/pages.php +++ /dev/null @@ -1,15 +0,0 @@ -switched = switch_to_blog($blog_id); - } - } - - /** - * Function that gets called after every action - * - * @param string $command a string that corresponds to UDC command to call a certain method for this class. - * @param array $data an array of data post or get fields - * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id - * - * link to udrpc_action main function in class UpdraftCentral_Listener - */ - public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Here, we're restoring to the current (default) blog before we switched - if ($this->switched) restore_current_blog(); - } - - /** - * Constructor - */ - public function __construct() { - $this->_admin_include('plugin.php', 'file.php', 'template.php', 'class-wp-upgrader.php', 'plugin-install.php', 'update.php'); - } - - /** - * Installs and activates a plugin through upload - * - * @param array $params Parameter array containing information pertaining the currently uploaded plugin - * @return array Contains the result of the current process - */ - public function upload_plugin($params) { - return $this->process_chunk_upload($params, 'plugin'); - } - - /** - * Checks whether the plugin is currently installed and activated. - * - * @param array $query Parameter array containing the name of the plugin to check - * @return array Contains the result of the current process - */ - public function is_plugin_installed($query) { - - if (!isset($query['plugin'])) - return $this->_generic_error_response('plugin_name_required'); - - - $result = $this->_get_plugin_info($query); - return $this->_response($result); - } - - /** - * Applies currently requested action for plugin processing - * - * @param string $action The action to apply (e.g. activate or install) - * @param array $query Parameter array containing information for the currently requested action - * - * @return array - */ - private function _apply_plugin_action($action, $query) { - - $result = array(); - switch ($action) { - case 'activate': - case 'network_activate': - $info = $this->_get_plugin_info($query); - if ($info['installed']) { - if (is_multisite() && 'network_activate' === $action) { - $activate = activate_plugin($info['plugin_path'], '', true); - } else { - $activate = activate_plugin($info['plugin_path']); - } - - if (is_wp_error($activate)) { - $result = $this->_generic_error_response('generic_response_error', array($activate->get_error_message())); - } else { - $result = array('activated' => true); - } - } else { - $result = $this->_generic_error_response('plugin_not_installed', array($query['plugin'])); - } - break; - case 'deactivate': - case 'network_deactivate': - $info = $this->_get_plugin_info($query); - if ($info['active']) { - if (is_multisite() && 'network_deactivate' === $action) { - deactivate_plugins($info['plugin_path'], false, true); - } else { - deactivate_plugins($info['plugin_path']); - } - - if (!is_plugin_active($info['plugin_path'])) { - $result = array('deactivated' => true); - } else { - $result = $this->_generic_error_response('deactivate_plugin_failed', array($query['plugin'])); - } - } else { - $result = $this->_generic_error_response('not_active', array($query['plugin'])); - } - break; - case 'install': - $api = plugins_api('plugin_information', array( - 'slug' => $query['slug'], - 'fields' => array( - 'short_description' => false, - 'sections' => false, - 'requires' => false, - 'rating' => false, - 'ratings' => false, - 'downloaded' => false, - 'last_updated' => false, - 'added' => false, - 'tags' => false, - 'compatibility' => false, - 'homepage' => false, - 'donate_link' => false, - ) - )); - - if (is_wp_error($api)) { - $result = $this->_generic_error_response('generic_response_error', array($api->get_error_message())); - } else { - $info = $this->_get_plugin_info($query); - $installed = $info['installed']; - - $error_code = $error_message = ''; - if (!$installed) { - // WP < 3.7 - if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTPLUS_DIR.'/central/classes/class-automatic-upgrader-skin.php'); - - $skin = new Automatic_Upgrader_Skin(); - $upgrader = new Plugin_Upgrader($skin); - - $download_link = $api->download_link; - $installed = $upgrader->install($download_link); - - if (is_wp_error($installed)) { - $error_code = $installed->get_error_code(); - $error_message = $installed->get_error_message(); - } elseif (is_wp_error($skin->result)) { - $error_code = $skin->result->get_error_code(); - $error_message = $skin->result->get_error_message(); - - $error_data = $skin->result->get_error_data($error_code); - if (!empty($error_data)) { - if (is_array($error_data)) $error_data = json_encode($error_data); - $error_message .= ' '.$error_data; - } - } elseif (is_null($installed) || !$installed) { - global $wp_filesystem; - $upgrade_messages = $skin->get_upgrade_messages(); - - if (!class_exists('WP_Filesystem_Base')) include_once(ABSPATH.'/wp-admin/includes/class-wp-filesystem-base.php'); - - // Pass through the error from WP_Filesystem if one was raised. - if ($wp_filesystem instanceof WP_Filesystem_Base && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) { - $error_code = $wp_filesystem->errors->get_error_code(); - $error_message = $wp_filesystem->errors->get_error_message(); - } elseif (!empty($upgrade_messages)) { - // We're only after for the last feedback that we received from the install process. Mostly, - // that is where the last error has been inserted. - $messages = $skin->get_upgrade_messages(); - $error_code = 'install_failed'; - $error_message = end($messages); - } else { - $error_code = 'unable_to_connect_to_filesystem'; - $error_message = __('Unable to connect to the filesystem. Please confirm your credentials.'); - } - } - } - - if (!$installed || is_wp_error($installed)) { - $result = $this->_generic_error_response('plugin_install_failed', array( - 'plugin' => $query['plugin'], - 'error_code' => $error_code, - 'error_message' => $error_message - )); - } else { - $result = array('installed' => true); - } - } - break; - } - - return $result; - } - - /** - * Preloads the submitted credentials to the global $_POST variable - * - * @param array $query Parameter array containing information for the currently requested action - */ - private function _preload_credentials($query) { - if (!empty($query) && isset($query['filesystem_credentials'])) { - parse_str($query['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. - $_POST[$key] = $value; - } - } - } - } - - /** - * Checks whether we have the required fields submitted and the user has - * the capabilities to execute the requested action - * - * @param array $query The submitted information - * @param array $fields The required fields to check - * @param array $capabilities The capabilities to check and validate - * - * @return array|string - */ - private function _validate_fields_and_capabilities($query, $fields, $capabilities) { - - $error = ''; - if (!empty($fields)) { - for ($i=0; $i_generic_error_response('keyword_required'); - } else { - $error = $this->_generic_error_response('plugin_'.$query[$field].'_required'); - } - break; - } - } - } - - if (empty($error) && !empty($capabilities)) { - for ($i=0; $i_generic_error_response('plugin_insufficient_permission'); - break; - } - } - } - - return $error; - } - - /** - * Activates the plugin - * - * @param array $query Parameter array containing the name of the plugin to activate - * @return array Contains the result of the current process - */ - public function activate_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('activate_plugins')); - if (!empty($error)) { - return $error; - } - - $action = 'activate'; - if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action; - - $result = $this->_apply_plugin_action($action, $query); - if (empty($result['activated'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Deactivates the plugin - * - * @param array $query Parameter array containing the name of the plugin to deactivate - * @return array Contains the result of the current process - */ - public function deactivate_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('activate_plugins')); - if (!empty($error)) { - return $error; - } - - $action = 'deactivate'; - if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action; - - $result = $this->_apply_plugin_action($action, $query); - if (empty($result['deactivated'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Download, install and activates the plugin - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug - * @return array Contains the result of the current process - */ - public function install_activate_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('install_plugins', 'activate_plugins')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - - $result = $this->_apply_plugin_action('install', $query); - if (!empty($result['installed']) && $result['installed']) { - $action = 'activate'; - if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action; - - $result = $this->_apply_plugin_action($action, $query); - if (empty($result['activated'])) { - return $result; - } - } else { - return $result; - } - - return $this->_response($result); - } - - /** - * Download, install the plugin - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug - * @return array Contains the result of the current process - */ - public function install_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('install_plugins')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - - $result = $this->_apply_plugin_action('install', $query); - if (empty($result['installed'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Uninstall/delete the plugin - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug - * @return array Contains the result of the current process - */ - public function delete_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('delete_plugins')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - $info = $this->_get_plugin_info($query); - - if ($info['installed']) { - $deleted = delete_plugins(array($info['plugin_path'])); - - if ($deleted) { - $result = array('deleted' => true); - } else { - $result = $this->_generic_error_response('delete_plugin_failed', array($query['plugin'])); - } - } else { - $result = $this->_generic_error_response('plugin_not_installed', array($query['plugin'])); - } - - return $this->_response($result); - } - - /** - * Updates/upgrade the plugin - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug - * @return array Contains the result of the current process - */ - public function update_plugin($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('update_plugins')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - $info = $this->_get_plugin_info($query); - - // Make sure that we still have the plugin installed before running - // the update process - if ($info['installed']) { - // Load the updates command class if not existed - if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php'); - $update_command = new UpdraftCentral_Updates_Commands($this->rc); - - $result = $update_command->update_plugin($info['plugin_path'], $query['slug']); - if (!empty($result['error'])) { - $result['values'] = array($query['plugin']); - } - } else { - $result = $this->_generic_error_response('plugin_not_installed', array($query['plugin'])); - } - - return $this->_response($result); - } - - /** - * Gets the plugin information along with its active and install status - * - * @internal - * @param array $query Contains either the plugin name or slug or both to be used when retrieving information - * @return array - */ - private function _get_plugin_info($query) { - - $info = array( - 'active' => false, - 'installed' => false - ); - - // Clear plugin cache so that newly installed/downloaded plugins - // gets reflected when calling "get_plugins" - if (function_exists('wp_clean_plugins_cache')) { - wp_clean_plugins_cache(); - } - - // Gets all plugins available. - $get_plugins = get_plugins(); - - // Loops around each plugin available. - foreach ($get_plugins as $key => $value) { - $slug = basename($key, '.php'); - - // If the plugin name matches that of the specified name, it will gather details. - // In case name check isn't enough, we'll use slug to verify if the plugin being queried is actually installed. - // - // Reason for name check failure: - // Due to plugin name inconsistencies - where wordpress.org registered plugin name is different - // from the actual plugin files's metadata (found inside the plugin's PHP file itself). - if ((!empty($query['plugin']) && $value['Name'] === $query['plugin']) || (!empty($query['slug']) && $slug === $query['slug'])) { - $info['installed'] = true; - $info['active'] = is_plugin_active($key); - $info['plugin_path'] = $key; - $info['data'] = $value; - break; - } - } - - return $info; - } - - /** - * Loads all available plugins with additional attributes and settings needed by UpdraftCentral - * - * @param array $query Parameter array Any available parameters needed for this action - * @return array Contains the result of the current process - */ - public function load_plugins($query) { - - $error = $this->_validate_fields_and_capabilities($query, array(), array('install_plugins', 'activate_plugins')); - if (!empty($error)) { - return $error; - } - - $website = get_bloginfo('name'); - $results = array(); - - // Load the updates command class if not existed - if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php'); - $updates = new UpdraftCentral_Updates_Commands($this->rc); - - // Get plugins for update - $plugin_updates = $updates->get_item_updates('plugins'); - - // Get all plugins - $plugins = get_plugins(); - - foreach ($plugins as $key => $value) { - $slug = basename($key, '.php'); - - $plugin = new stdClass(); - $plugin->name = $value['Name']; - $plugin->description = $value['Description']; - $plugin->slug = $slug; - $plugin->version = $value['Version']; - $plugin->author = $value['Author']; - $plugin->status = is_plugin_active($key) ? 'active' : 'inactive'; - $plugin->website = $website; - $plugin->multisite = is_multisite(); - $plugin->site_url = trailingslashit(get_bloginfo('url')); - - if (!empty($plugin_updates[$key])) { - $update_info = $plugin_updates[$key]; - - if (version_compare($update_info->Version, $update_info->update->new_version, '<')) { - if (!empty($update_info->update->new_version)) $plugin->latest_version = $update_info->update->new_version; - if (!empty($update_info->update->package)) $plugin->download_link = $update_info->update->package; - if (!empty($update_info->update->sections)) $plugin->sections = $update_info->update->sections; - } - } - - if (empty($plugin->short_description) && !empty($plugin->description)) { - // Only pull the first sentence as short description, it should be enough rather than displaying - // an empty description or a full blown one which the user can access anytime if they press on - // the view details link in UpdraftCentral. - $temp = explode('.', $plugin->description); - $short_description = $temp[0]; - - // Adding the second sentence wouldn't hurt, in case the first sentence is too short. - if (isset($temp[1])) $short_description .= '.'.$temp[1]; - - $plugin->short_description = $short_description.'.'; - } - - $results[] = $plugin; - } - - $result = array( - 'plugins' => $results - ); - - $result = array_merge($result, $this->_get_backup_credentials_settings(WP_PLUGIN_DIR)); - return $this->_response($result); - } - - /** - * Gets the backup and security credentials settings for this website - * - * @param array $query Parameter array Any available parameters needed for this action - * @return array Contains the result of the current process - */ - public function get_plugin_requirements() { - return $this->_response($this->_get_backup_credentials_settings(WP_PLUGIN_DIR)); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/posts.php b/wordpress/wp-content/plugins/updraftplus/central/modules/posts.php deleted file mode 100644 index 0a82af474c266c90b6c9e21e75045358db6f4731..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/posts.php +++ /dev/null @@ -1,1633 +0,0 @@ -switched = switch_to_blog($blog_id); - } - } - - /** - * Function that gets called after every action - * - * @param string $command a string that corresponds to UDC command to call a certain method for this class. - * @param array $data an array of data post or get fields - * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id - * - * link to udrpc_action main function in class UpdraftCentral_Listener - */ - public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Here, we're restoring to the current (default) blog before we switched - if ($this->switched) restore_current_blog(); - } - - /** - * Returns the keys and fields names that are associated to a particular module type - * - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - private function get_state_fields_by_type($type) { - $state_fields = array( - 'post' => array( - 'validation_fields' => array('publish_posts', 'edit_posts', 'delete_posts'), - 'items_key' => 'posts', - 'count_key' => 'posts_count', - 'list_key' => 'posts', - 'result_key' => 'get', - 'error_key' => 'post_state_change_failed' - ), - 'page' => array( - 'validation_fields' => array('publish_pages', 'edit_pages', 'delete_pages'), - 'items_key' => 'pages', - 'count_key' => 'pages_count', - 'list_key' => 'pages', - 'result_key' => 'get', - 'error_key' => 'page_state_change_failed' - ) - ); - - if (!isset($state_fields[$type])) return array(); - return $state_fields[$type]; - } - - /** - * Fetch and retrieves posts based from the submitted parameters - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function get($params) { - - $state_fields = $this->get_state_fields_by_type($this->post_type); - if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_get_posts'); - - $error = $this->_validate_capabilities($state_fields['validation_fields']); - if (!empty($error)) return $error; - - // check paged parameter; if empty set to defaults - $paged = !empty($params['paged']) ? (int) $params['paged'] : 1; - $numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10; - $offset = ($paged - 1) * $numberposts; - - $args = array( - 'posts_per_page' => $numberposts, - 'paged' => $paged, - 'offset' => $offset, - 'post_type' => $this->post_type, - 'post_status' => 'publish,private,draft,pending,future', - ); - - if (!empty($params['keyword'])) { - $args['s'] = $params['keyword']; - } - - if ('post' == $this->post_type) { - if (!empty($params['category'])) { - $args['cat'] = (int) $params['category']; - } - } - - if (!empty($params['date'])) { - list($monthnum, $year) = explode(':', $params['date']); - - $args['monthnum'] = $monthnum; - $args['year'] = $year; - } - - if (!empty($params['status']) && 'all' !== $params['status']) { - $args['post_status'] = $params['status']; - } - - $query = new WP_Query($args); - $result = $query->posts; - - $count_posts = (int) $query->found_posts; - $page_count = 0; - - if ($count_posts > 0) { - $page_count = absint($count_posts / $numberposts); - $remainder = absint($count_posts % $numberposts); - $page_count = ($remainder > 0) ? ++$page_count : $page_count; - } - - $info = array( - 'page' => $paged, - 'pages' => $page_count, - 'results' => $count_posts, - 'items_from' => (($paged * $numberposts) - $numberposts) + 1, - 'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts, - ); - - $posts = array(); - if (!empty($result)) { - foreach ($result as $post) { - // Pulling any other relevant and additional information regarding - // the post before returning it in the response. - $postdata = $this->get_postdata($post, false); - if (!empty($postdata)) { - array_push($posts, $postdata); - } - } - } - - $response = array( - $state_fields['items_key'] => $posts, - 'options' => $this->get_options($this->post_type), - 'info' => $info, - $state_fields['count_key'] => $this->get_post_status_counts($this->post_type) - ); - - // Load any additional information if preload parameter is set. Will only be - // requested on initial load of items in UpdraftCentral. - if (isset($params['preload']) && $params['preload']) { - $timeout = !empty($params['timeout']) ? $params['timeout'] : 30; - $response = array_merge($response, $this->get_preload_data($timeout, $this->post_type)); - } - - return $this->_response($response); - } - - /** - * Extracts public properties from complex object and return a simple - * object (stdClass) that contains the public properties of the original object. - * - * @param object $obj Any type of complex objects that needs converting (e.g. WP_Taxonomy, WP_Term or WP_User) - * @return stdClass - */ - protected function trim_object($obj) { - // To preserve the object's accessibility through its properties we recreate - // the object using the stdClass and fill it with the public properties - // that will be extracted from the original object ($obj). - $newObj = new stdClass(); - - if (is_object($obj)) { - // Making sure that we only extract those publicly accessible properties excluding - // the private, protected, static ones and methods. - $props = get_object_vars($obj); - if (!empty($props)) { - foreach ($props as $key => $value) { - $newObj->{$key} = $value; - } - } - } - - return $newObj; - } - - /** - * Retrieves information that will be preloaded in UC for quick and easy access - * when editing a certain page or post - * - * @param int $timeout The user-defined timeout from UpdraftCentral - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - protected function get_preload_data($timeout, $type = 'post') { - global $updraftplus; - - if (!function_exists('get_page_templates')) { - require_once(ABSPATH.'wp-admin/includes/theme.php'); - } - - $templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates(); - if (!empty($templates)) { - $templates = array_flip($templates); - if (!isset($templates['default'])) { - $templates['default'] = __('Default template', 'updraftplus'); - } - } - - // Preloading elements saves time and avoid unnecessary round trips to fetch - // these information individually. - $authors = $this->get_authors(); - $parent_pages = $this->get_parent_pages(); - - $data = array( - 'authors' => $authors['data']['authors'], - 'parent_pages' => $parent_pages['data']['pages'], - 'templates' => $templates, - 'editor_styles' => $this->get_editor_styles($timeout), - 'wp_version' => $updraftplus->get_wordpress_version() - ); - - if ('post' == $type) { - $categories = $this->get_categories(); - $tags = $this->get_tags(); - - $data['taxonomies'] = $this->get_taxonomies(); - $data['categories'] = $categories['data']; - $data['tags'] = $tags['data']; - } - - return array( - 'preloaded' => json_encode($data) - ); - } - - /** - * Extract content from the given css path - * - * @param string $style CSS file path - * @param int $timeout The user-defined timeout from UpdraftCentral - * @return string - */ - protected function extract_css_content($style, $timeout) { - - $content = ''; - if (1 === preg_match('~^(https?:)?//~i', $style)) { - $response = wp_remote_get($style, array('timeout' => $timeout)); - if (!is_wp_error($response)) { - $result = trim(wp_remote_retrieve_body($response)); - if (!empty($result)) $content = $result; - } - } else { - // Editor styles that resides in "css/dist" - if (false !== ($pos = stripos($style, 'css/dist'))) { - $file = ABSPATH.WPINC.substr_replace($style, '/', 0, $pos); - } else { - // Styles that resides in "wp-content/themes" (coming from $editor_styles global var) - $file = get_theme_file_path($style); - } - - $is_valid = (function_exists('is_file')) ? is_file($file) : file_exists($file); - if ($is_valid) { - $result = trim(file_get_contents($file)); - if (!empty($result)) $content = $result; - } - } - - return $this->filter_url($content); - } - - /** - * Convert URL entries contained in the CSS content to absolute URLs - * - * @param string $content The content of the CSS file - * @return string - */ - protected function filter_url($content) { - - // Replace with valid URL (absolute) - preg_match_all('~url\((.+?)\)~i', $content, $all_matches); - if (!empty($all_matches) && isset($all_matches[1])) { - $urls = array_unique($all_matches[1]); - foreach ($urls as $url) { - $url = str_replace('"', '', $url); - if (false !== strpos($url, 'data:')) continue; - - if (1 !== preg_match('~^(https?:)?//~i', $url)) { - if (1 === preg_match('~(plugins|themes)~i', $url, $matches)) { - if (false !== ($pos = stripos($url, $matches[1]))) { - if (!function_exists('content_url')) { - require_once ABSPATH.WPINC.'/link-template.php'; - } - - $absolute_url = rtrim(content_url(), '/').substr_replace($url, '/', 0, $pos); - $content = str_replace($url, $absolute_url, $content); - } - } else { - $path = preg_replace('~(\.+\/)~', '', $url); - $dirpath = trailingslashit(get_stylesheet_directory()); - if (!file_exists($dirpath.$url)) $path = $this->resolve_path($path); - - $absolute_url = (!empty($path)) ? trailingslashit(get_stylesheet_directory_uri()).ltrim($path, '/') : ''; - $content = str_replace($url, $absolute_url, $content); - } - } - } - } - - return $content; - } - - /** - * Resolve URL to its actual absolute path - * - * @param string $path Some relative path to check - * @return string - */ - protected function resolve_path($path) { - $dir = trailingslashit(get_stylesheet_directory()); - // Some relative paths declared within the css file (e.g. only has '../fonts/etc/', called deep down from a subfolder) where parent - // subfolder is not articulated needs to be resolve further to get its actual absolute path. Using glob will pinpoint its actual location - // rather than iterating through a series of sublevels just to find the actual file. - $result = str_replace($dir, '', glob($dir.'{,*/}{'.$path.'}', GLOB_BRACE)); - - if (!empty($result)) return $result[0]; - return false; - } - - /** - * Retrieve the editor styles/assets to be use by UpdraftCentral when editing a post - * - * @param int $timeout The user-defined timeout from UpdraftCentral - * @return array() - */ - protected function get_editor_styles($timeout) { - global $editor_styles, $wp_styles; - $editing_styles = $loaded = array(); - - $required = array('css/dist/editor/style.css', 'css/dist/block-library/style.css', 'css/dist/block-library/theme.css'); - foreach ($required as $style) { - $editing_styles[] = array('css' => $this->extract_css_content($style, $timeout), 'inline' => ''); - }; - - do_action('enqueue_block_editor_assets'); - do_action('enqueue_block_assets'); - - // Checking for editor styles support since styles make vary from theme to theme - if ($editor_styles) { - foreach ($editor_styles as $style) { - if (false !== array_search($style, $loaded)) continue; - - $editing_styles[] = array('css' => $this->extract_css_content($style, $timeout), 'inline' => ''); - $loaded[] = $style; - } - } - - if ($wp_styles) { - foreach ($wp_styles->queue as $handle) { - $style = $wp_styles->registered[$handle]->src; - if (false !== array_search($style, $loaded)) continue; - - $inline = $wp_styles->print_inline_style($handle, false); - $editing_styles[] = array( - 'css' => $this->extract_css_content($style, $timeout), - 'inline' => (!$inline) ? '' : $inline - ); - $loaded[] = $style; - } - } - - $editing_styles[] = array('css' => $this->extract_css_content('/style.css', $timeout), 'inline' => ''); - return $editing_styles; - } - - /** - * Retrieves the total number of items found under each post statuses - * - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - protected function get_post_status_counts($type = 'post') { - $posts = wp_count_posts($type); - - $publish = (int) $posts->publish; - $private = (int) $posts->private; - $draft = (int) $posts->draft; - $pending = (int) $posts->pending; - $future = (int) $posts->future; - $trash = (int) $posts->trash; - - // We exclude "trash" from the overall total as WP doesn't actually - // consider or include it in the total count. - $all = $publish + $private + $draft + $pending + $future; - - return array( - 'all' => $all, - 'publish' => $publish, - 'private' => $private, - 'draft' => $draft, - 'pending' => $pending, - 'future' => $future, - 'trash' => $trash, - ); - } - - /** - * Retrieves a collection of formatted dates found for the given post statuses. - * It will be used as options for the date filter when managing the posts in UpdraftCentral. - * - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - protected function get_date_options($type = 'post') { - global $wpdb; - - $date_options = $wpdb->get_col("SELECT DATE_FORMAT(`post_date`, '%M %Y') as `formatted_post_date` FROM {$wpdb->posts} WHERE `post_type` = '{$type}' AND `post_status` IN ('publish', 'private', 'draft', 'pending', 'future') GROUP BY `formatted_post_date` ORDER BY `post_date` DESC"); - - return $date_options; - } - - /** - * Make sure that we have the required fields to use in UpdraftCentral for - * displaying the categories and tags sections. Add if missing. - * - * @param object $item Taxonomy item to check - * @return object - */ - protected function map_tax($item) { - $taxs = array('category' => 'categories', 'post_tag' => 'tags'); - if (array_key_exists($item->name, $taxs)) { - if (!isset($item->show_in_rest)) $item->show_in_rest = true; - if (!isset($item->rest_base)) $item->rest_base = $taxs[$item->name]; - } - - return $item; - } - - /** - * Fetch and retrieves available taxonomies for this site and some capabilities specific - * to tags and categories when managing them. - * - * @return array - */ - protected function get_taxonomies() { - $taxonomies = get_taxonomies(array(), 'objects'); - $taxonomies = array_map(array($this, 'map_tax'), $taxonomies); - - $response = array( - 'taxonomies' => $taxonomies, - 'current_user_cap' => array( - 'manage_categories' => current_user_can('manage_categories'), - 'edit_categories' => current_user_can('edit_categories'), - 'delete_categories' => current_user_can('delete_categories'), - 'assign_categories' => current_user_can('assign_categories'), - 'manage_post_tags' => current_user_can('manage_post_tags'), - 'edit_post_tags' => current_user_can('edit_post_tags'), - 'delete_post_tags' => current_user_can('delete_post_tags'), - 'assign_post_tags' => current_user_can('assign_post_tags'), - ) - ); - - return $response; - } - - /** - * Fetch and retrieves categories based from the submitted parameters - * - * @param array $query Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_categories($query = array()) { - $page = !empty($query['page']) ? (int) $query['page'] : 1; - $items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100; - $offset = ($page - 1) * $items_per_page; - $order = !empty($query['order']) ? $query['order'] : 'asc'; - $orderby = !empty($query['orderby']) ? $query['orderby'] : 'name'; - - $args = array( - 'hide_empty' => false, - 'orderby' => $orderby, - 'order' => $order, - 'number' => $items_per_page, - 'offset' => $offset - ); - - $categories = get_categories($args); - $category_options = array(); - - if (!empty($categories)) { - foreach ($categories as $key => $term) { - $parent_term = get_term((int) $term->parent, $term->taxonomy); - if (!is_wp_error($parent_term) && !is_null($parent_term)) { - $parent_term = json_encode($this->trim_object($parent_term)); - } else { - $parent_term = ''; - } - - $category_options[] = array( - 'id' => $term->term_id, - 'name' => $term->name, - 'parent' => $term->parent - ); - - $categories[$key] = array( - 'term' => json_encode($this->trim_object($term)), - 'misc' => array( - 'link' => get_term_link($term), - 'parent_term' => $parent_term, - 'taxonomy' => $term->taxonomy - ) - ); - } - } - - $categorytax = get_taxonomy('category'); - $parent_dropdown_args = array( - 'taxonomy' => 'category', - 'hide_empty' => 0, - 'name' => 'newcategory_parent', - 'orderby' => 'name', - 'hierarchical' => 1, - 'show_option_none' => '— '.$categorytax->labels->parent_item.' —', - 'echo' => false - ); - - $parent_dropdown_args = apply_filters('post_edit_category_parent_dropdown_args', $parent_dropdown_args); - $parent_dropdown = wp_dropdown_categories($parent_dropdown_args); - - if (!function_exists('wp_popular_terms_checklist')) { - require_once ABSPATH . 'wp-admin/includes/template.php'; - } - - ob_start(); - wp_popular_terms_checklist('category'); - $popular_terms_checklist = ob_get_contents(); - ob_end_clean(); - - return $this->_response(array( - 'terms' => $categories, - 'misc' => array( - 'formatted' => $category_options, - 'raw' => $categories, - 'tax' => json_encode($this->trim_object($categorytax)), - 'popular' => $popular_terms_checklist, - 'parent_dropdown' => $parent_dropdown, - 'capabilities' => array( - 'can_edit_terms' => current_user_can($categorytax->cap->edit_terms) - ) - ) - )); - } - - /** - * Fetch and retrieves tags based from the submitted parameters - * - * @param array $query Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_tags($query = array()) { - $page = !empty($query['page']) ? (int) $query['page'] : 1; - $items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100; - $offset = ($page - 1) * $items_per_page; - $order = !empty($query['order']) ? $query['order'] : 'desc'; - $orderby = !empty($query['orderby']) ? $query['orderby'] : 'count'; - - $args = array( - 'hide_empty' => false, - 'orderby' => $orderby, - 'order' => $order, - 'number' => $items_per_page, - 'offset' => $offset - ); - - $tags = get_tags($args); - $tag_options = array(); - $tag_cloud = ''; - - if (!empty($tags)) { - $tags_for_cloud = array(); - foreach ($tags as $key => $term) { - if (!isset($term->link)) $term->link = get_tag_link($term->term_id); - array_push($tags_for_cloud, $term); - - $parent_term = get_term((int) $term->parent, $term->taxonomy); - if (!is_wp_error($parent_term) && !is_null($parent_term)) { - $parent_term = json_encode($this->trim_object($parent_term)); - } else { - $parent_term = ''; - } - - $tag_options[] = array( - 'id' => $term->term_id, - 'name' => $term->name, - ); - - $tags[$key] = array( - 'term' => json_encode($this->trim_object($term)), - 'misc' => array( - 'link' => get_term_link($term), - 'parent_term' => $parent_term, - 'taxonomy' => $term->taxonomy - ) - ); - } - - if (!function_exists('wp_generate_tag_cloud')) { - require_once ABSPATH.WPINC.'/category-template.php'; - } - - $tag_cloud = wp_generate_tag_cloud($tags_for_cloud, array( - 'smallest' => 10, - 'largest' => 22, - 'unit' => 'pt', - 'number' => 10, - 'format' => 'flat', - 'separator' => " ", - 'orderby' => 'count', - 'order' => 'DESC', - 'show_count' => 1, - 'echo' => false - )); - } - - $tagtax = get_taxonomy('post_tag'); - return $this->_response(array( - 'terms' => $tags, - 'misc' => array( - 'formatted' => $tag_options, - 'raw' => $tags, - 'tax' => json_encode($this->trim_object($tagtax)), - 'tag_cloud' => $tag_cloud, - 'capabilities' => array( - 'can_assign_terms' => current_user_can($tagtax->cap->assign_terms) - ) - ) - )); - } - - /** - * Fetch all available taxonomies and terms information for the given post object - * - * @param array $post The "Post" object to use when retrieving the information - * @return array - */ - protected function get_taxonomies_terms($post) { - $taxonomies = get_object_taxonomies($post->post_type, 'objects'); - $taxonomies = array_map(array($this, 'map_tax'), $taxonomies); - - $taxonomy_names = array(); - $taxonomy_terms = array(); - $taxonomy_caps = array(); - - foreach ($taxonomies as $taxonomy) { - $terms = get_the_terms($post->ID, $taxonomy->name); - $terms = !is_array($terms) ? (array) $terms : $terms; - - $taxonomy_terms[$taxonomy->name] = $terms; - $taxonomy_caps[$taxonomy->name] = array( - 'hierarchical' => is_taxonomy_hierarchical($taxonomy->name), - 'edit_terms' => current_user_can($taxonomy->cap->edit_terms), - 'assign_terms' => current_user_can($taxonomy->cap->assign_terms), - ); - array_push($taxonomy_names, $taxonomy->name); - } - - return array( - 'objects' => $taxonomies, - 'names' => $taxonomy_names, - 'terms' => $taxonomy_terms, - 'caps' => $taxonomy_caps, - ); - } - - /** - * Retrieves the underlying data for the given post. Some extra information are - * passed along that will be consumed by the editor in UpdraftCentral - * - * @param int|object $param Post object or a post ID - * @param boolean $encode True to encode the post object, false otherwise - * @return array - */ - public function get_postdata($param, $encode = true) { - $response = array(); - - if (is_object($param) && isset($param->ID)) { - $post = $param; - } elseif (is_numeric($param)) { - $post = get_post($param); - } - - if ($post) { - $post_type_obj = get_post_type_object($post->post_type); - - $is_post_type_viewable = false; - if (!empty($post_type_obj)) { - $is_post_type_viewable = $post_type_obj->publicly_queryable || ($post_type_obj->_builtin && $post_type_obj->public); - } - - if (!function_exists('get_sample_permalink')) { - require_once ABSPATH.'wp-admin/includes/post.php'; - } - - // Validate template exists on the current theme, otherwise, - // reset the template to default. - $template = get_page_template_slug($post->ID); - if (!empty($template)) { - $page_templates = wp_get_theme()->get_page_templates($post); - if ('default' != $template && !isset($page_templates[$template])) { - update_post_meta($post->ID, '_wp_page_template', 'default'); - } - } - - $published_date = array( - 'jj' => date('d', strtotime($post->post_date)), - 'mm' => date('m', strtotime($post->post_date)), - 'aa' => date('Y', strtotime($post->post_date)), - 'hh' => date('H', strtotime($post->post_date)), - 'mn' => date('i', strtotime($post->post_date)), - 'ss' => date('s', strtotime($post->post_date)) - ); - - $sample_permalink = get_sample_permalink($post->ID, $post->post_title, ''); - $permalink = get_permalink($post->ID); - $slug = $post->post_name; - - if (!empty($sample_permalink) && !empty($slug)) { - if (isset($sample_permalink[0])) { - if (false !== stripos($sample_permalink[0], '%pagename%/') || false !== stripos($sample_permalink[0], '%postname%/')) { - $token = (false !== stripos($sample_permalink[0], '%pagename%/')) ? '%pagename%/' : '%postname%/'; - $permalink = str_replace($token, '', $sample_permalink[0]).$slug; - } - } - } - - $response = array( - 'post' => $encode ? json_encode($post) : $post, - 'misc' => array( - 'guid_rendered' => apply_filters('get_the_guid', $post->guid, $post->ID), - 'link' => $permalink, - 'slug' => $slug, - 'site_url' => site_url('/'), - 'title_rendered' => get_the_title($post->ID), - 'content_rendered' => apply_filters('the_content', $post->post_content), - 'excerpt' => $post->post_excerpt, - 'featured_media' => 0, - 'sticky' => is_sticky($post->ID), - 'template' => get_page_template_slug($post->ID), - 'permalink_template' => get_permalink($post->ID, true), - 'author_name' => get_the_author_meta('display_name', $post->post_author), - 'publish_month_year' => date('F Y', strtotime($post->post_date)), - 'published_date' => $published_date, - 'format' => get_post_format($post->ID), - 'post_type_name' => $post_type_obj->name, - 'post_type_viewable' => $is_post_type_viewable, - 'post_type_public' => $post_type_obj->public, - 'post_type_hierarchical' => $post_type_obj->hierarchical, - 'sample_permalink' => get_sample_permalink($post->ID, $post->post_title, ''), - 'post_password_required' => post_password_required($post), - 'post_type_supports_authors' => post_type_supports($post->post_type, 'author'), - 'post_type_supports_comments' => post_type_supports($post->post_type, 'comments'), - 'post_type_supports_revisions' => post_type_supports($post->post_type, 'revisions'), - 'post_revisions' => array(), // N.B. We're not going to allow revisions editing for now - 'post_thumbnail_id' => get_post_thumbnail_id($post->ID), - 'can_publish_posts' => current_user_can($post_type_obj->cap->publish_posts), - 'can_edit_others_posts' => current_user_can($post_type_obj->cap->edit_others_posts), - 'can_unfiltered_html' => current_user_can('unfiltered_html') - ) - ); - - if ('post' == $post->post_type) { - $taxonomies = $this->get_taxonomies_terms($post); - $response['misc']['taxonomy_objects'] = $taxonomies['objects']; - $response['misc']['taxonomy_names'] = $taxonomies['names']; - $response['misc']['taxonomy_terms'] = $taxonomies['terms']; - $response['misc']['taxonomy_caps'] = $taxonomies['caps']; - - if (!function_exists('wp_popular_terms_checklist') || !function_exists('get_terms_to_edit')) { - require_once ABSPATH . 'wp-admin/includes/template.php'; - require_once ABSPATH . 'wp-admin/includes/taxonomy.php'; - } - - if (!function_exists('wp_get_post_categories')) { - require_once(ABSPATH.WPINC.'/post.php'); - } - - $categories = wp_get_post_categories($post->ID, array('fields' => 'ids')); - if (!is_wp_error($categories)) { - $response['misc']['categories'] = empty($categories) ? array() : $categories; - $terms_to_edit = get_terms_to_edit($post->ID, 'category'); - if (!empty($terms_to_edit)) { - $response['misc']['categories_list'] = str_replace(',', ', ', $terms_to_edit); - } - - $popular_ids = wp_popular_terms_checklist('category', 0, 10, false); - // On WP 3.4 the "wp_terms_checklist" doesn't have an "echo" parameter and will automatically - // display the rendered checklist. Therefore, we're going to pull the terms so that all - // versions starting from WP 3.4 will pull the content instead of displaying them. - - ob_start(); - // In this call we'll have to set the "echo" parameter to true so that later version of WP - // will be able to catch and process it. - wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'popular_cats' => $popular_ids, 'echo' => true)); - $popular_checklist = ob_get_contents(); - ob_end_clean(); - - $response['misc']['categories_checklist'] = $popular_checklist; - - ob_start(); - wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'checked_ontop' => 0, 'echo' => true)); - $quickedit_checklist = ob_get_contents(); - ob_end_clean(); - - $response['misc']['categories_quickedit_checklist'] = $quickedit_checklist; - } - - $tags = wp_get_post_tags($post->ID, array('fields' => 'ids')); - if (!is_wp_error($tags)) { - $response['misc']['tags'] = empty($tags) ? array() : $tags; - $terms_to_edit = get_terms_to_edit($post->ID, 'post_tag'); - if (!empty($terms_to_edit)) { - $response['misc']['tags_list'] = str_replace(',', ', ', $terms_to_edit); - } - } - } - - // Naturally, the "featured_media" will suffice when loading the image (media) in - // UpdraftCentral since the value in this field is the actual image id of the featured - // media used in UC. If we currently don't have an entry in the "featured_media_updraftcentral" meta, - // then UC will need to download the featured media (image) for this current post/page - // using the "featured_media_url" field (below) if not empty. - $featured_media = get_post_meta($post->ID, 'featured_media_updraftcentral', true); - if (!empty($featured_media)) { - $response['misc']['featured_media'] = $featured_media; - } - - // Retrieve featured media if currently present for the given post/page. - // If present, we pull the image (media) URL in case there's a need for - // UpdraftCentral to download the image upon loading the editor (e.g. the featured_media id - // above no longer exists). - $media_id = (int) get_post_thumbnail_id($post->ID); - if (!empty($media_id)) { - $response['misc']['featured_media_url'] = wp_get_attachment_url($media_id); - } else { - // The post/page no longer has a "featured_media" or doesn't have one currently, therefore, - // we're going to set the "featured_media" and "featured_media_url" fields to both empty to - // to avoid any further actions (e.g. download media). - $response['misc']['featured_media'] = 0; - $response['misc']['featured_media_url'] = ''; - } - } - - return $response; - } - - /** - * Changes the state/status of the submitted post(s) - * - * @param array $params An array of data that serves as parameters for the given request - * @return array - */ - public function set_state($params) { - - $state_fields = $this->get_state_fields_by_type($this->post_type); - if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_set_state'); - - $error = $this->_validate_capabilities($state_fields['validation_fields']); - if (!empty($error)) return $error; - - $result = array(); - if (!empty($params['list'])) { - $posts = array(); - foreach ($params['list'] as $id) { - $post = $this->apply_state($id, $params['action'], $this->post_type); - if (!empty($post)) { - array_push($posts, $post); - } - } - - if (!empty($posts)) { - $result = array($state_fields['list_key'] => $posts); - } - } elseif (!empty($params['id'])) { - $post = $this->apply_state($params['id'], $params['action'], $this->post_type); - if (!empty($post)) $result = $post; - } - - if (!empty($result)) { - $response = $this->get($params); - if (!empty($response['response']) && 'rpcok' === $response['response']) { - $result[$state_fields['result_key']] = $response['data']; - } - - return $this->_response($result); - } else { - return $this->_generic_error_response($state_fields['error_key'], array('action' => $params['action'])); - } - } - - /** - * Creates new category - * - * @param array $params An array of data that serves as parameters for the given request - * @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true. - * @return array - */ - public function add_category($params, $wrap_response = true) { - $error = $this->_validate_capabilities(array('manage_categories')); - if (!empty($error)) return $error; - - $name = sanitize_text_field($params['name']); - $args = array(); - if (!empty($params['parent'])) { - $args['parent'] = $params['parent']; - } - - $result = wp_insert_term($name, 'category', $args); - if (!is_wp_error($result)) { - $term_id = $result['term_id']; - $term = get_term($term_id, 'category'); - - $data = array(); - if (!is_wp_error($term)) { - $data = array( - 'id' => $term->term_id, - 'count' => $term->count, - 'description' => $term->description, - 'link' => get_term_link($term->term_id, 'category'), - 'name' => $term->name, - 'slug' => $term->slug, - 'taxonomy' => $term->taxonomy, - 'parent' => $term->parent, - 'meta' => array() - ); - - $categories = $this->get_categories(); - if ($wrap_response) $data['categories'] = json_encode($categories['data']); - } - - return $wrap_response ? $this->_response($data) : $data; - } else { - $error = array( - 'message' => __($result->get_error_message(), 'updraftplus') - ); - - return $wrap_response ? $this->_generic_error_response('post_add_category_failed', $error) : $error; - } - } - - /** - * Assigns categories to a certain post object - * - * @param int $post_id The ID of the post object - * @param array $category_ids A collection of category IDs to assign to the post object - * @return void - */ - protected function assign_category_to_post($post_id, $category_ids) { - if (!empty($category_ids)) { - // Making sure that we have the correct type to use and we - // don't have any redundant IDs before saving. - $category_ids = array_unique(array_map('intval', $category_ids)); - - // Attach (new) categories to post - wp_set_object_terms($post_id, $category_ids, 'category'); - } else { - wp_set_object_terms($post_id, get_option('default_category'), 'category'); - } - } - - /** - * Creates new tag - * - * @param array $params An array of data that serves as parameters for the given request - * @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true. - * @return array - */ - public function add_tag($params, $wrap_response = true) { - // N.B. Since the "manage_post_tags" capability does not exist in WP 3.4. We'll use the "manage_categories" instead. Besides, the "manage_post_tags" along with the other tag-related capabilities in the latest versions are actually mapped to the "manage_categories" capability (refer to wp-includes/capabilities.php under the "map_meta_cap" function). - $error = $this->_validate_capabilities(array('manage_categories')); - if (!empty($error)) return $error; - - $name = sanitize_text_field($params['name']); - $result = wp_insert_term($name, 'post_tag'); - if (!is_wp_error($result)) { - $term_id = $result['term_id']; - $term = get_term($term_id, 'post_tag'); - - $data = array(); - if (!is_wp_error($term)) { - $data = array( - 'id' => $term->term_id, - 'count' => $term->count, - 'description' => $term->description, - 'link' => get_term_link($term->term_id, 'post_tag'), - 'name' => $term->name, - 'slug' => $term->slug, - 'taxonomy' => $term->taxonomy, - 'meta' => array() - ); - - $tags = $this->get_tags(); - if ($wrap_response) $data['tags'] = json_encode($tags['data']); - } - - return $wrap_response ? $this->_response($data) : $data; - } else { - $error = array( - 'message' => __($result->get_error_message(), 'updraftplus') - ); - - return $wrap_response ? $this->_generic_error_response('post_add_tag_failed', $error) : $error; - } - } - - /** - * Assigns tags to a certain post object - * - * @param int $post_id The ID of the post object - * @param array $tag_ids A collection of tag IDs to assign to the post object - * @return void - */ - protected function assign_tag_to_post($post_id, $tag_ids) { - if (!empty($tag_ids)) { - // Making sure that we have the correct type to use and we - // don't have any redundant IDs before saving. - $tag_ids = array_unique(array_map('intval', $tag_ids)); - - // Attach (new) tags to post - wp_set_object_terms($post_id, $tag_ids, 'post_tag'); - } else { - wp_set_object_terms($post_id, null, 'post_tag'); - } - } - - /** - * Saves or updates post/page information based from the submitted data - * - * @param array $params An array of data that serves as parameters for the given request - * @return array - */ - public function save($params) { - - $validation_fields = array( - 'post' => array('publish_posts', 'edit_posts', 'delete_posts'), - 'page' => array('publish_pages', 'edit_pages', 'delete_pages') - ); - - if (!isset($validation_fields[$this->post_type])) return $this->_generic_error_response('unsupported_type_on_save_post'); - - $error = $this->_validate_capabilities($validation_fields[$this->post_type]); - if (!empty($error)) return $error; - - if (!empty($params['id']) || !empty($params['new'])) { - $args = array(); - - // post_content - if (!empty($params['content'])) - $args['post_content'] = $params['content']; - - // post_excerpt - if (!empty($params['excerpt'])) - $args['post_excerpt'] = $params['excerpt']; - - // menu_order - if (isset($params['order'])) - $args['menu_order'] = (int) $params['order']; - - // post_parent - if (isset($params['parent'])) { - $args['post_parent'] = empty($params['parent']) ? 0 : $params['parent']; - } - - // post_name - if (!empty($params['slug'])) - $args['post_name'] = $params['slug']; - - // post_status - if (!empty($params['status'])) { - $args['post_status'] = $params['status']; - } - - // post_title - if (!empty($params['title'])) - $args['post_title'] = $params['title']; - - // post_author - if (!empty($params['author'])) - $args['post_author'] = $params['author']; - - // comment_status - if (!empty($params['comment_status'])) - $args['comment_status'] = $params['comment_status']; - - // ping_status - if (!empty($params['ping_status'])) - $args['ping_status'] = $params['ping_status']; - - // visibility - if (!empty($params['visibility'])) { - switch ($params['visibility']) { - case 'public': - $args['post_status'] = 'publish'; - $args['post_password'] = ''; - break; - case 'password': - $args['post_status'] = 'publish'; - $args['post_password'] = $params['password']; - break; - case 'private': - $args['post_status'] = 'private'; - $args['post_password'] = ''; - break; - default: - break; - } - } - - // post/publish date - if (!empty($params['date'])) { - $datetime = strtotime($params['date']); - $post_date = date('Y-m-d H:i:s', $datetime); - - $args['post_date'] = $post_date; - $args['post_date_gmt'] = gmdate('Y-m-d H:i:s', $datetime); - - // We only change the status to "future" based from the submitted date if the post status - // is not empty and equal to 'publish' and the date is for the coming future. - if (!empty($params['status']) && 'publish' == $params['status']) { - if (strtotime($post_date) > strtotime(date('Y-m-d H:i:s'))) $args['post_status'] = 'future'; - } - } - - // Make sure we have a slug/post_name generated before insert/update - if (empty($params['slug']) && !empty($params['title'])) { - $args['post_name'] = sanitize_title_with_dashes($params['title']); - } - - if (!empty($params['new'])) { - $args['post_type'] = $this->post_type; - $post_id = wp_insert_post($args, true); - } else { - $args['ID'] = $params['id']; - $args['post_modified'] = date('Y-m-d H:i:s'); - $args['post_modified_gmt'] = gmdate('Y-m-d H:i:s'); - - $post_id = wp_update_post($args, true); - } - - // We have successfully created/updated a post at this point, thus, we'll continue - // with implementing the other requested processes and return the result. - if (!is_wp_error($post_id)) { - // sticky post - if (isset($params['sticky'])) { - $sticky = (bool) $params['sticky']; - if ($sticky) { - stick_post($post_id); - } else { - if (is_sticky($post_id)) { - unstick_post($post_id); - } - } - } - - // template - if (!empty($params['template'])) { - update_post_meta($post_id, '_wp_page_template', $params['template']); - } - - // featured_media - if (isset($params['featured_media'])) { - if (!empty($params['featured_media'])) { - $featured_media = (int) $params['featured_media']; - $attach_continue = true; - - $url = wp_get_attachment_url($featured_media); - if (!empty($url) && !empty($params['featured_media_url']) && $url == $params['featured_media_url']) { - set_post_thumbnail($post_id, $featured_media); - update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']); - $attach_continue = false; - } - - if ($attach_continue) { - $featured_media_data = !empty($params['featured_media_data']) ? $params['featured_media_data'] : null; - $media_id = $this->attach_remote_image($params['featured_media_url'], $featured_media_data, $post_id); - if (!empty($media_id)) { - // If we have a successful attachment then add reference to UC's media id - update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']); - } - } - } else { - // Remove featured image. - delete_post_meta($post_id, '_thumbnail_id'); - delete_post_meta($post_id, 'featured_media_updraftcentral'); - } - } - - // categories - $categories_updated = false; - if (!empty($params['categories'])) { - $term_ids = array(); - foreach ($params['categories'] as $value) { - $category = sanitize_text_field($value); - $parent = 0; - - if (false !== strpos($category, ':')) { - list($parent, $category) = explode(':', $category); - $result = $this->add_category(array('name' => $category, 'parent' => $parent), false); - - if (!empty($result)) { - array_push($term_ids, $result['id']); - } - } else { - $term = get_term_by('id', $category, 'category'); - if (!empty($term)) { - $term_id = $term->term_id; - array_push($term_ids, $term_id); - } - } - } - - $this->assign_category_to_post($post_id, $term_ids); - $categories_updated = true; - } - - // tags - $tags_updated = false; - if (!empty($params['tags'])) { - $term_ids = array(); - foreach ($params['tags'] as $value) { - $tag = sanitize_text_field($value); - $field = is_numeric($tag) ? 'id' : 'name'; - - $term = get_term_by($field, $tag, 'post_tag'); - if (!empty($term)) { - $term_id = $term->term_id; - array_push($term_ids, $term_id); - } else { - $result = $this->add_tag(array('name' => $tag), false); - if (!empty($result)) { - array_push($term_ids, $result['id']); - } - } - } - - $this->assign_tag_to_post($post_id, $term_ids); - $tags_updated = true; - } - - // Pulling any other relevant and additional information regarding - // the post before returning it in the response. - $postdata = $this->get_postdata($post_id); - - if (!empty($params['new'])) { - $timeout = !empty($params['timeout']) ? $params['timeout'] : 30; - $postdata = array_merge($postdata, $this->get_preload_data($timeout, $this->post_type)); - } else { - if ($categories_updated || $tags_updated) { - $categories = $this->get_categories(); - $tags = $this->get_tags(); - - $postdata['preloaded'] = json_encode(array( - 'categories' => $categories['data'], - 'tags' => $tags['data'] - )); - } - } - - $postdata['options'] = $this->get_options($this->post_type); - return $this->_response($postdata); - } else { - // ERROR: error creating or updating post - return $this->_generic_error_response('post_save_failed', array( - 'message' => $post_id->get_error_message(), - 'args' => $args - )); - } - } else { - // ERROR: no id parameter, invalid request - return $this->_generic_error_response('post_invalid_request', array('message' => __('Expected parameter(s) missing.', 'updraftplus'))); - } - } - - /** - * Fetch and retrieves authors based from the submitted parameters - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_authors($params = array()) { - // If expected parameters are empty or does not exists then set them to some default values - $page = !empty($params['page']) ? (int) $params['page'] : 1; - $per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 15; - $offset = ($page - 1) * $per_page; - $who = !empty($params['who']) ? $params['who'] : 'authors'; - $order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC'; - $orderby = !empty($params['orderby']) ? $params['orderby'] : 'display_name'; - - $users = get_users(array( - 'number' => $per_page, - 'paged' => $page, - 'offset' => $offset, - 'who' => $who, - 'order' => $order, - 'orderby' => $orderby, - )); - - $authors = array(); - $locale = get_locale(); - - foreach ($users as $user) { - $data = array( - 'user' => json_encode($this->trim_object($user)), - 'misc' => array( - 'link' => get_author_posts_url($user->ID, $user->user_nicename), - 'locale' => function_exists('get_user_locale') ? get_user_locale($user) : $locale, - 'registered_date' => date('c', strtotime($user->user_registered)), - ) - ); - - array_push($authors, $data); - } - - return $this->_response(array( - 'authors' => $authors - )); - } - - /** - * Fetch and retrieves parent pages based from the submitted parameters - * - * @param array $params Containing all the needed information to filter the results of the current request - * @return array - */ - public function get_parent_pages($params = array()) { - // If expected parameters are empty or does not exists then set them to some default values - $page = !empty($params['page']) ? (int) $params['page'] : 1; - $per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 100; - $offset = ($page - 1) * $per_page; - $exclude = !empty($params['exclude']) ? $params['exclude'] : array(); - $order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC'; - $orderby = !empty($params['orderby']) ? $params['orderby'] : 'menu_order'; - $status = !empty($params['status']) ? $params['status'] : 'publish'; - - $args = array( - 'posts_per_page' => $per_page, - 'paged' => $page, - 'offset' => $offset, - 'post__not_in' => $exclude, - 'order' => $order, - 'orderby' => $orderby, - 'post_type' => 'page', - 'post_status' => $status, - ); - - $query = new WP_Query($args); - $posts = $query->posts; - - $pages = array(); - if (!empty($posts)) { - foreach ($posts as $post) { - // Get additional information and merge with the response - $postdata = $this->get_postdata($post, true); - if (!empty($postdata)) array_push($pages, $this->trim_parent_info($postdata)); - } - } - - return $this->_response(array( - 'pages' => $pages - )); - } - - /** - * Trim down return data for parent pages - * - * @param array $postdata The array containing the data to process - * @return array - */ - protected function trim_parent_info($postdata) { - - if (isset($postdata['post'])) { - $post = json_decode($postdata['post']); - - $page = new stdClass(); - $page->ID = $post->ID; - $page->post_title = $post->post_title; - $page->post_parent = $post->post_parent; - $page->post_type = $post->post_type; - $page->post_status = $post->post_status; - - $postdata['post'] = json_encode($page); - } - - return $postdata; - } - - /** - * Retrieves pages, templates, authors, categories and tags data that will be - * used as options when displayed on the editor in UpdraftCentral - * - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - protected function get_options($type = 'post') { - // Primarily used for editor consumption so we don't include trash here. Besides, - // trash posts/pages aren't included as parent options. - $parent_pages = $this->get_parent_pages(); - $pages = $parent_pages['data']['pages']; - - // Add flexibility by letting users filter the default roles and add their own - // custom page/post "author" role(s) if need be. - $author_roles = apply_filters('updraftcentral_author_roles', array('administrator', 'editor', 'author', 'contributor')); - $authors = get_users(array('role__in' => $author_roles)); - - if (!function_exists('get_page_templates')) { - require_once(ABSPATH.'wp-admin/includes/theme.php'); - } - - $templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates(); - $template_options = array(); - foreach ($templates as $template => $filename) { - $item = array( - 'filename' => $filename, - 'template' => $template, - ); - $template_options[] = $item; - } - - $page_options = array(); - foreach ($pages as $page_item) { - if (isset($page_item['post'])) { - $page = json_decode($page_item['post']); - $item = array( - 'id' => $page->ID, - 'title' => $page->post_title, - 'parent' => $page->post_parent - ); - $page_options[] = $item; - } - } - - $author_options = array(); - foreach ($authors as $user) { - $item = array( - 'id' => $user->ID, - 'name' => $user->display_name, - ); - $author_options[] = $item; - } - - $response = array( - 'page' => $page_options, - 'author' => $author_options, - 'template' => $template_options, - 'date' => $this->get_date_options($type), - ); - - if ('post' == $type) { - $categories = get_categories(array('hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC')); - $tags = get_tags(array('hide_empty' => false)); - - $category_options = array(); - foreach ($categories as $category) { - $item = array( - 'id' => $category->term_id, - 'name' => $category->name, - 'parent' => $category->parent - ); - $category_options[] = $item; - } - - $tag_options = array(); - foreach ($tags as $tag) { - $item = array( - 'id' => $tag->term_id, - 'name' => $tag->name, - ); - $tag_options[] = $item; - } - - $response['category'] = $category_options; - $response['tag'] = $tag_options; - } - - return $response; - } - - /** - * Changes the state/status of the given post based from the submitted action/request - * - * @param int $id The ID of the current page to work on - * @param string $action The type of change that the current request is going to apply - * @param string $type The type of the module that the current request is processing - * - * @return array - */ - protected function apply_state($id, $action, $type = 'post') { - if (empty($id)) return false; - - $post = get_post($id); - if (!empty($post)) { - $previous_status = $post->post_status; - $deleted = false; - - switch ($action) { - case 'draft': - $args = array('ID' => $id, 'post_status' => 'draft'); - wp_update_post($args); - break; - case 'trash': - wp_trash_post($id); - break; - case 'publish': - $args = array('ID' => $id, 'post_status' => 'publish'); - wp_update_post($args); - break; - case 'restore': - $args = array('ID' => $id, 'post_status' => 'pending'); - wp_update_post($args); - break; - case 'delete': - $result = wp_delete_post($id, true); - if (!empty($result)) $deleted = true; - break; - default: - break; - } - - $postdata = $this->get_postdata($post); - if (!empty($postdata) || $deleted) { - $data = $deleted ? $id : $postdata; - $result = array( - 'id' => $id, - 'previous_status' => $previous_status - ); - - $result[$type] = $data; - return $result; - } - } - - return false; - } - - /** - * Imports image from UpdraftCentral's page/post editor - * - * @param string $image_url The URL of the image to import - * @param string $image_data The image data to save. If empty, image_url will be used to download the image - * @param int $post_id The ID of the page where this image is to be attached - * - * @return integer - */ - protected function attach_remote_image($image_url, $image_data, $post_id) { - if (empty($image_url) || empty($post_id)) return; - - $image = pathinfo($image_url); - $image_name = $image['basename']; - $upload_dir = wp_upload_dir(); - - if (empty($image_data)) { - $response = wp_remote_get($image_url); - if (!is_wp_error($response)) { - $image_data = wp_remote_retrieve_body($response); - } - } else { - $image_data = base64_decode($image_data); - } - - $media_id = 0; - if (!empty($image_data)) { - $unique_file_name = wp_unique_filename($upload_dir['path'], $image_name); - $filename = basename($unique_file_name); - - if (wp_mkdir_p($upload_dir['path'])) { - $file = $upload_dir['path'] . '/' . $filename; - } else { - $file = $upload_dir['basedir'] . '/' . $filename; - } - - file_put_contents($file, $image_data); - $wp_filetype = wp_check_filetype($filename, null); - - $attachment = array( - 'post_mime_type' => $wp_filetype['type'], - 'post_title' => sanitize_file_name($filename), - 'post_content' => '', - 'post_status' => 'inherit' - ); - - $media_id = wp_insert_attachment($attachment, $file, $post_id); - require_once(ABSPATH . 'wp-admin/includes/image.php'); - - $attach_data = wp_generate_attachment_metadata($media_id, $file); - wp_update_attachment_metadata($media_id, $attach_data); - set_post_thumbnail($post_id, $media_id); - } - - return $media_id; - } - - /** - * Checks whether we have the required fields submitted and the user has - * the capabilities to execute the requested action - * - * @param array $capabilities The capabilities to check and validate - * - * @return array|void - */ - protected function _validate_capabilities($capabilities) { - foreach ($capabilities as $capability) { - if (!current_user_can($capability)) return $this->_generic_error_response('insufficient_permission'); - } - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/theme.php b/wordpress/wp-content/plugins/updraftplus/central/modules/theme.php deleted file mode 100644 index b168d492d266f152470a98a3da37b45c545139df..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/theme.php +++ /dev/null @@ -1,611 +0,0 @@ -switched = switch_to_blog($blog_id); - } - } - - /** - * Function that gets called after every action - * - * @param string $command a string that corresponds to UDC command to call a certain method for this class. - * @param array $data an array of data post or get fields - * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id - * - * link to udrpc_action main function in class UpdraftCentral_Listener - */ - public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Here, we're restoring to the current (default) blog before we switched - if ($this->switched) restore_current_blog(); - } - - /** - * Constructor - */ - public function __construct() { - $this->_admin_include('theme.php', 'file.php', 'template.php', 'class-wp-upgrader.php', 'theme-install.php', 'update.php'); - } - - /** - * Installs and activates a theme through upload - * - * @param array $params Parameter array containing information pertaining the currently uploaded theme - * @return array Contains the result of the current process - */ - public function upload_theme($params) { - return $this->process_chunk_upload($params, 'theme'); - } - - /** - * Checks whether the theme is currently installed and activated. - * - * @param array $query Parameter array containing the name of the theme to check - * @return array Contains the result of the current process - */ - public function is_theme_installed($query) { - - if (!isset($query['theme'])) - return $this->_generic_error_response('theme_name_required'); - - - $result = $this->_get_theme_info($query['theme']); - return $this->_response($result); - } - - /** - * Applies currently requested action for theme processing - * - * @param string $action The action to apply (e.g. activate or install) - * @param array $query Parameter array containing information for the currently requested action - * - * @return array - */ - private function _apply_theme_action($action, $query) { - - $result = array(); - switch ($action) { - case 'activate': - $info = $this->_get_theme_info($query['theme']); - if ($info['installed']) { - switch_theme($info['slug']); - if (wp_get_theme()->get_stylesheet() === $info['slug']) { - $result = array('activated' => true); - } else { - $result = $this->_generic_error_response('theme_not_activated', array($query['theme'])); - } - } else { - $result = $this->_generic_error_response('theme_not_installed', array($query['theme'])); - } - break; - case 'network_enable': - $info = $this->_get_theme_info($query['theme']); - if ($info['installed']) { - // Make sure that network_enable_theme is present and callable since - // it is only available at 4.6. If not, we'll do things the old fashion way - if (is_callable(array('WP_Theme', 'network_enable_theme'))) { - WP_Theme::network_enable_theme($info['slug']); - } else { - $allowed_themes = get_site_option('allowedthemes'); - $allowed_themes[$info['slug']] = true; - - update_site_option('allowedthemes', $allowed_themes); - } - - $allowed = WP_Theme::get_allowed_on_network(); - if (is_array($allowed) && !empty($allowed[$info['slug']])) { - $result = array('enabled' => true); - } else { - $result = $this->_generic_error_response('theme_not_enabled', array($query['theme'])); - } - } else { - $result = $this->_generic_error_response('theme_not_installed', array($query['theme'])); - } - break; - case 'network_disable': - $info = $this->_get_theme_info($query['theme']); - if ($info['installed']) { - // Make sure that network_disable_theme is present and callable since - // it is only available at 4.6. If not, we'll do things the old fashion way - if (is_callable(array('WP_Theme', 'network_disable_theme'))) { - WP_Theme::network_disable_theme($info['slug']); - } else { - $allowed_themes = get_site_option('allowedthemes'); - if (isset($allowed_themes[$info['slug']])) { - unset($allowed_themes[$info['slug']]); - } - - update_site_option('allowedthemes', $allowed_themes); - } - - $allowed = WP_Theme::get_allowed_on_network(); - if (is_array($allowed) && empty($allowed[$info['slug']])) { - $result = array('disabled' => true); - } else { - $result = $this->_generic_error_response('theme_not_disabled', array($query['theme'])); - } - } else { - $result = $this->_generic_error_response('theme_not_installed', array($query['theme'])); - } - break; - case 'install': - $api = themes_api('theme_information', array( - 'slug' => $query['slug'], - 'fields' => array( - 'description' => true, - 'sections' => false, - 'rating' => true, - 'ratings' => true, - 'downloaded' => true, - 'downloadlink' => true, - 'last_updated' => true, - 'screenshot_url' => true, - 'parent' => true, - ) - )); - - if (is_wp_error($api)) { - $result = $this->_generic_error_response('generic_response_error', array($api->get_error_message())); - } else { - $info = $this->_get_theme_info($query['theme']); - $installed = $info['installed']; - - $error_code = $error_message = ''; - if (!$installed) { - // WP < 3.7 - if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTPLUS_DIR.'/central/classes/class-automatic-upgrader-skin.php'); - - $skin = new Automatic_Upgrader_Skin(); - $upgrader = new Theme_Upgrader($skin); - - $download_link = $api->download_link; - $installed = $upgrader->install($download_link); - - if (is_wp_error($installed)) { - $error_code = $installed->get_error_code(); - $error_message = $installed->get_error_message(); - } elseif (is_wp_error($skin->result)) { - $error_code = $skin->result->get_error_code(); - $error_message = $skin->result->get_error_message(); - - $error_data = $skin->result->get_error_data($error_code); - if (!empty($error_data)) { - if (is_array($error_data)) $error_data = json_encode($error_data); - $error_message .= ' '.$error_data; - } - } elseif (is_null($installed) || !$installed) { - global $wp_filesystem; - $upgrade_messages = $skin->get_upgrade_messages(); - - if (!class_exists('WP_Filesystem_Base')) include_once(ABSPATH.'/wp-admin/includes/class-wp-filesystem-base.php'); - - // Pass through the error from WP_Filesystem if one was raised. - if ($wp_filesystem instanceof WP_Filesystem_Base && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) { - $error_code = $wp_filesystem->errors->get_error_code(); - $error_message = $wp_filesystem->errors->get_error_message(); - } elseif (!empty($upgrade_messages)) { - // We're only after for the last feedback that we received from the install process. Mostly, - // that is where the last error has been inserted. - $messages = $skin->get_upgrade_messages(); - $error_code = 'install_failed'; - $error_message = end($messages); - } else { - $error_code = 'unable_to_connect_to_filesystem'; - $error_message = __('Unable to connect to the filesystem. Please confirm your credentials.'); - } - } - } - - if (!$installed || is_wp_error($installed)) { - $result = $this->_generic_error_response('theme_install_failed', array( - 'theme' => $query['theme'], - 'error_code' => $error_code, - 'error_message' => $error_message - )); - } else { - $result = array('installed' => true); - } - } - break; - } - - return $result; - } - - /** - * Preloads the submitted credentials to the global $_POST variable - * - * @param array $query Parameter array containing information for the currently requested action - */ - private function _preload_credentials($query) { - if (!empty($query) && isset($query['filesystem_credentials'])) { - parse_str($query['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. - $_POST[$key] = $value; - } - } - } - } - - /** - * Checks whether we have the required fields submitted and the user has - * the capabilities to execute the requested action - * - * @param array $query The submitted information - * @param array $fields The required fields to check - * @param array $capabilities The capabilities to check and validate - * - * @return array|string - */ - private function _validate_fields_and_capabilities($query, $fields, $capabilities) { - - $error = ''; - if (!empty($fields)) { - for ($i=0; $i_generic_error_response('keyword_required'); - } else { - $error = $this->_generic_error_response('theme_'.$query[$field].'_required'); - } - break; - } - } - } - - if (empty($error) && !empty($capabilities)) { - for ($i=0; $i_generic_error_response('theme_insufficient_permission'); - break; - } - } - } - - return $error; - } - - /** - * Activates the theme - * - * @param array $query Parameter array containing the name of the theme to activate - * @return array Contains the result of the current process - */ - public function activate_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes')); - if (!empty($error)) { - return $error; - } - - $result = $this->_apply_theme_action('activate', $query); - if (empty($result['activated'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Enables theme for network - * - * @param array $query Parameter array containing the name of the theme to activate - * @return array Contains the result of the current process - */ - public function network_enable_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes')); - if (!empty($error)) { - return $error; - } - - $result = $this->_apply_theme_action('network_enable', $query); - if (empty($result['enabled'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Disables theme from network - * - * @param array $query Parameter array containing the name of the theme to activate - * @return array Contains the result of the current process - */ - public function network_disable_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes')); - if (!empty($error)) { - return $error; - } - - $result = $this->_apply_theme_action('network_disable', $query); - if (empty($result['disabled'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Download, install and activates the theme - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug - * @return array Contains the result of the current process - */ - public function install_activate_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme', 'slug'), array('install_themes', 'switch_themes')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - - $result = $this->_apply_theme_action('install', $query); - if (!empty($result['installed']) && $result['installed']) { - $result = $this->_apply_theme_action('activate', $query); - if (empty($result['activated'])) { - return $result; - } - } else { - return $result; - } - - return $this->_response($result); - } - - /** - * Download, install the theme - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug - * @return array Contains the result of the current process - */ - public function install_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme', 'slug'), array('install_themes')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - - $result = $this->_apply_theme_action('install', $query); - if (empty($result['installed'])) { - return $result; - } - - return $this->_response($result); - } - - /** - * Uninstall/delete the theme - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug - * @return array Contains the result of the current process - */ - public function delete_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme'), array('delete_themes')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - $info = $this->_get_theme_info($query['theme']); - - if ($info['installed']) { - $deleted = delete_theme($info['slug']); - - if ($deleted) { - $result = array('deleted' => true); - } else { - $result = $this->_generic_error_response('delete_theme_failed', array($query['theme'])); - } - } else { - $result = $this->_generic_error_response('theme_not_installed', array($query['theme'])); - } - - return $this->_response($result); - } - - /** - * Updates/upgrade the theme - * - * @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug - * @return array Contains the result of the current process - */ - public function update_theme($query) { - - $error = $this->_validate_fields_and_capabilities($query, array('theme'), array('update_themes')); - if (!empty($error)) { - return $error; - } - - $this->_preload_credentials($query); - $info = $this->_get_theme_info($query['theme']); - - // Make sure that we still have the theme installed before running - // the update process - if ($info['installed']) { - // Load the updates command class if not existed - if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php'); - $update_command = new UpdraftCentral_Updates_Commands($this->rc); - - $result = $update_command->update_theme($info['slug']); - if (!empty($result['error'])) { - $result['values'] = array($query['theme']); - } - } else { - return $this->_generic_error_response('theme_not_installed', array($query['theme'])); - } - - return $this->_response($result); - } - - /** - * Gets the theme information along with its active and install status - * - * @internal - * @param array $theme The name of the theme to pull the information from - * @return array Contains the theme information - */ - private function _get_theme_info($theme) { - - $info = array( - 'active' => false, - 'installed' => false - ); - - // Clear theme cache so that newly installed/downloaded themes - // gets reflected when calling "get_themes" - if (function_exists('wp_clean_themes_cache')) { - wp_clean_themes_cache(); - } - - // Gets all themes available. - $themes = wp_get_themes(); - $current_theme_slug = basename(get_stylesheet_directory()); - - // Loops around each theme available. - foreach ($themes as $slug => $value) { - $name = $value->get('Name'); - $theme_name = !empty($name) ? $name : $slug; - - // If the theme name matches that of the specified name, it will gather details. - if ($theme_name === $theme) { - $info['installed'] = true; - $info['active'] = ($slug === $current_theme_slug) ? true : false; - $info['slug'] = $slug; - $info['data'] = $value; - break; - } - } - - return $info; - } - - /** - * Loads all available themes with additional attributes and settings needed by UpdraftCentral - * - * @param array $query Parameter array Any available parameters needed for this action - * @return array Contains the result of the current process - */ - public function load_themes($query) { - - $error = $this->_validate_fields_and_capabilities($query, array(), array('install_themes', 'switch_themes')); - if (!empty($error)) { - return $error; - } - - $website = get_bloginfo('name'); - $results = array(); - - // Load the updates command class if not existed - if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php'); - $updates = new UpdraftCentral_Updates_Commands($this->rc); - - // Get themes for update - $theme_updates = (array) $updates->get_item_updates('themes'); - - // Get all themes - $themes = wp_get_themes(); - $current_theme_slug = basename(get_stylesheet_directory()); - - foreach ($themes as $slug => $value) { - $name = $value->get('Name'); - $theme_name = !empty($name) ? $name : $slug; - - $theme = new stdClass(); - $theme->name = $theme_name; - $theme->description = $value->get('Description'); - $theme->slug = $slug; - $theme->version = $value->get('Version'); - $theme->author = $value->get('Author'); - $theme->status = ($slug === $current_theme_slug) ? 'active' : 'inactive'; - - $template = $value->get('Template'); - $theme->child_theme = !empty($template) ? true : false; - $theme->website = $website; - $theme->multisite = is_multisite(); - $theme->site_url = trailingslashit(get_bloginfo('url')); - - if ($theme->child_theme) { - $parent_theme = wp_get_theme($template); - $parent_name = $parent_theme->get('Name'); - - $theme->parent = !empty($parent_name) ? $parent_name : $parent_theme->get_stylesheet(); - } - - if (!empty($theme_updates[$slug])) { - $update_info = $theme_updates[$slug]; - - if (version_compare($theme->version, $update_info->update['new_version'], '<')) { - if (!empty($update_info->update['new_version'])) $theme->latest_version = $update_info->update['new_version']; - if (!empty($update_info->update['package'])) $theme->download_link = $update_info->update['package']; - } - } - - if (empty($theme->short_description) && !empty($theme->description)) { - // Only pull the first sentence as short description, it should be enough rather than displaying - // an empty description or a full blown one which the user can access anytime if they press on - // the view details link in UpdraftCentral. - $temp = explode('.', $theme->description); - $short_description = $temp[0]; - - // Adding the second sentence wouldn't hurt, in case the first sentence is too short. - if (isset($temp[1])) $short_description .= '.'.$temp[1]; - - $theme->short_description = $short_description.'.'; - } - - $results[] = $theme; - } - - $result = array( - 'themes' => $results, - 'theme_updates' => $theme_updates, - ); - - $result = array_merge($result, $this->_get_backup_credentials_settings(get_theme_root())); - return $this->_response($result); - } - - /** - * Gets the backup and security credentials settings for this website - * - * @param array $query Parameter array Any available parameters needed for this action - * @return array Contains the result of the current process - */ - public function get_theme_requirements() { - return $this->_response($this->_get_backup_credentials_settings(get_theme_root())); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/updates.php b/wordpress/wp-content/plugins/updraftplus/central/modules/updates.php deleted file mode 100644 index d438b0941a1d74a02f74f5b4d3c0d017d650e64c..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/updates.php +++ /dev/null @@ -1,869 +0,0 @@ -_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. - $_POST[$key] = $value; - } - } - } - - $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']; - $core_updates = array(); - 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 - break; - } - - $translation_updates = array(); - if (!empty($updates['translations'])) { - $translation_updates[] = $this->_update_translation(); - } - - return $this->_response(array( - 'plugins' => $plugin_updates, - 'themes' => $theme_updates, - 'core' => $core_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 - * - * @return array - */ - 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 - * - * @return array - */ - 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) - * - * @return array - */ - public function get_item_updates($entity) { - $updates = array(); - switch ($entity) { - case 'themes': - wp_update_themes(); - $updates = $this->maybe_add_third_party_items(get_theme_updates(), 'theme'); - break; - case 'plugins': - wp_update_plugins(); - $updates = $this->maybe_add_third_party_items(get_plugin_updates(), 'plugin'); - break; - } - - return $updates; - } - - /** - * 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 - * @return array - */ - private function _update_plugin($plugin, $slug) { - - $status = array( - 'update' => 'plugin', - 'plugin' => $plugin, - 'slug' => sanitize_key($slug), - 'oldVersion' => '', - 'newVersion' => '', - ); - - if (false !== strpos($plugin, '..') || false !== strpos($plugin, ':') || !preg_match('#^[^\/]#i', $plugin)) { - $status['error'] = 'not_found'; - return $status; - } - - $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'; - return $status; - } - - if ($plugin_data['Version']) { - $status['oldVersion'] = $plugin_data['Version']; - } - - if (!current_user_can('update_plugins')) { - $status['error'] = 'updates_permission_denied'; - return $status; - } - - include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'); - - wp_update_plugins(); - - // WP < 3.7 - 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)) { - $result = $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'; - return $status; - } - - $plugin_data = get_plugins('/' . $result[$plugin]['destination_name']); - $plugin_data = reset($plugin_data); - - if ($plugin_data['Version']) { - $status['newVersion'] = $plugin_data['Version']; - } - return $status; - - } elseif (is_wp_error($result)) { - $status['error'] = $result->get_error_code(); - $status['error_message'] = $result->get_error_message(); - return $status; - - } elseif (is_bool($result) && !$result) { - $status['error'] = 'unable_to_connect_to_filesystem'; - - global $wp_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(); - } - - return $status; - - } else { - // An unhandled error occured - $status['error'] = 'update_failed'; - return $status; - } - } - - /** - * Adapted from _update_theme (above) - * - * @param string $core - * @return array - */ - private function _update_core($core) { - - global $wp_filesystem; - - $status = array( - 'update' => 'core', - 'core' => $core, - 'oldVersion' => '', - 'newVersion' => '', - ); - - // 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'; - return $status; - } - - include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'); - - wp_version_check(); - - $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; - $core_update_key = $k; - } - } - - if (false === $core_update_key) { - $status['error'] = 'no_update_found'; - return $status; - } - - $update = $get_core_updates[$core_update_key]; - - // WP < 3.7 - 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(); - return $status; - - } 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(); - } - - return $status; - - - } elseif (preg_match('/^[0-9]/', $result)) { - - $status['newVersion'] = $result; - - return $status; - - } else { - // An unhandled error occured - $status['error'] = 'update_failed'; - return $status; - } - - } - - private function _update_theme($theme) { - - global $wp_filesystem; - - $status = array( - 'update' => 'theme', - 'theme' => $theme, - 'oldVersion' => '', - 'newVersion' => '', - ); - - if (false !== strpos($theme, '/') || false !== strpos($theme, '\\')) { - $status['error'] = 'not_found'; - return $status; - } - - $theme_version = $this->get_theme_version($theme); - if (false === $theme_version) { - $status['error'] = 'not_found'; - return $status; - } - $status['oldVersion'] = $theme_version; - - if (!current_user_can('update_themes')) { - $status['error'] = 'updates_permission_denied'; - return $status; - } - - include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'); - - wp_update_themes(); - - // WP < 3.7 - 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); - $upgrader->init(); - $result = $upgrader->bulk_upgrade(array($theme)); - - if (is_array($result) && empty($result[$theme]) && is_wp_error($skin->result)) { - $result = $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'; - return $status; - } - - $new_theme_version = $this->get_theme_version($theme); - if (false === $new_theme_version) { - $status['error'] = 'update_failed'; - return $status; - } - - $status['newVersion'] = $new_theme_version; - - return $status; - - } elseif (is_wp_error($result)) { - $status['error'] = $result->get_error_code(); - $status['error_message'] = $result->get_error_message(); - return $status; - - } 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(); - } - - return $status; - - } else { - // An unhandled error occured - $status['error'] = 'update_failed'; - return $status; - } - - } - - /** - * Updates available translations for this website - * - * @return Array - */ - private function _update_translation() { - global $wp_filesystem; - - $status = array(); - - 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'; - } else { - // An unhandled error occured - $status['error'] = 'update_failed'; - } - - return $status; - } - - private function get_theme_version($theme) { - - if (function_exists('wp_get_theme')) { - // Since WP 3.4.0 - $theme = wp_get_theme($theme); - - if (is_a($theme, 'WP_Theme')) { - return $theme->Version; - } else { - return false; - } - - } else { - $theme_data = get_theme_data(WP_CONTENT_DIR . '/themes/'.$theme.'/style.css'); - - if (isset($theme_data['Version'])) { - return $theme_data['Version']; - } else { - return false; - } - } - } - - /** - * 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') - ); - - // Most of the premium plugin developers are hooking into the "pre_set_site_transient_update_plugins" and - // "pre_set_site_transient_update_themes" filters if they want their plugins or themes to support automatic - // updates. Thus, we're making sure here that if for some reason, those plugins or themes didn't get through - // and added to the "update_plugins" or "update_themes" transients when calling the get_site_transient('update_plugins') - // or get_site_transient('update_themes') we add them here manually. - $filters = apply_filters("pre_set_site_transient_update_{$type}s", $transient, "update_{$type}s"); - - - $all_items = array(); - switch ($type) { - case 'plugin': - $all_items = get_plugins(); - break; - case 'theme': - $this->_frontend_include('theme.php'); - if (function_exists('wp_get_themes')) { - $themes = wp_get_themes(); - if (!empty($themes)) { - // We make sure that the return key matched the previous - // key from "get_themes", otherwise, no updates will be found - // even if it does have one. "get_themes" returns the name of the - // theme as the key while "wp_get_themes" returns the slug. - foreach ($themes as $theme) { - $all_items[$theme->Name] = $theme; - } - } - } else { - $all_items = get_themes(); - } - break; - default: - break; - } - - - if (!empty($all_items)) { - $all_items = (array) $all_items; - foreach ($all_items as $key => $data) { - if (!isset($items[$key]) && isset($filters->response[$key])) { - - $update_info = ('plugin' === $type) ? $filters->response[$key] : $data; - - // If "package" is empty, it means that this plugin or theme does not support automatic updates - // currently, since the "package" field is the one holding the download link of these plugins/themes - // and WordPress is using this field to download the latest version of these items. - // - // Most of the time, this "package" field is not empty, but for premium plugins/themes this can be - // conditional, only then if the user provides a legit access or api key can this field be populated or available. - // - // We set this variable to "false" by default, as plugins/themes hosted in wordpress.org always sets this - // to the downloadable zip file of the plugin/theme. - // - // N.B. We only add premium plugins/themes that has this "package" field set and non-empty, otherwise, it - // does not support automatic updates as explained above. - $is_package_empty = false; - - if (is_object($update_info)) { - if (!isset($update_info->package) || empty($update_info->package)) { - $is_package_empty = true; - } - - } elseif (is_array($update_info)) { - if (!isset($update_info['package']) || empty($update_info['package'])) { - $is_package_empty = true; - } - } - - // Add this plugin/theme to the current updates collection - if (!$is_package_empty) { - $items[$key] = ('plugin' === $type) ? (object) $data : $this->get_theme_info($key); - $items[$key]->update = $update_info; - } - - } - } - } - - return $this->prep_items_for_updates($items, $type); - } - - /** - * Extracts theme's data or information - * - * @param string $theme A string representing a theme's name or slug. - * @return object|boolean If successful, an object containing the theme data or information, "false" otherwise. - */ - private function get_theme_info($theme) { - - if (function_exists('wp_get_theme')) { - $theme = wp_get_theme($theme); - if (is_a($theme, 'WP_Theme')) { - return $theme; - } - } else { - $theme_data = get_theme_data(WP_CONTENT_DIR.'/themes/'.$theme.'/style.css'); - if (isset($theme_data['Version'])) { - if (!isset($theme_data['ThemeURI'])) $theme_data['ThemeURI'] = $theme_data['URI']; - return (object) $theme_data; - } - } - - return false; - } - - /** - * Fix items for update with missing "plugin" or "theme" field if applicable - * - * @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 prep_items_for_updates($items, $type) { - - foreach ($items as $key => $data) { - $update_info = $data->update; - - // Some plugins and/or themes does not adhere to the standard WordPress updates meta - // properties/fields. Thus, missing some fields such as "plugin" or "theme" - // in their update information results in "Automatic updates is unavailable for this item" - // in UDC since we're using these fields to process the updates. - // - // As a workaround, we're filling these missing fields in order to solve the above issue - // in case the developer of these plugins/themes forgot to include them. - if (is_object($update_info)) { - $update_info = (array) $update_info; - if (!isset($update_info[$type])) { - $update_info[$type] = $key; - } - - $update_info = (object) $update_info; - - } elseif (is_array($update_info)) { - if (!isset($update_info[$type])) { - $update_info[$type] = $key; - } - } - - // Re-assign the updated info to the original "update" property - $items[$key]->update = $update_info; - } - - return $items; - } - - /** - * Custom validation for translation permission. Since the 'install_languages' capability insn't available until 4.9 - * therefore, we wrapped the validation check in this block to support older version of WP. - * - * @return Boolean - */ - private function user_can_update_translations() { - global $updraftplus; - $wp_version = $updraftplus->get_wordpress_version(); - - if (version_compare($wp_version, '4.9', '<')) { - if (current_user_can('update_core') || current_user_can('update_plugins') || current_user_can('update_themes')) return true; - } else { - if (current_user_can('install_languages')) return true; - } - - return false; - } - - public function get_updates($options) { - - // Forcing Elegant Themes (Divi) updates component to load if it exist. - if (function_exists('et_register_updates_component')) et_register_updates_component(); - - if (!current_user_can('update_plugins') && !current_user_can('update_themes') && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied'); - - $this->_admin_include('plugin.php', 'update.php', 'file.php', 'template.php'); - $this->_frontend_include('update.php'); - - if (!is_array($options)) $options = array(); - - // Normalise it - $plugin_updates = array(); - if (current_user_can('update_plugins')) { - - // Detect if refresh needed - $transient = get_site_transient('update_plugins'); - if (!empty($options['force_refresh']) || false === $transient) { - delete_site_transient('update_plugins'); - wp_update_plugins(); - } - - $get_plugin_updates = $this->maybe_add_third_party_items(get_plugin_updates(), 'plugin'); - if (is_array($get_plugin_updates)) { - foreach ($get_plugin_updates as $update) { - - // For some reason, some 3rd-party (premium) plugins are returning the same version - // with that of the currently installed version in WordPress. Thus, we're making sure here to - // only return those items for update that has new versions greater than the currently installed version. - if (version_compare($update->Version, $update->update->new_version, '>=')) continue; - - $plugin_updates[] = array( - 'name' => $update->Name, - 'plugin_uri' => $update->PluginURI, - 'version' => $update->Version, - 'description' => $update->Description, - 'author' => $update->Author, - 'author_uri' => $update->AuthorURI, - 'title' => $update->Title, - 'author_name' => $update->AuthorName, - 'update' => array( - // With Affiliates-WP, if you have not connected, this is null. - 'plugin' => isset($update->update->plugin) ? $update->update->plugin : null, - 'slug' => $update->update->slug, - 'new_version' => $update->update->new_version, - 'package' => $update->update->package, - 'tested' => isset($update->update->tested) ? $update->update->tested : null, - 'compatibility' => isset($update->update->compatibility) ? (array) $update->update->compatibility : null, - 'sections' => isset($update->update->sections) ? (array) $update->update->sections : null, - ), - ); - } - } - } - - $theme_updates = array(); - if (current_user_can('update_themes')) { - - // Detect if refresh needed - $transient = get_site_transient('update_themes'); - if (!empty($options['force_refresh']) || false === $transient) { - delete_site_transient('update_themes'); - wp_update_themes(); - } - $get_theme_updates = $this->maybe_add_third_party_items(get_theme_updates(), 'theme'); - if (is_array($get_theme_updates)) { - foreach ($get_theme_updates as $update) { - - // We're making sure here to only return those items for update that has new - // versions greater than the currently installed version. - if (version_compare($update->Version, $update->update['new_version'], '>=')) continue; - - $name = $update->Name; - $theme_name = !empty($name) ? $name : $update->update['theme']; - - $theme_updates[] = array( - 'name' => $theme_name, - 'theme_uri' => $update->ThemeURI, - 'version' => $update->Version, - 'description' => $update->Description, - 'author' => $update->Author, - 'author_uri' => $update->AuthorURI, - 'update' => array( - 'theme' => $update->update['theme'], - 'new_version' => $update->update['new_version'], - 'package' => $update->update['package'], - 'url' => $update->update['url'], - ), - ); - - } - } - } - - $core_updates = array(); - if (current_user_can('update_core')) { - - // Detect if refresh needed - $transient = get_site_transient('update_core'); - if (!empty($options['force_refresh']) || false === $transient) { - // The next line is only needed for older WP versions - otherwise, the parameter to wp_version_check forces a check. - delete_site_transient('update_core'); - wp_version_check(array(), true); - } - - $get_core_updates = get_core_updates(); - - if (is_array($get_core_updates)) { - - $core_update_key = false; - $core_update_latest_version = false; - - // 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; - $core_update_key = $k; - } - } - - if (false !== $core_update_key) { - - $update = $get_core_updates[$core_update_key]; - - global $wpdb; - - $mysql_version = $wpdb->db_version(); - - $is_mysql = (file_exists(WP_CONTENT_DIR . '/db.php') && empty($wpdb->is_mysql)) ? false : true; - - // We're making sure here to only return those items for update that has new - // versions greater than the currently installed version. - if (version_compare($wp_version, $update->version, '<')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable - $core_updates[] = array( - 'download' => $update->download, - 'version' => $update->version, - 'php_version' => $update->php_version, - 'mysql_version' => $update->mysql_version, - 'installed' => array( - 'version' => $wp_version,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable - 'mysql' => $mysql_version, - 'php' => PHP_VERSION, - 'is_mysql' => $is_mysql, - ), - 'sufficient' => array( - 'mysql' => version_compare($mysql_version, $update->mysql_version, '>='), - 'php' => version_compare(PHP_VERSION, $update->php_version, '>='), - ), - ); - } - - } - } - - } - - $translation_updates = array(); - if (function_exists('wp_get_translation_updates') && $this->user_can_update_translations()) { - $translations = wp_get_translation_updates(); - - $translation_updates = array( - 'items' => $translations - ); - } - - // Do we need to ask the user for filesystem credentials? - $request_filesystem_credentials = array(); - $check_fs = array( - 'plugins' => WP_PLUGIN_DIR, - 'themes' => WP_CONTENT_DIR.'/themes', - 'core' => untrailingslashit(ABSPATH) - ); - - if (!empty($translation_updates)) { - // 'en_US' don't usually have the "languages" folder, thus, we - // check if there's a need to ask for filesystem credentials for that - // folder if it exists, most especially for locale other than 'en_US'. - $language_dir = WP_CONTENT_DIR.'/languages'; - if ('en_US' !== get_locale() && is_dir($language_dir)) { - $check_fs['translations'] = $language_dir; - } - } - - foreach ($check_fs as $entity => $dir) { - $filesystem_method = get_filesystem_method(array(), $dir); - ob_start(); - $filesystem_credentials_are_stored = request_filesystem_credentials(site_url()); - $filesystem_form = strip_tags(ob_get_contents(), '

    '); - ob_end_clean(); - $request_filesystem_credentials[$entity] = ('direct' != $filesystem_method && !$filesystem_credentials_are_stored); - } - - $automatic_backups = (class_exists('UpdraftPlus_Options') && class_exists('UpdraftPlus_Addon_Autobackup') && UpdraftPlus_Options::get_updraft_option('updraft_autobackup_default', true)) ? true : false; - - return $this->_response(array( - 'plugins' => $plugin_updates, - 'themes' => $theme_updates, - 'core' => $core_updates, - 'translations' => $translation_updates, - 'meta' => array( - 'request_filesystem_credentials' => $request_filesystem_credentials, - 'filesystem_form' => $filesystem_form, - 'automatic_backups' => $automatic_backups - ), - )); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/modules/users.php b/wordpress/wp-content/plugins/updraftplus/central/modules/users.php deleted file mode 100644 index dee61b4d18c0df43eab36a4d8d360e77918f115e..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/modules/users.php +++ /dev/null @@ -1,632 +0,0 @@ -ID === $b->ID) { - return 0; - } - - return ($a->ID < $b->ID) ? -1 : 1; - } - - /** - * Searches users based from the keyword submitted - * - * @internal - * @param array $query Parameter array containing the filter and keyword fields - * @return array Contains the list of users found as well as the total users count - */ - private function _search_users($query) { - $this->_admin_include('user.php'); - $query1 = new WP_User_Query(array( - 'orderby' => 'ID', - 'order' => 'ASC', - 'role'=> $query["role"], - 'search' => '*' . esc_attr($query["search"]) . '*', - 'search_columns' => array('user_login', 'user_email') - )); - $query2 = new WP_User_Query(array( - 'orderby' => 'ID', - 'order' => 'ASC', - 'role'=> $query["role"], - 'meta_query'=>array( - 'relation' => 'OR', - array( - 'key' => 'first_name', - 'value' => $query["search"], - 'compare' => 'LIKE' - ), - array( - 'key' => 'last_name', - 'value' => $query["search"], - 'compare' => 'LIKE' - ), - ) - )); - - if (empty($query1->results) && empty($query2->results)) { - return array("message" => "users_not_found"); - } else { - $found_users = array_merge($query1->results, $query2->results); - $temp = array(); - foreach ($found_users as $new_user) { - if (!isset($temp[$new_user->ID])) { - $temp[$new_user->ID] = $new_user; - } - }; - - $users = array_values($temp); - - // Sort users: - usort($users, array($this, 'compare_user_id')); - $offset = ((int) $query['page_no'] * (int) $query['per_page']) - (int) $query['per_page']; - $user_list = array_slice($users, $offset, $query['per_page']); - - return array( - 'users' => $user_list, - 'total_users' => count($users) - ); - } - } - - /** - * Calculates the number of pages needed to construct the pagination links - * - * @internal - * @param array $query - * @param array $total_users The total number of users found from the WP_User_Query query - * @return array Contains information needed to construct the pagination links - */ - private function _calculate_pages($query, $total_users) { - - $per_page_options = array(10, 20, 30, 40, 50); - - if (!empty($query)) { - - $pages = array(); - $page_count = ceil($total_users / $query["per_page"]); - if ($page_count > 1) { - - for ($i = 0; $i < $page_count; $i++) { - if ($i + 1 == $query['page_no']) { - $paginator_item = array( - "value"=>$i+1, - "setting"=>"disabled" - ); - } else { - $paginator_item = array( - "value"=>$i+1 - ); - } - array_push($pages, $paginator_item); - }; - - if ($query['page_no'] >= $page_count) { - $page_next = array( - "value"=>$page_count, - "setting"=>"disabled" - ); - } else { - $page_next = array( - "value"=>$query['page_no'] + 1 - ); - }; - if (1 === $query['page_no']) { - $page_prev = array( - "value"=>1, - "setting"=>"disabled" - ); - } else { - $page_prev = array( - "value"=>$query['page_no'] - 1 - ); - }; - - return array( - "page_no" => $query['page_no'], - "per_page" => $query["per_page"], - "page_count" => $page_count, - "pages" => $pages, - "page_next" => $page_next, - "page_prev" => $page_prev, - "total_results" => $total_users, - "per_page_options" => $per_page_options - ); - - } else { - return array( - "page_no" => $query['page_no'], - "per_page" => $query["per_page"], - "page_count" => $page_count, - "total_results" => $total_users, - "per_page_options" => $per_page_options - ); - } - } else { - return array( - "per_page_options" => $per_page_options - ); - } - } - - /** - * Validates whether the username exists - * - * @param array $params Contains the user name to check and validate - * @return array An array containing the result of the current process - */ - public function check_username($params) { - $this->_admin_include('user.php'); - $username = $params['user_name']; - - $blog_id = get_current_blog_id(); - if (!empty($params['site_id'])) { - $blog_id = $params['site_id']; - } - - - // Here, we're switching to the actual blog that we need - // to pull users from. - - $switched = function_exists('switch_to_blog') ? switch_to_blog($blog_id) : false; - - if (username_exists($username) && is_user_member_of_blog(username_exists($username), $blog_id)) { - $result = array("valid" => false, "message" => 'username_exists'); - return $this->_response($result); - } - if (!validate_username($username)) { - $result = array("valid" => false, "message" => 'username_invalid'); - return $this->_response($result); - } - - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - $result = array("valid" => true, "message" => 'username_valid'); - return $this->_response($result); - } - - /** - * Pulls blog sites available - * for the current WP instance. - * If the site is a multisite, then sites under the network - * will be pulled, otherwise, it will return an empty array. - * - * @return Array - an array of sites - */ - private function _get_blog_sites() { - - if (!is_multisite()) return array(); - - // Initialize array container - $sites = $network_sites = array(); - - // Check to see if latest get_sites (available on WP version >= 4.6) function is - // available to pull any available sites from the current WP instance. If not, then - // we're going to use the fallback function wp_get_sites (for older version). - if (function_exists('get_sites') && class_exists('WP_Site_Query')) { - $network_sites = get_sites(); - } else { - if (function_exists('wp_get_sites')) { - $network_sites = wp_get_sites(); - } - } - - // We only process if sites array is not empty, otherwise, bypass - // the next block. - if (!empty($network_sites)) { - foreach ($network_sites as $site) { - - // Here we're checking if the site type is an array, because - // we're pulling the blog_id property based on the type of - // site returned. - // get_sites returns an array of object, whereas the wp_get_sites - // function returns an array of array. - $blog_id = is_array($site) ? $site['blog_id'] : $site->blog_id; - - - // We're saving the blog_id and blog name as an associative item - // into the sites array, that will be used as "Sites" option in - // the frontend. - $sites[$blog_id] = get_blog_details($blog_id)->blogname; - } - } - - return $sites; - } - - /** - * Validates whether the email exists - * - * @param array $params Contains the email to check and validate - * @return array An array containing the result of the current process - */ - public function check_email($params) { - $this->_admin_include('user.php'); - $email = $params['email']; - - $blog_id = get_current_blog_id(); - if (isset($params['site_id']) && 0 !== $params['site_id']) { - $blog_id = $params['site_id']; - } - - - // Here, we're switching to the actual blog that we need - // to pull users from. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - if (is_email($email) === false) { - $result = array("valid" => false, "message" => 'email_invalid'); - return $this->_response($result); - } - - if (email_exists($email) && is_user_member_of_blog(email_exists($email), $blog_id)) { - $result = array("valid" => false, "message" => 'email_exists'); - return $this->_response($result); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - $result = array("valid" => true, "message" => 'email_valid'); - return $this->_response($result); - } - - /** - * The get_users function pull all the users from the database - * based on the current search parameters/filters. Please see _search_users - * for the breakdown of these parameters. - * - * @param array $query Parameter array containing the filter and keyword fields - * @return array An array containing the result of the current process - */ - public function get_users($query) { - $this->_admin_include('user.php'); - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - $blog_id = get_current_blog_id(); - if (isset($query['site_id']) && 0 !== $query['site_id']) $blog_id = $query['site_id']; - - - // Here, we're switching to the actual blog that we need - // to pull users from. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - // Set default: - if (empty($query["per_page"])) { - $query["per_page"] = 10; - } - if (empty($query['page_no'])) { - $query['page_no'] = 1; - } - if (empty($query["role"])) { - $query["role"] = ""; - } - - $users = array(); - $total_users = 0; - - if (!empty($query["search"])) { - $search_results = $this->_search_users($query); - - if (isset($search_results['users'])) { - $users = $search_results['users']; - $total_users = $search_results['total_users']; - } - } else { - $user_query = new WP_User_Query(array( - 'orderby' => 'ID', - 'order' => 'ASC', - 'number' => $query["per_page"], - 'paged'=> $query['page_no'], - 'role'=> $query["role"] - )); - - if (empty($user_query->results)) { - $result = array("message" => 'users_not_found'); - return $this->_response($result); - } - - $users = $user_query->results; - $total_users = $user_query->get_total(); - } - - foreach ($users as &$user) { - $user_object = get_userdata($user->ID); - if (method_exists($user_object, 'to_array')) { - $user = $user_object->to_array(); - $user["roles"] = $user_object->roles; - $user["first_name"] = $user_object->first_name; - $user["last_name"] = $user_object->last_name; - $user["description"] = $user_object->description; - } else { - $user = $user_object; - } - } - - $result = array( - "users"=>$users, - "paging" => $this->_calculate_pages($query, $total_users) - ); - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - return $this->_response($result); - } - - /** - * Creates new user for the current blog - * - * @param array $user User information to add - * @return array An array containing the result of the current process - */ - public function add_user($user) { - $this->_admin_include('user.php'); - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - - $blog_id = get_current_blog_id(); - if (isset($user['site_id']) && 0 !== $user['site_id']) $blog_id = $user['site_id']; - - - // Here, we're switching to the actual blog that we need - // to pull users from. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - if (!current_user_can('create_users') && !is_super_admin()) { - $result = array('error' => true, 'message' => 'user_create_no_permission', 'data' => array('multisite' => is_multisite())); - return $this->_response($result); - } - if (is_email($user["user_email"]) === false) { - $result = array("error" => true, "message" => "email_invalid"); - return $this->_response($result); - } - if (email_exists($user["user_email"]) && is_user_member_of_blog(email_exists($user["user_email"]), $blog_id)) { - $result = array("error" => true, "message" => "email_exists"); - return $this->_response($result); - } - if (username_exists($user["user_login"]) && is_user_member_of_blog(username_exists($user["user_login"]), $blog_id)) { - $result = array("error" => true, "message" => "username_exists"); - return $this->_response($result); - } - if (!validate_username($user["user_login"])) { - $result = array("error" => true, "message" => 'username_invalid'); - return $this->_response($result); - } - if (isset($user['site_id']) && !current_user_can('manage_network_users')) { - $result = array("error" => true, "message" => 'user_create_no_permission'); - return $this->_response($result); - } - - if (email_exists($user["user_email"]) && !is_user_member_of_blog(email_exists($user["user_email"]), $blog_id)) { - $user_id = email_exists($user["user_email"]); - } else { - $user_id = wp_insert_user($user); - } - $role = $user['role']; - if (is_multisite()) { - add_existing_user_to_blog(array('user_id' => $user_id, 'role' => $role)); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - if ($user_id > 0) { - $result = array("error" => false, "message" => "user_created_with_user_name", "values" => array($user['user_login'])); - return $this->_response($result); - } else { - $result = array("error" => true, "message" => "user_create_failed", "values" => array($user)); - } - - - return $this->_response($result); - } - - /** - * [delete_user - UCP: users.delete_user] - * - * This function is used to check to make sure the user_id is valid and that it has has user delete permissions. - * If there are no issues, the user is deleted. - * - * current_user_can: This check the user permissons from UCP - * get_userdata: This get the user data on the data from user_id in the $user_id array - * wp_delete_user: Deleting users on the User ID (user_id) and, IF Specified, the Assigner ID (assign_user_id). - * - * @param [type] $params [description] THis is an Array of params sent over from UpdraftCentral - * @return [type] Array [description] This will send back an error array along with message if there are any issues with the user_id - */ - public function delete_user($params) { - $this->_admin_include('user.php'); - $user_id = $params['user_id']; - $assign_user_id = $params["assign_user_id"]; - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($params['site_id']) && 0 !== $params['site_id']) $blog_id = $params['site_id']; - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - if (!current_user_can('delete_users') && !is_super_admin()) { - $result = array('error' => true, 'message' => 'user_delete_no_permission', 'data' => array('multisite' => is_multisite())); - return $this->_response($result); - } - if (get_userdata($user_id) === false) { - $result = array("error" => true, "message" => "user_not_found"); - return $this->_response($result); - } - - if (wp_delete_user($user_id, $assign_user_id)) { - $result = array("error" => false, "message" => "user_deleted"); - } else { - $result = array("error" => true, "message" => "user_delete_failed"); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * Edits existing user information - * - * @param array $user User information to save - * @return array An array containing the result of the current process - */ - public function edit_user($user) { - $this->_admin_include('user.php'); - - // Here, we're getting the current blog id. If blog id - // is passed along with the parameters then we override - // that current (default) value with the parameter blog id value. - - $blog_id = get_current_blog_id(); - if (isset($user['site_id']) && 0 !== $user['site_id']) $blog_id = $user['site_id']; - - // Here, we're switching to the actual blog that we need - // to apply our changes. - - $switched = false; - if (function_exists('switch_to_blog')) { - $switched = switch_to_blog($blog_id); - } - - if (!current_user_can('edit_users') && !is_super_admin() && get_current_user_id() !== $user["ID"]) { - $result = array('error' => true, 'message' => 'user_edit_no_permission', 'data' => array('multisite' => is_multisite())); - return $this->_response($result); - } - - if (false === get_userdata($user["ID"])) { - $result = array("error" => true, "message" => "user_not_found"); - return $this->_response($result); - } - if (get_current_user_id() == $user["ID"]) { - unset($user["role"]); - } - - /* Validate Username*/ - if (!validate_username($user["user_login"])) { - $result = array("error" => true, "message" => 'username_invalid'); - return $this->_response($result); - } - /* Validate Email if not the same*/ - - $remote_user = get_userdata($user["ID"]); - $old_email = $remote_user->user_email; - - if ($user['user_email'] !== $old_email) { - if (is_email($user['user_email']) === false) { - $result = array("error" => true, "message" => 'email_invalid'); - return $this->_response($result); - } - - if (email_exists($user['user_email'])) { - $result = array("error" => true, "message" => 'email_exists'); - return $this->_response($result); - } - } - - - $user_id = wp_update_user($user); - if (is_wp_error($user_id)) { - $result = array("error" => true, "message" => "user_edit_failed_with_error", "values" => array($user_id)); - } else { - $result = array("error" => false, "message" => "user_edited_with_user_name", "values" => array($user["user_login"])); - } - - // Here, we're restoring to the current (default) blog before we - // do the switched. - - if (function_exists('restore_current_blog') && $switched) { - restore_current_blog(); - } - - return $this->_response($result); - } - - /** - * Retrieves available roles to be used as filter options - * - * @return array An array containing all available roles - */ - public function get_roles() { - $this->_admin_include('user.php'); - $roles = array_reverse(get_editable_roles()); - return $this->_response($roles); - } - - /** - * Retrieves information to be use as filters - * - * @return array An array containing the filter fields and their data - */ - public function get_user_filters() { - $this->_admin_include('user.php'); - - // Pull sites options if available. - $sites = $this->_get_blog_sites(); - - $result = array( - "sites" => $sites, - "roles" => array_reverse(get_editable_roles()), - "paging" => $this->_calculate_pages(null, 0), - ); - return $this->_response($result); - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/central/updraftplus.php b/wordpress/wp-content/plugins/updraftplus/central/updraftplus.php deleted file mode 100644 index 9835e4229f8a8f9ebc7905d45e2b0f5c48945d3d..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/central/updraftplus.php +++ /dev/null @@ -1,489 +0,0 @@ -text_domain = self::PLUGIN_NAME; - $this->maybe_initialize_required_objects(); - } - - /** - * Below are interface methods' implementations that are required by UpdraftCentral to function properly. Please - * see the "interface.php" to check all the required interface methods. - */ - - /** - * Retrieves current clean url for anchor link where href attribute value is not url (for ex. #div) or empty - * - * @return string - */ - public function get_current_clean_url() { - $url = $this->get_class_name()::get_current_clean_url();// phpcs:ignore PHPCompatibility.Syntax.NewDynamicAccessToStatic.Found - if (!empty($url)) return $url; - - return ''; - } - - /** - * Returns the name of the plugin that is associated with this host class - * - * @return string - */ - public function get_plugin_name() { - return self::PLUGIN_NAME; - } - - /** - * Checks whether the plugin's DIR constant is currently define or not - * - * @return bool - */ - public function is_host_dir_set() { - return defined('UPDRAFTPLUS_DIR') ? true : false; - } - - /** - * Retrieves the host directory associated with the host plugin - * - * @return string - */ - public function get_host_dir() { - return $this->is_host_dir_set() ? UPDRAFTPLUS_DIR : ''; - } - - /** - * Checks whether the plugin's SET_TIME_LIMIT constant is currently define or not - * - * @return bool - */ - public function is_time_limit_set() { - return defined('UPDRAFTPLUS_SET_TIME_LIMIT') ? true : false; - } - - /** - * Retrieves the script execution limit set by the host plugin, otherwise, - * we will pull from the PHP config file (php.ini) or set to 60 (1 minute) by default - * - * @return int - */ - public function get_time_limit() { - $php_limit = ini_get('max_execution_time'); - $default = !empty($php_limit) ? $php_limit : 60; - - return $this->is_time_limit_set() ? UPDRAFTPLUS_SET_TIME_LIMIT : $default; - } - - /** - * Retrieves the filter used by UpdraftPlus to log errors or certain events - * - * @return string - */ - public function get_logline_filter() { - return 'updraftplus_logline'; - } - - /** - * Retrieves the filter used by UpdraftPlus to pull the authentication headers - * - * @return string - */ - public function get_auth_headers_filter() { - return 'updraftplus_auth_headers'; - } - - /** - * Retrieves the filter used by UpdraftPlus to pull the remotecontrol login key - * - * @return string - */ - public function get_login_key_filter() { - return 'updraftplus_remotecontrol_login_key'; - } - - /** - * Retrieves the UpdraftCentral generated keys - * - * @return array|bool - */ - public function get_central_localkeys() { - return $this->get_option('updraft_central_localkeys'); - } - - /** - * Updates the UpdraftCentral's keys - * - * @param string $value Specify option value - * @param bool $use_cache Whether or not to use the WP options cache - * @param string $autoload Whether to autoload (only takes effect on a change of value) - * - * @return bool - */ - public function update_central_localkeys($value, $use_cache = true, $autoload = 'yes') { - if ($this->has_options()) { - return $this->update_option('updraft_central_localkeys', $value, $use_cache, $autoload); - } - - return false; - } - - /** - * Checks whether debug mod is set - * - * @return bool - */ - public function get_debug_mode() { - return $this->get_option('updraft_debug_mode'); - } - - /** - * Gets an RPC object, and sets some defaults on it that we always want - * - * @param string $indicator_name indicator name - * @return array|bool - */ - public function get_udrpc($indicator_name) { - global $updraftplus; - - if ($updraftplus) { - return $updraftplus->get_udrpc($indicator_name); - } - - return false; - } - - /** - * Used as a central location (to avoid repetition) to register or de-register hooks into the WP HTTP API - * - * @param bool $register True to register, false to de-register - * @return void - */ - public function register_wp_http_option_hooks($register = true) { - global $updraftplus; - - if ($updraftplus) { - $updraftplus->register_wp_http_option_hooks($register); - } - } - - /** - * Retrieves the WordPress version - * - * @return string|bool - */ - public function get_wordpress_version() { - global $updraftplus; - - if ($updraftplus) { - return $updraftplus->get_wordpress_version(); - } - - return false; - } - - /** - * Retrieves the class name of the host plugin - * - * @return string|bool - */ - public function get_class_name() { - global $updraftplus; - - if ($updraftplus) { - return get_class($updraftplus); - } - - return false; - } - - /** - * Returns the instance of the host plugin - * - * @return object|bool - */ - public function get_instance() { - global $updraftplus; - - if ($updraftplus) { - return $updraftplus; - } - - return false; - } - - /** - * Returns the admin instance of the host plugin - * - * @return object|bool - */ - public function get_admin_instance() { - global $updraftplus_admin; - - if ($updraftplus_admin) { - return $updraftplus_admin; - } - - return false; - } - - /** - * Retrieves the host plugin's Options class - * - * @return class|bool - */ - public function get_option_class() { - if ($this->has_options()) { - return UpdraftPlus_Options; - } - - return false; - } - - /** - * Checks whether the host plugin's Options class exists - * - * @return bool - */ - public function has_options() { - return class_exists('UpdraftPlus_Options'); - } - - /** - * Updates a specific option's value - * - * @param string $option Specify option name - * @param string $value Specify option value - * @param bool $use_cache Whether or not to use the WP options cache - * @param string $autoload Whether to autoload (only takes effect on a change of value) - * - * @return bool - */ - public function update_option($option, $value, $use_cache = true, $autoload = 'yes') { - if ($this->has_options()) { - return UpdraftPlus_Options::update_updraft_option($option, $value, $use_cache, $autoload); - } - - return false; - } - - /** - * Retrieves a specific option's value - * - * @param string $option Specify option name - * @param mixed $default Optional. The default value to return when option is not found - * - * @return mixed|bool - */ - public function get_option($option, $default = null) { - if ($this->has_options()) { - return UpdraftPlus_Options::get_updraft_option($option, $default); - } - - return false; - } - - /** - * Returns the current version of the host plugin - * - * @return string|bool - */ - public function get_version() { - global $updraftplus; - - if ($updraftplus) { - return $updraftplus->version; - } - - return false; - } - - /** - * Returns the admin page url of the host plugin - * - * @return string - */ - public function admin_page_url() { - if ($this->has_options()) { - return UpdraftPlus_Options::admin_page_url(); - } - - return ''; - } - - /** - * Returns the filesystem class of the host's plugin - * - * @return class|bool - */ - public function get_filesystem_functions() { - if ($this->has_filesystem_functions()) { - return UpdraftPlus_Filesystem_Functions; - } - - return false; - } - - /** - * Checks whether the filesystem class of the host plugin exists - * - * @return bool - */ - public function has_filesystem_functions() { - return class_exists('UpdraftPlus_Filesystem_Functions'); - } - - /** - * Returns the updraftplus.com destination path - * - * @return string - */ - public function get_udcom_destination() { - return defined('UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION') ? UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION : 'https://updraftplus.com/?updraftcentral_action=receive_key'; - } - - /** - * Returns the googlecloud callback url used by the analytics module - * - * @return string - */ - public function get_googlecloud_callback_url() { - return defined('UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics'; - } - - /** - * Returns the googlecloud client id used to connect to the google app - * - * @return string - */ - public function get_googlecloud_client_id() { - return defined('UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID') ? UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID : '306245874349-6s896c3tjpra26ns3dpplhqcl6rv6qlb.apps.googleusercontent.com'; - } - - /** - * Checks whether force debugging is set - * - * @return bool - */ - public function is_force_debug() { - return (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) ? true : false; - } - - /** - * Checks whether autobackup addon is present - * - * @return bool - */ - public function has_autobackup_addon() { - return class_exists('UpdraftPlus_Addon_Autobackup'); - } - - /** - * Get information on disk space used by an entity, or by UD's internal directory. Returns as a human-readable string. - * - * @param string $entity The entity (e.g. 'plugins'; 'all' for all entities, or 'ud' for UD's internal directory) - * @param string $format Return format - 'text' or 'numeric' - * @return string|integer|bool If $format is text, It returns strings. Otherwise integer value. - */ - public function get_disk_space_used($entity, $format = 'text') { - if ($this->has_filesystem_functions()) { - return UpdraftPlus_Filesystem_Functions::get_disk_space_used($entity, $format); - } - - return false; - } - - /** - * Checks whether autobackup is run by default - * - * @param bool $default The default value to set when the option is not found - * - * @return bool - */ - public function get_autobackup_default($default = true) { - return $this->get_option('updraft_autobackup_default', $default); - } - - /** - * Adds a section to the 'advanced tools' page for generating UpdraftCentral keys. Called by a filter - * inside the constructor method of this class. - * - * @return void - */ - public function debugtools_dashboard() { - global $updraftcentral_main; - - if (!class_exists('UpdraftCentral_Main')) { - if (defined('UPDRAFTCENTRAL_CLIENT_DIR') && file_exists(UPDRAFTCENTRAL_CLIENT_DIR.'/bootstrap.php')) { - include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/bootstrap.php'); - $updraftcentral_main = new UpdraftCentral_Main(); - } - } - - if ($updraftcentral_main) { - $updraftcentral_main->debugtools_dashboard(); - } - } - - /** - * Initializes required objects (if not yet initialized) for UpdraftCentral usage - * - * @return void - */ - private function maybe_initialize_required_objects() { - global $updraftplus, $updraftplus_admin; - - if (!class_exists('UpdraftPlus')) { - if (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/class-updraftplus.php')) { - include_once(UPDRAFTPLUS_DIR.'/class-updraftplus.php'); - $updraftplus = new UpdraftPlus(); - } - } - - if (!class_exists('UpdraftPlus_Admin')) { - if (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/admin.php')) { - include_once(UPDRAFTPLUS_DIR.'/admin.php'); - $updraftplus_admin = new UpdraftPlus_Admin(); - } - } - - if (!class_exists('UpdraftPlus_Options')) { - if (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/options.php')) { - require_once(UPDRAFTPLUS_DIR.'/options.php'); - } - } - - if (!class_exists('UpdraftPlus_Filesystem_Functions')) { - if (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/includes/class-filesystem-functions.php')) { - require_once(UPDRAFTPLUS_DIR.'/includes/class-filesystem-functions.php'); - } - } - } -} diff --git a/wordpress/wp-content/plugins/updraftplus/changelog.txt b/wordpress/wp-content/plugins/updraftplus/changelog.txt deleted file mode 100644 index 6ccb1fda484b903dde255b63b691b101cf7e1ff9..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/changelog.txt +++ /dev/null @@ -1,1945 +0,0 @@ -This file contains changelog entries that are not contained in the main readme.txt file (that file contains the newest entries). - -= 1.12.35 - 03/Mar/2017 = - -* FIX: Fix an issue causing corruption of interrupted Dropbox backups. All Dropbox users are recommended to update asap. -* TWEAK: Fix a regression that prevented information about a faulty WP scheduler from being shown in recent releases (incomplete fix in 1.12.34) -* TWEAK: submit_button() needs to be available (possible UpdraftCentral fatal when requesting filesystem creds) -* TWEAK: Remove an ES5 JavaScript construct (incompatible with some old browsers) -* TWEAK: Fix incorrect variable name in routine that triggered WP automatic update check -* TWEAK: Fix a logic error whereby if Google Drive and Google Cloud were both in use and partially set up, a notice about completing the setup of Cloud could fail to show - -= 1.12.34 - 23/Feb/2017 = - -* FEATURE: Added the ability to allow other plugins to call an automatic backup more easily -* FEATURE: Added the ability to select which tables you want to backup when using the 'Backup now' modal (Premium) -* FIX: Re-scanning a Dropbox that contained more than 1000 backup archives only fetched the first 1000 (this was previously awaiting on Dropbox fixing a related bug on their API servers). -* FIX: Escape table names to allow table names with hyphens in, when reading data -* FIX: The "Advanced Tools" tab was appearing with no contents if you chose an unwritable backup directory (regression) -* TRANSLATIONS: Remove bundled Swedish (sv), Spanish (Spain) (es_ES) and Czeck (Čeština‎, cs_CZ) translations, since these are now retrieved from wordpress.org. -* TWEAK: Prevent a JavaScript message being logged when loading UD infrastructure on non-UD settings pages (e.g. plugins that integrate to do backups via UD) -* TWEAK: Make it easier for other plugins to get/set UpdraftPlus options with less code -* TWEAK: Make sure that the get_plugins() function is available before using it when generating notices -* TWEAK: Add the updraftplus_exclude_directory and updraftplus_exclude_file filters allowing arbitrary backup exclusions from code -* TWEAK: Add a work-around for a bug in some server/Firefox combinations in handling of the Content-Length header with non-ASCII characters -* TWEAK: Cause an informational message to be shown in the Rackspace module if php-json is not enabled -* TWEAK: Fix a regression that prevented information about a faulty WP scheduler from being shown in recent releases -* TWEAK: Made alert regarding plupload's 'HTTP -200' error, when upload of file fails, more informative. -* TWEAK: Internal changes to the remote storage method API (future improvements which build on these are planned) - -= 1.12.32 - 26/Jan/2017 = - -* FEATURE: Add UpdraftCentral (https://updraftcentral.com) UpdraftVault listener -* FEATURE: Encryption and decryption is now chunked, meaning that large databases of any size can be encrypted without being prevented by memory limits -* FIX: Fix a bug whereby if a backup set containing a manual "more files" element was imported via a remote scan, then an error would show concerning it when attempting to restore. -* FIX: On certain combinations of changing the "more files to backup" settings, these changes might not be reflected in the "Backup Now" dialog without a page reload -* FIX: Remove a PHP 5.5+-only construction that crept into 1.12.31 -* TWEAK: Allow UpdraftCentral command classes to provide commands via the __call method -* TWEAK: Move the existing backups table into the templating system -* TWEAK: When trying to restore before cleaning up a previous restore, the detailed error message shown needed tweaking -* TWEAK: Some refactoring of the dashboard JavaScript, to abstract/harmonise all AJAX calls -* TWEAK: Removed the triple click and replaced it with standard double click -* TWEAK: Some refactoring of the UpdraftCentral command interface, to facilitate reduction of duplicated dashboard control code -* TWEAK: One less HTTP round-trip when deleting from the dashboard -* TWEAK: Updated advanced tools to allow UpdraftCentral to use wipe settings and export / import -* TWEAK: Revamped the 'Premium / Extensions' tab in the free version -* TWEAK: Work around HTTP 400 error from Dropbox on servers with several-year old version of curl, caused by bad interaction between curl and Dropbox over a specific header -* TWEAK: Add a notice advising of WP-Optimize (https://wordpress.org/plugins/wp-optimize/) to the available notices -* TWEAK: Prevent an unwanted PHP log notice when using Google Drive -* TWEAK: More file directories are now added using a directory browser -* TWEAK: Update plugin update checker library (paid versions) to version 3.1, which fixes some PHP 7 issues - -= 1.12.30 - 23/Dec/2016 = - -* FIX: Fix a Dropbox APIv2 issue where paths containing certain characters were incorrectly being encoded -* FEATURE: Add UpdraftCentral (https://updraftcentral.com) comment-control and advanced tools listeners -* TWEAK: Starting an operation to retrieve a remote backup from UpdraftCentral succeeded, but gave a UI error in UC when doing so -* TWEAK: Fix a Dropbox APIv2 issue where Team storage displayed an incorrect value -* TWEAK: Support for the new AWS S3 Canada Central 1 and London regions -* TWEAK: Some re-factoring of the settings page output code for easier maintenance -* TWEAK: Some re-factoring of the notices code, to allow re-use in other projects -* TWEAK: Make sure that a UpdraftCentral_Commands class is available before loading any external command classes, so that they can rely on its presence - -= 1.12.29 - 22/Nov/2016 = - -* FIX: Fix a PHP error in the notices code (regression in 1.12.28) -* FIX: Manual database search and replace now outputs logged operation information (regression in 1.12.28) - -= 1.12.28 - 21/Nov/2016 = - -* TWEAK: The UPDRAFTPLUS_DROPBOX_API_V1 constant will be ignored from 28th June 2017 (when Dropbox turn off that API entirely) -* TWEAK: A new internal infrastructure for handling user-visible notices in the dashboard and reports -* TWEAK: Small layout tweak to fix a malformatted error message - -= 1.12.27 - 17/Nov/2016 = - -* FIX: The WP 4.7 compatibility tweak in 1.12.26 introduced a regression that caused the question to appear when unwanted on other WP versions. - -= 1.12.26 - 16/Nov/2016 = - -* COMPATIBILITY: On WordPress 4.7, the behaviour of shiny updates has changed, necessitating a small tweak to prevent an unwanted "do you really want to move away from this page?" question from the browser on the updates/plugins pages in some situations. -* TWEAK: When the Dropbox quota state seems to imply that the next upload will fail, do not register this as an error before it actually happens. -* TWEAK: When an error occurs when re-scanning Dropbox, make sure the error details are logged in the browser developer console -* FIX: Fix ability to rescan a Dropbox sub-folder (regression in 1.12.25) - -= 1.12.25 - 12/Nov/2016 = - -* COMPATIBILITY: Dropbox APIv2 capability (see: https://updraftplus.com/dropbox-api-version-1-deprecation/) in 1.12.24 was not complete - this release now avoids all APIv1 use -* TWEAK: The 'site information' advanced tool now contains information on loaded Apache modules. -* TWEAK: Small layout tweak to fix a malformatted error message - -= 1.12.24 - 08/Nov/2016 = - -* FIX: When importing a single site into a multisite install as a new site (experimental feature), the main multisite URL was being incorrectly adjusted -* FIX: Fix a bug with remote scans not returning more database archives correctly -* COMPATIBILITY: Add Dropbox APIv2 capability (see: https://updraftplus.com/dropbox-api-version-1-deprecation/) -* FEATURE: Look for mysqldump.exe in likely locations on Windows, for faster database backups -* TWEAK: UpdraftVault, Amazon S3 and DreamObjects downloaders have been rewritten without race conditions -* TWEAK: Introduce an abstraction layer for reporting on the status of restore operations -* TWEAK: Deleting remote backup sets from the dashboard is now batched for sets with many archives, to avoid potential PHP timeouts on slow remote services -* TWEAK: Updated bundled phpseclib library to version 1.0.4 -* TWEAK: Introduce an internal templating layer, for improved long-term maintainability -* TWEAK: When importing a single site into a multisite install as a new site, remove any cron entries for backup runs on the new site -* TWEAK: Fix an inconsequential off-by-one in the chunked downloading algorithm so that the behaviour is as documented -* TWEAK: Improve accessibility of Labelauty components with keyboard navigation -* TWEAK: Tweak the algorithm for scheduling resumptions, to improve efficiency in the (once) seen corner-case of PHP usually having a predictable run-time, but with an instance of a much longer run-time -* TWEAK: Slightly more logging when an S3 error condition occurs, allowing easier diagnosis -* TWEAK: Add support for the new US East (Ohio) region to S3 -* TWEAK: OneDrive authentication can now detect a block by CloudFlare, and direct the user accordingly -* TWEAK: If there are remote storage methods needing authentication, then pop up a box showing this to the user - so that it does not rely on them spotting the dashboard notice or having read the instructions - -= 1.12.23 - 04/Oct/2016 = - -* FIX: Fix a bug in URL replacement when cloning from a flat configuration to a WP-in-own-directory configuration -* FIX: The button for testing connections to extra databases added to the backup was not working -* FIX: Direct dashboard logins from UpdraftCentral were not working on WP 3.2 - 3.4 sites -* COMPATIBILITY: Will upgrade Dropbox OAuthv1 tokens to OAuthv2 (to handle Dropbox API v1 deprecation in summer 2017) -* TWEAK: Deleting an already-deleted backup set from UpdraftCentral now produces a more informative error message -* TWEAK: When restoring only a single site out of a multisite install, store less data in memory on irrelevant tables, and do less logging when skipping tables -* TWEAK: Update bundled UDRPC library to version 1.4.9 - fixes a bug with the admin URL used for contact via UpdraftCentral on multisite -* TWEAK: Explicitly store the UpdraftPlus object as a global -* TWEAK: Prevent a pointless "unsaved settings" warning if settings were changed then the 'wipe' button used -* TWEAK: When using the Importer add-on, allow backups from WordPress Backup to Dropbox to be wrapped in an extra 'wpb2d' folder -* TWEAK: Strengthen protections against resuming an already-complete backup after migration on servers with misbehaving WP schedulers -* TWEAK: Touch already-existing but incomplete files being downloaded, to reduce possibility of two processes downloading at once -* TWEAK: Add a link to more information about UpdraftCentral in the advanced tool -* TWEAK: The UPDRAFTPLUS_MYSQLDUMP_EXECUTABLE define can now be used on Windows (you will need to define a path to take advantage of it) -* TWEAK: Introduce the UPDRAFTPLUS_SKIP_CPANEL_QUOTA_CHECK constant to allow skipping of trying to check cPanel quota - -= 1.12.21 - 08/Sep/2016 = - -* FIX: Fix a bug in the updater code that caused updates checks to be run more often than intended -* TWEAK: Improve/tidy layout of the "Advanced Tools" tab -* TWEAK: Make it more obvious in the file uploading widget when an upload is 100% complete -* TWEAK: Prevent spurious OneDrive message being shown when re-scanning remote storage and not using OneDrive -* TWEAK: OneDrive storage now uses the refresh token yes frequently (less HTTP calls) - -= 1.12.20 - 29/Aug/2016 = - -* FEATURE: OpenStack uploads (including Rackspace Cloudfiles) can now adapt their upload rate to network conditions, leading to much faster uploads on many networks -* FEATURE: Updated the OneDrive configuration to make it easier to setup. A custom Microsoft Developer App is no longer required -* FEATURE: The "Advanced Tools" tab now has tools for importing and exporting settings -* TWEAK: Honour the "do not verify SSL certificates" setting with WebDAV storage on PHP 5.6+ -* TWEAK: When there's a connection problem to updraftplus.com when claiming licences, provide more error info and guidance -* TWEAK: In particular circumstances (malfunctioning WP scheduler, expert option to keep backups after despatching remotely selected (non-default)), zips could be sent to Google Drive more than once -* TWEAK: Tweak issue in 1.12.18 with automatic backup pop-up appearing under another pop-up if you update themes via the themes pop-up (instead of the direct link) -* TWEAK: When rescanning remote storage, don't log a potentially confusing message for an unconfigured storage module -* TWEAK: Show a visual indicator and advice if an invalid hostname is entered for WebDAV -* TWEAK: Removed the no-longer-useful debug backup buttons -* TWEAK: Add a message when generating a key on a server without php-openssl, with information about how to make it faster -* TWEAK: Prevent PHP installs which print PHP logging information to the browser from messing up the WebDAV settings in some situations -* TWEAK: If PHP reports the current memory limit as a non-positive integer, do not display any message to the user about a low memory limit -* TWEAK: If the user deletes their Google API project, then show clearer information on what to do when a backup fails -* TWEAK: If you changed your OneDrive client ID, UD will now more clearly advise you of the need to re-authenticate -* COMPATABILITY: Updated the OneDrive authentication procedure to make it compatible with the new Microsoft Developer Apps - -= 1.12.18 - 03/Aug/2016 = - -* TWEAK: When Microsoft OneDrive quota is insufficient, the advisory message from UD now includes the available quota (as well as the used) -* FEATURE: The Azure add-on/Premium now supports new-style Azure storage, as well as classic -* FEATURE: The Rackspace enhanced wizard can now be accessed via UpdraftCentral -* TWEAK: Fix a regression in recent WP versions which caused remote keys to not always be retained after a migration -* TWEAK: When logging Azure upload locations, include the account name -* TWEAK: Make the entering of settings for WebDAV more user-friendly -* TWEAK: Update bundled select2 to version 4.0.3 -* TWEAK: Clarify error message when a 'more files' location is not found -* TWEAK: Add redirection_404 to the list of tables likely to be large, and not needing search/replacing -* COMPATIBILITY: Compatible with WP 4.6 (previous paid versions have incompatibilities with the changes made to 'shiny updates/installs/deletes' in WP 4.6) - -= 1.12.17 - 19/Jul/2016 = - -* FIX: Previous free release included empty translation files -* TWEAK: Add 'snapshots' to the default list of directories to exclude from the uploads backup (is used by another backup plugin - avoid backups-of-backups) -* TWEAK: Add et_bloom_stats to the list of tables likely to be large, and not needing search/replacing - -= 1.12.16 - 07/Jul/2016 = - -* TWEAK: Log FTP progress upload less often (slight resource usage improvement) -* TWEAK: For multi-archive backup sets, the HTML title attribute of download buttons had unnecessary duplicated information -* TWEAK: Improve OneDrive performance by cacheing directory listings -* TWEAK: Detect and handle a case in which OneDrive incorrectly reports a file as incompletely uploaded -* FIX: OneDrive scanning of large directories for existing backup sets was only detecting the first 200 files - -= 1.12.15 - 06/Jul/2016 = - -* TWEAK: S3 now supports the new Mumbai region -* TWEAK: If the user enters an AWS/S3 access key that looks prima facie invalid, then mention this in the error output -* TWEAK: Make the message that the user is shown in the case of no network connectivity to updraftplus.com when connecting for updates (paid versions) clearer -* TWEAK: Extend cacheing of enumeration of uploads that was introduced in 1.11.1 to other data in wp-content also -* TWEAK: Avoid fatal error in Migrator if running via WP-CLI with the USER environment variable unset -* TWEAK: When DB_CHARSET is defined but empty, treat it the same as if undefined -* TWEAK: Add updraftplus_remotesend_udrpc_object_obtained action hook, allowing customisation of HTTP transport options for remote sending -* TWEAK: Introduced new UPDRAFTPLUS_RESTORE_ALL_SETTINGS constant to assist in complicated load-balancing setups with duplicate install on the same URL -* TWEAK: Update bundled tripleclick script to fix bug in teardown handler -* TWEAK: Update bundled UDRPC library to version 1.4.8 -* TWEAK: Patch Labelauty to be friendly to screen-readers -* TWEAK: Suppress the UD updates check on paid versions that immediately follows a WP automatic core security update -* TWEAK: Handle missing UpdraftCentral command classes more elegantly -* FEATURE: Endpoint handlers for forthcoming updates and user mangement features in UpdraftCentral -* TRANSLATIONS: Remove bundled German (de_DE) translation, since this is now retrieved from wordpress.org -* FIX: Fix inaccurate reporting of the current Vault quota usage in the report email -* FIX: Fix logic errors in processing return codes when no direct MySQL/MySQLi connection was possible in restoring that could cause UpdraftPlus to wrongly conclude that restoring was not possible - -= 1.12.13 - 07/Jun/2016 = - -* TWEAK: Default the S3 secret key field type to 'password' instead of 'text' -* TWEAK: Do more checks for active output buffers prior to spooling files to the browser (to prevent memory overflows) -* TWEAK: Update bundled UDRPC library to version 1.4.7 - -= 1.12.12 - 25/May/2016 = - -* FIX: When restoring a plugins backup on multisite, old plugins were inactivated but not always removed -* TWEAK: Use POST instead of GET for OneDrive token requests - some new accounts seem to have begun requiring this -* TWEAK: When backing up user-configured directories, don't log confusing/misleading messages for unzippable directory symlinks -* TRANSLATIONS: wordpress.org is now serving up translations for fr_FR, pt_PT and ro_RO, so these can/have been removed from the plugin zip (1.2Mb released) - -= 1.12.11 - 19/May/2016 = - -* FIX: 1.12.8 (paid versions only) contained a regression that prevented S3 access if the user had a custom policy that did not include location permission. This fix means that the work-around of adding that permission to the policy is no longer required. -* FIX: Fix a regression in 1.12.8 that prevented non-existent DreamObjects buckets from being created -* FIX: Fix inaccurate reporting of the current Vault quota usage in the report email since 1.12.8 -* FIX: The short-lived 1.12.10 had a duplicate copy of the plugin in the release zip -* TWEAK: Detect a particular obscure PHP bug in some versions that is triggered by the Amazon S3 SDK, and automatically switch to the older SDK if it is hit (N.B. Not compatible with Frankfurt region). -* TWEAK: Audit/update all use of wp_remote_ functions to reflect API changes in the upcoming WP 4.6 -* TWEAK: Tweak to the settings saving, to avoid a false-positive trigger of a particular rule found in some mod_security installs -* TWEAK: Update bundled UDRPC library to version 1.4.5 - -= 1.12.9 - 11/May/2016 = - -* FIX: In yesterday's 1.12.8, some previously accessible Amazon S3 buckets could no longer be accessed - -= 1.12.8 - 10/May/2016 = - -* FEATURE: Support S3's "infrequent access" storage class (Premium) -* FIX: Fix bug in SFTP uploading algorithm that would corrupt archives if a resumption was necessary -* TWEAK: Add information on UpdraftVault quota to reporting emails -* TWEAK: Update the bundled AWS library to version 2.8.30 -* TWEAK: Update the bundled Symfony library to version 2.8.5 -* TWEAK: Update the bundled phpseclib library to version 1.0.2 (which includes a fix for SFTP on PHP 5.3) -* TWEAK: Improve the overlapping runs detection when writing out individual database tables, for helping servers with huge tables without mysqldump -* TWEAK: Prevent restoration from replacing the local record of keys of remote sites to send backups to (Migrator add-on) -* TWEAK: Re-order the classes in class-zip.php, to help misbehaving XCache (and perhaps other opcode cache) instances -* TWEAK: Do not include transient update availability data in the backup (which will be immediately out-of-date) -* TWEAK: Updated the URLs of various S3-compatible providers to use SSL, where available -* TWEAK: Added an endpoint drop-down for Dreamobjects, using their new/updated endpoint (currently only one choice, but they will have more in future) -* TWEAK: Suppress a log message from UpdraftVault when that message is not in use -* TWEAK: When key creation times out in the Migrator, display the error message in the UI - -= 1.12.6 - 30/Apr/2016 = - -* FIX: UpdraftVault quota usage was being shown incorrectly in recounts on sites connected to accounts backing up multiple sites -* TWEAK: In accordance with Barracuda's previous announcement, copy.com no longer exists - https://techlib.barracuda.com/CudaDrive/EOL -* TWEAK: Allow particular log lines to be cancelled -* TWEAK: Explicitly set the separator when calling http_build_query(), to prevent problems with non-default configurations -* TWEAK: Tweak the algorithm for sending data to a remote UD installation to cope with eventually-consistent filesystems that are temporarily inconsistent -* TWEAK: Make the automatic backups advert prettier -* TWEAK: Detect and combine file and database backups running on different schedules which coincide -* TWEAK: Update bundled Select2 to version 4.0.2 -* TWEAK: Update UDRPC library to version 1.4.3 -* TWEAK: Update dashboard notices for 2017/18 - -= 1.12.5 - 08/Apr/2016 = - -* TWEAK: (Paid versions) - tweak the updater class so that it sends the information that updraftplus.com needs in order to correctly advise about language pack update availability. (If you are continuously seeing the same language pack update offered, then this may continue for a few more hours - please be patient!). -* TWEAK: Detect another case and deal with an HTTP 413 response when sending chunked data on a direct site-to-site migration - -= 1.12.4 - 07/Apr/2016 = - -* FEATURE: Faster uploads to Dropbox, via adapting to network conditions: https://updraftplus.com/faster-dropbox-uploads/ -* FEATURE: (Paid versions) Plugin now no longer bundles all translations - instead, WP's mechanism for downloading single translations, as/when needed, is used (reduces on-disk size by 12MB=36%)). -* FIX: Deal with some database encryption phrases with special characters that were incorrectly processed -* FIX: Deal with an error in the advanced retention rules processing code, that could lead to keeping the wrong backup set -* FIX: Fix an unescaped string which could cause a JavaScript notice on the UD settings page in some non-English translations -* FIX: The minimum allowed value for the split size was not previously taking effect when saving settings -* TWEAK: When connection to an updraftplus.com UpdraftCentral dashboard, allow use of the alternative connection method -* TWEAK: Suppress some known deprecation warnings on PHP 7 -* TWEAK: Show OpenSSL/Mcrypt info in the log + debug info -* TWEAK: Detect a completed upload to Dropbox masked by a race event from the WP scheduler -* TWEAK: The drop-down in the 'Migrate' dialog will now update on a rescan without needing a page reload -* TWEAK: (Paid versions) Update bundled plugin updater class (Yahnis Elsts) to version 3.0 -* TWEAK: Add woocommerce_sessions to the list of tables of transient data -* TWEAK: When saving settings, if invalid input is adjusted, this will be reflected back to the UI without a page load (not on all elements) -* TWEAK: When saving settings, the schedule was written twice on non-multisite installs - -= 1.12.2 - 30/Mar/2016 = - -* TWEAK: When testing Amazon S3 bucket accessibility, slightly improve one of the corner-case checks -* TWEAK: When creating an encryption key for direct Migration, or UpdraftCentral, allow the user to choose their key size (this helps with PHP installs lacking both OpenSSL and GMP, in which case key creation can be really slow) -* TWEAK: Detect another case and deal with an HTTP 413 response when sending chunked data on a direct site-to-site migration - -= 1.12.1 - 24/Mar/2016 = - -* TWEAK: Update the bundled remote communications library - needed for some UpdraftCentral users - -= 1.12.0 - 23/Mar/2016 = - -* FEATURE: Compatible with the forthcoming (very soon!) UpdraftCentral remote control panel -* COMPATIBILITY: Tested + supported on the upcoming WordPress 4.5 -* FIX: On some setups, if no remote storage was configured (not recommended), then old backups were not being pruned -* FIX: Make FTP active mode (very rarely seen) work correctly again -* TWEAK: Added a hint to FTP users who appear to be firewalled on only the data channel when attempting to use encrypted FTP -* TWEAK: Improve detection of the WordPress scheduler duplicating periodic events when the server is overloaded -* TWEAK: Simplify main tab layout upon first use -* TWEAK: Add some previously unbundled licence files -* TWEAK: Prevent a couple of PHP notices being logged when running a manual search/replace -* TWEAK: Add a filter to allow more over-riding of pruning - -= 1.11.29 - 27/Feb/2016 = - -* FIX: When saving settings on multisite, some connections to remote storage could drop and need to be re-made -* FIX: Fix an inoperative button in the Clone dialog box -* FIX: Fix an error upon automatic backups (Premium) in 2.11.28 -* TWEAK: Updated readme to reflect > 700,000 active sites -* TWEAK: When cloning a site and mod_rewrite is not available, give a warning pre-restore -* TWEAK: Options saving on multisite is now much more efficient (in terms of database requests required) -* TWEAK: Improve the scheduling algorithm in the case of hosts that allow very long runs times, and a network outage on the cloud storage -* TWEAK: When connecting to updraftplus.com to claim a licence (paid versions), use the newer JSON-based protocol -* TWEAK: Many and various internal improvements to structure of the admin page HTML, CSS and JS -* TWEAK: The boxes for adding extra addresses for reporting, and extra DBs, now fade in - -= 1.11.27 - 17/Feb/2016 = - -* FEATURE: Automatic backups can take place before updates commissioned via WordPress.Com/JetPack remote site management (requires a not-yet-released version of JetPack - all current releases are insufficient, so please don't file reports about this yet) -* FIX: Fixed a further logic error in the advanced backup retention options, potentially relevant if you had more than one extra rule, affecting the oldest backups -* TWEAK: Resolve issue on some sites with in-dashboard downloads being interfered with by other site components -* TWEAK: Auto-backups now hook to a newly-added more suitable action, on WP 4.4+ (https://core.trac.wordpress.org/ticket/30441) -* TWEAK: Make WebDAV library not use a language construct that's not supported by HHVM -* TWEAK: Change options in the "Backup Now" dialog as main settings are changed -* TWEAK: Show the file options in the "Backup Now" dialog if/when alerting the user that they've chosen inconsistent options -* TWEAK: When pruning old backups, save the history to the database at least every 10 seconds, to help with sites with slow network communications and short PHP timeouts - -= 1.11.26 - 13/Feb/2016 = - -* TWEAK: Prevent HTTP 500 download errors on some very large file/hosting setups -* TWEAK: A tiny number of users had a badly laid-out settings screen until they refreshed their browser cache. This release prevents that. - -= 1.11.24 - 10/Feb/2016 = - -* FIX: Fixed further logic errors in the advanced backup retention options, potentially relevant if you had more than one extra rule -* TWEAK: Saving of settings is now done over AJAX (i.e. without a page reload) -* TWEAK: In-dashboard downloads now process the HTTP Range: header, allowing resumption of failed downloads via the browser -* TWEAK: Tweak 'Existing Backups' table CSS, to allow more entities per row -* TWEAK: Warn copy.com users of Barracuda ending the service - https://techlib.barracuda.com/CudaDrive/EOL -* TWEAK: Rename the 'hidden' CSS class, to prevent clashes with other plugins/themes which load their CSS code onto UD's page (which they shouldn't be doing) -* TWEAK: Fix newsletter sign-up link -* TWEAK: Log and triple-click summary now mentions the total size of the backup (i.e. total of the compressed backup set) -* TWEAK: Try to detect a very rare case of recoverable database read failure, and schedule a re-try -* TWEAK: Suppress unnecessary warning message when Dropbox account info checking fails -* TWEAK: Attempt to restart a large OneDrive upload in a particular case seen when OneDrive's end seems to get into confusion about state -* TWEAK: Various bits of internal re-factoring to support future improvements - -= 1.11.23 - 26/Jan/2016 = - -* FIX: When migrating a sub-folder based multisite into a non-root install with different relative path to the source multisite (I can't think of any good reasons to do this), the search/replace could leave sub-sites unreachable without manual correction -* FIX: Logic errors in the advanced backup retention options could lead to the oldest backups being deleted prematurely, and some backups not being deleted when they were overdue for deletion -* FIX: Amazon S3 bucket creation wizard (in the S3 enhanced add-on) was not honouring the chosen region for new buckets -* FIX: Upon restoration over an existing site, inactive plugins could remain post-restore (bug introduced in 1.11.20) -* TWEAK: Various internal re-organisations, to improve modularity/re-use of the code -* TWEAK: Internal CSS re-organisation to make future layout changes easier -* TWEAK: The "stop" link in the backup progress indicator now halts the backup asap, instead of at the next scheduled resumption -* TWEAK: Clarify the course of action needed if you attempt a Dropbox backup without Curl -* TWEAK: Add support for the new Asia Pacific (Seoul) region to Amazon S3 -* TWEAK: Make the automatic backup option box appear on the updates page for users who can update plugins or themes (not just core - previously it was assumed that these would always go together in the real world, but some managed hosts are now removing the core update capability from the default admin user, whilst leaving the others) -* TWEAK: Change default zip split size to 400Mb on new installs -* TWEAK: Clean up use of composer, to conform to proper usage standards, and update to current version (to avoid causing a problem for plugins using PSR-4 autoloaders) -* TWEAK: Provide direct links to cart when choosing UpdraftPlus Vault storage -* TWEAK: Add debug.log to the default exclusions in wp-content (when people leave debug logging on and forget, it can get huge) -* TWEAK: On multisite, make sure that the site/blogs tables are placed early in the backup (assists with quickly scanning backup info) -* TWEAK: Update to phpseclib 1.0.1 -* TWEAK: Prevent a PHP notice when using SCP -* TWEAK: Add new termmeta table to the default list of core tables (which is usually automatically detected) - -= 1.11.21 - 28/Dec/2015 = - -* TWEAK: If there's a problem connecting to UpdraftPlus Vault, in some situations the information on the cause was not easily readable -* TWEAK: Slightly more logging on failed OneDrive operations, to aid problem-solving -* TWEAK: Add wysija_email_user_stat to the list of potentially huge non-critical tables (which can get skipped in an emergency) -* FIX: Package Pear/Exception.php, so that servers without it already can use Microsoft Azure blob storage -* FIX: Prevent PHP fatal error on admin area in some restore scenarios - -= 1.11.20 - 21/Dec/2015 = - -* FEATURE: WordPress multisite backup administrators can now selectively restore data from a chosen site, instead of restoring the entire WordPress install - https://updraftplus.com/selectively-restoring-on-wordpress-multisite/ (requires WP 3.5+, UpdraftPlus Premium) -* FEATURE: Import a WordPress single-site backup into WordPress multisite, as a new site (requires WP 3.5+, UpdraftPlus Premium) - https://updraftplus.com/information-on-importing-a-single-site-wordpress-backup-into-a-wordpress-network-i-e-multisite/ -* FIX: Properly encode folder paths with Copy.Com, allowing some previously prevented folder names to work -* FIX: In-dashboard decryption using some decryption keys with URL-encodable characters failed -* FIX: Prevent PHP fatal error on settings page on a buggy old PHP version (5.2.10) when Google Cloud storage add-on not present -* FIX: When using multiple remote storage providers, a race condition could lead to some old backups not being deleted on the storage not uploaded to last -* FIX: Views are now placed after tables in the database backup -* FIX: In-page uploader widget was not working on sub-domain based multisites in some dashboard access scenarios -* FIX: Package Net/URL2 (PEAR), so that servers without it already can use Microsoft Azure blob storage -* TWEAK: Upgrade Microsoft OneDrive API usage to latest version -* TWEAK: Automatic backups are now hooked into the themes page in the network admin on WP multisite installs -* TWEAK: Dashboard messages were not being clearly shown when trying to use UpdraftPlus Vault without PHP Curl available -* TWEAK: Protect against other plugins loading incompatible Google SDKs when Google Cloud is being used -* TWEAK: When trying to use S3, DreamObjects or UpdraftPlus Vault without PHP Curl available, make the cause of the problem more obvious -* TWEAK: When sending data to remote site, keep re-trying on receipt of an HTTP 413 (request entity too large) down to 64Kb (instead of previous 128Kb) - a webserver was seen in the wild configured at this level -* TWEAK: Detect the WordPress scheduler invoking a scheduled backup multiple times, in some cases where the existing semaphore lock wasn't helping (because the backup already finished, or the WP scheduler invoked multiple instances of the same event minutes apart - apparently possible when very heavily overloaded) -* TWEAK: Detect an inconsistent semaphore locking state, and fix it (apparently only possible upon unexpected server crash) -* TWEAK: Provide a button to cancel (not just continue) an interrupted restore -* TWEAK: Work around buggy Ubuntu PHP versions - https://bugs.launchpad.net/ubuntu/+source/php5/+bug/1315888 -* TWEAK: Make sure that backup options get passed on with continuation data, when resuming an interrupted restore -* TWEAK: Catch a few untranslated strings (in the decryptor widget for encrypted backups) -* TWEAK: Log more information if a connection to UpdraftPlus Vault fails -* TWEAK: The internal info shown when triple-clicking a backup set's date had messed-up formatting - -= 1.11.18 - 25/Nov/2015 = - -* FEATURE: On hosts with low timeouts that kill restore operations half-way though, provide an obvious button on the dashboard to immediately resume; see: https://updraftplus.com/resuming-interrupted-restores/ -* FEATURE: Usability improvements and ability to select file components in the 'Backup Now' dialog - https://updraftplus.com/improvements-to-the-backup-now-dialog-box/ -* FEATURE: Full support for Microsoft Azure blob storage (UpdraftPlus Premium) -* FEATURE: Allow all files beginning with a defined prefix to be excluded from the backup by inputting (for example) prefix:someprefix_,prefix:someotherprefix- in your exclusion settings - see: https://updraftplus.com/faqs/how-can-i-exclude-particular-filesdirectories-from-the-backup/ -* FEATURE: UpdraftPlus Premium can now restore backups created by "Dropbox Backup" by WPAdm -* COMPATIBILITY: Tested/supported on the forthcoming WordPress 4.4 -* TWEAK: Faster zip file creation on PHP 7 with ZipArchive - https://updraftplus.com/faster-zip-file-creation-with-the-php-7-zip-engine/ -* TWEAK: Improve settings tab: remove headings, tweak a few wordings, move "remote storage" section further up -* TWEAK: Introduce UPDRAFTPLUS_SET_TIME_LIMIT internal constant -* TWEAK: Quote the table name passed to MySQL in DESCRIBE statement -* TWEAK: Prevent a PHP notice being logged during initial updates connection, and another when restoring third-party databases -* TWEAK: Style previously unstyled button in some auto-backup scenarios -* FIX: A few settings were not being deleted by the "Wipe Settings" button. -* FIX: Importer would not correctly handle third-party backups where the files and zip were both in zip format, separately, and where they were restored together -* FIX: With multi-archive backup sets, files in the top level of a backup of WP core or 'others' were not restored by an in-dashboard restore if they over-wrote an existing non-identical file if they were not in the first archive - -= 1.11.17 - 13/Nov/2015 = - -* FIX: Resolve a conflict with "Simple Calendar" (formerly "Google Calendar Events") since their re-written 3.0 release, when using Google Drive storage - -= 1.11.15 - 28/Oct/2015 = - -* FEATURE: Google Cloud Storage support (UpdraftPlus Premium) -* FIX: Automatic backups of WordPress core prior to WP core upgrade in recent versions were including non-WP core files -* FIX: OwnCloud 8.1's WebDAV server responds differently, breaking UD's communications: restore the ability to backup to OwnCloud WebDAV -* TWEAK: Allow use of the Meta key for selecting multiple backup sets (as well as Control) -* TWEAK: When sending backup data directly site-to-site (when migrating), handle the (very rare) case where a remote server complains of the chunk size after accepting previous chunks of the same size -* TWEAK: Add message to final log line when sending backup set directly to a remote site, reminding the user of what to do next. -* TWEAK: Tweak zip-splitting algorithm, to prevent delayed split on resumption when final file in the last-created zip is gigantic -* TWEAK: By default, exclude directories that appear to be the UpdraftPlus internal directory of a site stored in a sub-directory when backing up WordPress core -* TWEAK: In the debugging output, show more clearly when Curl is not installed -* TWEAK: Remove trailing slashes from what WP returns as the uploads/plugins directories, in case the user has specified a manual directory over-ride and erroneously added a trailing slash -* TWEAK: Replace all remaining http:// links to updraftplus.com with https:// -* TWEAK: Raise some of the Google Drive network timeouts -* TWEAK: Suppress an internal PHP notice when pruning backups in some circumstances -* TRANSLATIONS: Various updated translations - -= 1.11.12 - 29/Sep/2015 = - -* FEATURE: More sophisticated rules for retention/deletion (UpdraftPlus Premium) - https://updraftplus.com/more-sophisticated-backup-retention/ -* FEATURE: Delete multiple backups at once - https://updraftplus.com/deleting-multiple-backups/ -* FEATURE: When choosing a monthly backup, you can now choose the starting date (e.g. choose 17th, not just choose the next week-day, e.g. next Monday) -* FEATURE: You can exclude files with any particular extension by using the constant UPDRAFTPLUS_EXCLUDE_EXTENSIONS (comma-separate different extensions), or by inputting (for example) ext:.zip,ext:.mp4 in your exclusion settings. -* FEATURE: Tested and supported on the forthcoming PHP 7.0 -* FIX: SFTP uploads could hang after finishing, if more than one attempt was needed to upload the file -* FIX: Stop causing JavaScript errors on WordPress 3.2 on the plugins page -* TWEAK: UI improvement when choosing multiple storage options - https://updraftplus.com/a-prettier-way-to-choose-remote-storage-options/ -* TWEAK: The storage selection drop-down (free version) now has icons to make it easier on the eye -* TWEAK: Use UpdraftPlus Vault logo -* TWEAK: Replace target="_new" with target="_blank" when opening new browser ports, to be more standards-compliant -* TWEAK: Tweak the auto-split algorithm again to catch another case where it would have been better to split in a low-resource situation -* TWEAK: When checking the backup integrity, allow for a multisite to not have a root options table; check sitemeta instead (unlikely, but theoretically possible) -* TWEAK: Raise default Google Drive network timeout from 15 seconds - it's too possible to hit this on a slow uplink (e.g. congested ADSL) -* TWEAK: Upgrade the bundled Google SDK to the most recent release (1.1.4) -* TWEAK: Add previously-untranslated string -* TWEAK: Suppress a PHP notice relating to a constant that needed quoting -* TWEAK: Turn off reporting of PHP deprecation conditions if using phpseclib on PHP 7 (can break communications - phpseclib uses PHP4-style constructors) -* TRANSLATIONS: Various updated translations - -= 1.11.9 - 04/Sep/2015 = - -* FIX: Dropbox introduced an un-documented, un-announced change to their server, which caused new site authentications in UpdraftPlus to no longer work. Now fixed with this release. -* FIX: If backing up multiple extra directories under "more files", if large directories required a resumption, then inclusion of the files multiple times in the backup was possible. -* TWEAK: Tweak the auto-split algorithm to not avoid firing in a particular case (that relies on a sequence of unlikely I/O events, seen on GoDaddy) if there's no resumption scheduled -* TWEAK: Add mysql.sql to the (changeable) default configuration for excludes from wp-content - on WP Engine this is an unreadable file that they create that thus produces a backup warning -* TWEAK: Add a dashboard warning (+ link to documentation) if UD appears to be incompletely installed -* TWEAK: Add UPDRAFTPLUS_WEBDAV_NEVER_CHUNK constant for WebDAV servers that return the wrong error code when chunking fails -* TWEAK: Introduce a UPDRAFTPLUS_REMOTESEND_DEFAULT_CHUNK_BYTES constant allowing the over-riding of the remote-send (Migrator) starting chunk size in wp-config.php (expert users) -* TWEAK: Add CSS classes to dashboard updates notices, so that people can hide them more easily if they wish -* TWEAK: If gzopen() is disabled, then test binzip without trying to use PclZip to verify the results -* TWEAK: Add work-around for PHP bug https://bugs.php.net/bug.php?id=62852 - -= 1.11.6 - 22/Aug/2015 = - -* FIX: SFTP was not working in 1.11.4 for some servers (related to phpseclib library update and sources of random data) -* FIX: 1.11.5 release had wrong version number in header for paying users; corrected with fresh release - -= 1.11.4 - 19/Aug/2015 = - -* FIX: Perform previously missing tweak on the database after restoring a multisite backup to an install with a different table prefix, which inhibited the ability to create new users on the main site in a network. -* TWEAK: Remove an inefficiency when examining files to prune from Google Drive, reducing the amount of time needed. -* TWEAK: Show a warning if UpdraftPlus's directory in wp-content/plugins has been manually renamed to include a space, which WordPress does not support -* TWEAK: Skip search/replacing of the 'guid' column in the posts table when migrating (improves performance - and prevents possible re-appearances of blog articles in peoples' feed readers if double-migrating) -* TWEAK: Upgraded the bundled phpseclib Math, Net and File libraries to current versions (1.0 branch) -* TWEAK: Prevent PHP notice in log file when deleting FTP backup from dashboard -* TRANSLATIONS: Updated translations, including Greek - -= 1.11.3 - 13/Aug/2015 = - -* FIX: Fix access to S3 for PHP 5.2 users using buckets in the US-WEST-1 region -* FIX: Fix access to UpdraftPlus Vault for some PHP 5.2 users - -= 1.11.2 - 11/Aug/2015 = - -* TWEAK: Handle the results when someone with no UpdraftPlus Vault quota attempts to connect more gracefully - -= 1.11.1 - 10/Aug/2015 = - -* FEATURE: UpdraftPlus Vault storage - simple to set up storage from your trusted provider: https://updraftplus.com/landing/vault - with 1Gb of free storage for UpdraftPlus Premium customers ( https://updraftplus.com/shop/updraftplus-premium/ ) - and more storage available for anyone to purchase. All other storage options (Dropbox, Google Drive, etc.) remain available, of course! -* FEATURE: S3 enhanced wizard now allows the user to optionally deny the new Amazon Web Services IAM user download and/or delete permissions, for an even more secure setup (at the cost of some convenience - you will need to download/restore/delete S3 backups outside of UpdraftPlus). -* FEATURE: Amazon S3 in UpdraftPlus Premium now supports optional server-side encryption -* FEATURE: An "UpdraftPlus" menu now appears on the WP admin bar, allowing quick access. -* COMPATIBILITY: Tested and compatible with WordPress 4.3 -* SPEED: For users' whose backups are created with a zip binary (the default engine, where available), CPU usage + zip creation times have been significantly reduced -* SPEED: For users cloning a website with a large number of users and a changed table prefix, a long and slow series of SQL operations has been replaced with a single fast one -* FIX: The chunk-uploading algorithm for Copy.Com could unnecessarily upload the same chunks multiple times. We have not had any reports, but we believe it's also theoretically possible that a Copy.Com upload could have been corrupted by the same issue, so recommend updating for all Copy.Com users. -* FIX: Fix issue with site cloning whereby on sites with very large numbers of entries in the postmeta table that needed search/replacing, some could be omitted (in the case seen, the table had >600,000 entries) -* FIX: Saving the settings immediately after authenticating with Copy.Com resulted in being redirected to WP's page for all settings. -* FIX: If PHP was killed by the webserver during the process of pruning old backups, then this would not be retried until the next backup, thus potentially leaving more backups than desired around in remote storage in the meanwhile. -* FIX: Log files sometimes failed to mention the MySQL version -* TRANSLATIONS: Various updated translations - thanks to our translators -* TWEAK: When choosing multiple remote storage options (Premium), these are now stacked via tabs, instead of vertically as before -* TWEAK: More help for enormous sites on badly resourced web hosting: part of the enumeration of uploads needing backing up is now cached, allowing more to be time when time limits are short -* TWEAK: Secret credentials (e.g. S3 secret key) in the admin area are now starred (as explained in our long-standing FAQ, this does nothing to protect against malicious WordPress co-admins on your site - https://updraftplus.com/faqs/in-the-administration-section-it-shows-my-amazon-ftp-etc-passwords-without-using-stars-is-this-safe/ - but at least we won't get asked about it so many times!). -* TWEAK: Provide more direct help to the user if they are hosting with Strato and get the 'File Size Limit Exceeded' zip message -* TWEAK: When migrating data directly to a remote site, if the remote site returns a 413 HTTP code ("Request Entity Too Large"), re-try using a smaller chunk size -* TWEAK: Log when about to begin encryption of a database file (allowing the progress to be monitored more easily if there's a problem) -* TWEAK: Detect a further case of an incompatible database (that is from a remote source and uses MySQL features not present on the destination server) and warn before attempting to import. -* TWEAK: Make the error message shown if trying to restore an incompatible database (that is from a remote source and uses MySQL features not present on the destination server) clearer. -* TWEAK: If the user uses "Backup Now" before saving their settings, warn them that the unsaved settings changes won't apply to this backup -* TWEAK: Only warn about very large files found once for each file (rather than once per resumption) -* TWEAK: Add the UPDRAFTPLUS_GOOGLEDRIVE_DISABLEGZIP constant - define it as true to work-around broken HTTP setups (possibly broken outgoing proxy?) when accessing Google Drive -* TWEAK: When claiming an add-on (paid versions), the user's updraftplus.com password will automatically be forgotten once it is no longer needed -* TWEAK: Handle the case of the user typing in an invalid number of backups to retain more elegantly -* TWEAK: No longer require the php-mcrypt module for Dropbox -* TWEAK: Also retry a smaller chunk size if it looks like mod_security unhelpfully replaced a 413 HTTP code with a 200 success message despite the operation actually failing for this reason, or if it looks like sending is just timing out before the PHP timeout (so that we don't get notified). -* TWEAK: Added new CA root certificates to store (http://curl.haxx.se/ca/cacert.pem) -* TWEAK: If the Migrator's direct-send component drops its chunk size, then store this information so that it doesn't have to go through the cycle of finding the best chunk size the next time. -* TWEAK: Added UPDRAFTPLUS_IPV4_ONLY constant to prevent use of IPv6 (currently implemented by Google Drive only) -* TWEAK: Deal with a case where the web host appears to be losing disk I/O near kill time, despite later database writes going through (so, the order of operations was insufficient to guarantee what had been completed). This issue was only cosmetic - backup sets were intact (hence "tweak", not "fix") -* TWEAK: Increase HTTP timeout for remote site-to-site operations -* TWEAK: Don't cause the 'debug' tab to abort rendering if the web host has disabled the gzopen() function (which is an odd/pointless thing to do) -* TWEAK: Resolve PHP 'strict standards' coding internal notice in Google Drive module - -= 1.10.3 - 2015-06-09 = - -* FEATURE: Migration component can now send backup data directly from one WP site to another - https://updraftplus.com/shop/updraftplus-premium/ -* FEATURE: Support active mode FTP servers (extremely rare) -* FIX: The error message when choosing no components to restore was empty -* FIX: Restore ability to run on WordPress 3.5 (actually fixed in downloads of 1.10.1 after 2015-05-13) -* FIX: Some automatic pre-upgrade backups were not marked internally as such, leading UD to delete the oldest scheduled backup prematurely backups prematurely -* TWEAK: Reduce HTTP round-trips when checking presence + downloading backup archives in a restore/migrate operation -* TWEAK: Alert the user earlier if they're trying to use a database with a character set not supported by MySQL -* TWEAK: Use separate internal jobs for separate downloads, and thus avoid race conditions when updating job data (symptom: download widgets that don't show current information) -* TWEAK: Add constant UPDRAFTPLUS_SFTP_TIMEOUT allowing users to over-ride (via wp-config.php) the default SFTP timeout (default now: 15 seconds). -* TWEAK: Make Copy.Com filter out non-backups from remote file listings at an earlier stage -* TWEAK: Log more information when a curl error occurs when getting a OneDrive access token -* TWEAK: Code re-arrangement in OneDrive library to deal with sites using the obsolete PHP safe_mode -* TWEAK: Clearer message for users whose access to paid plugin updates has expired (paid versions) -* TWEAK: Improve detection of started pre-upgrade automatic backups in case of webserver misbehaviour -* TWEAK: Fix untranslated message when confirming the wiping of settings -* TWEAK: Replace more non-SSL links to updraftplus.com with SSL links -* TWEAK: Use a POST instead of a GET during one of the restore sub-stages (avoids a false positive on some mod_security setups) -* TWEAK: Improve backup-file-naming routine to reduce the times when no ASCII name can be found (ASCII is preferred as not all cloud storage methods will accept arbitrary characters in filenames) -* TWEAK: Don't keep a log file (unless debug mode is on) for scheduled tasks that result in the result that nothing needs backing up -* TWEAK: Remove cache files from Cherry Framework child themes upon migration (framework misbehaves if cache files are present after URL change) - -= 1.10.1 - 2015-05-12 = - -* FEATURE: Microsoft OneDrive support (Premium version) - full support (including chunked/resumable uploading and downloading) -* FEATURE: Allow prevention of backup of unwanted tables, via a filter; see: https://updraftplus.com/faqs/how-can-i-exclude-a-specific-table-from-the-backup/ -* FIX: Restore window would not open if date contained a single quote character in it (which was possible only in some languages) -* FIX: Restore the ability of PHP installations (< 1%) without curl to use Google Drive (broke when Google introduced a new SSL certificate at their end which PHP couldn't handle properly without extra help). -* TWEAK: Add woocommerce_order_items and relevanssi_log to the list of potentially huge tables that won't need search/replacing -* TWEAK: Add link to admin email setting and fix broken link to reporting add-on in free version -* TWEAK: Provide more direct help for paid users getting blocked by the security shield when connecting for updates access -* TWEAK: Small tweak in zip-splitting algorithm if it looks likely that there are insufficient resources with no further resumptions yet scheduled -* TWEAK: "Migrate" dialogue, when the Migrator is installed, now contains a widget to use directly (instead of just directing to other route) -* TWEAK: Ask user to confirm if they navigate away from the settings page with unsaved changes -* TWEAK: Replace some non-SSL links to updraftplus.com with SSL links, and replace all non-SSL readme links -* TWEAK: Add UPDRAFTPLUS_DBSCAN_TIMEOUT constant to control how much time is allowed for scanning database, and make the default vary instead of constant (will help users with absolutely enormous databases). -* TWEAK: Provide clearer guidance to users with a wrong updraftplus.com password entered for updates -* TWEAK: When cloning a site with Jetpack, automatically clear Jetpack invalid connection status -* TWEAK: Prevent some old admin notices from being repeated when saving settings - -= 1.9.64 - 2015-04-20 = - -* FEATURE: (Premium) Added wizard to make it easier to create limited-access AWS users (requires PHP 5.3.3) -* SECURITY: Fix non-persistent back-end XSS vulnerability, reported by Sucuri - https://updraftplus.com/new-security-vulnerability-found-across-significant-numbers-of-wordpress-plugins-including-updraftplus/ -* FIX: Fix failure to access some files (e.g. for downloading or deleting) in Google Drive folders that contained >100 UpdraftPlus backup archives (thanks to IanUK for his help) -* TWEAK: Amazon S3 reduced redundancy storage (a feature of UpdraftPlus Premium) now requires use of PHP 5.3.3 or later. -* TWEAK: Various fixes to bring automatic backups code up to date with WP 4.2 release candidate 1 (there were some changes since beta 3, which worked since UD 1.9.62) -* TWEAK: Update to version 2.0 of plugin updater class (https://github.com/YahnisElsts/plugin-update-checker) - necessary on WP 4.2 to prevent shiny updates after the first failing when 3rd party plugins exist, and to suppress a PHP notice on the plugins page. -* TWEAK: Add wp_rp_tags to the list of potentially huge tables that won't need search/replacing -* TRANSLATION: New Slovenian translation, courtesy of Clav Icula - -= 1.9.63 - 2015-04-03 = - -* TWEAK: Revert to previous global SSL CA bundle: it seems Amazon S3 still has servers with 1024-bit SSL certificates - -= 1.9.62 - 2015-04-01 = - -* FEATURE: Automatic backups now integrate with the forthcoming WP 4.2's "shiny plugin updates" -* COMPATIBILITY: Tested and marked compatible with the forthcoming WordPress 4.2 (tested up to beta 3) -* FIX: Fix regression in 1.9.60 for corner-case of S3 users with no permission to check their bucket's location (but permission to write to it). -* TWEAK: Make "settings saved" message disappear after a few seconds, to prevent UI clutter -* TWEAK: Decrease UI clutter in backup time selection -* TWEAK: Update to latest global SSL CA bundle, removing again 1024-bit root CAs (last attempted in 1.9.26, but S3 still had some legacy servers). Modern web browsers have not accepted these for 6 months now. (SSL verification can be turned off in the expert options). -* TWEAK: Defeat WP Download Manager Google Drive plugin's loading of its SDK on all pages (conflicting with UD when UD attempts to backup to Google Drive) -* TWEAK: Detect case of old MySQL on Windows with table prefix that varies by case (and hence WP actually only works by accident) - produce a consistent backup that can be restored on any MySQL server. -* TWEAK: Add dashboard notice with links to more resources, for free users who've been installed >4 weeks (with option to dismiss notice) -* TWEAK: Add itsec_log to the list of tables of non-essential/not-needing-search/replace + likely to be large tables for backup strategy -* TWEAK: Improvement to scheduling algorithm in case where WP's scheduler starts same resumption multiple times - prevent next attempt being delayed longer than necessary -* TWEAK: Add a header to report emails indicating the job ID - helps when debugging -* TWEAK: Detect + show a more helpful error message if blocked by CloudFlare when connecting for updates (paid versions) -* TWEAK: Make it easier to use custom Dropbox API keys, via UPDRAFTPLUS_CUSTOM_DROPBOX_APP constant (define to true in wp-config.php) -* TWEAK: Tweak debug output of webserver information to avoid triggering a (silly) mod_security rule in some setups -* TWEAK: Alert the user if using Amazon S3 if they do not have the PHP XML Writer module available -* TWEAK: Log the fact that local deletions are being skipped, if the user set that option. -* TWEAK: Give timestamp of WPB2D backups without relying upon location of SQL file -* TWEAK: Detect a situation on migration where the uploads path is changed (from a site that began pre-WP 3.5) that was previously undetected -* TRANSLATIONS: French translation updated from less than half to complete, thanks to Erwan François. Various other translations updated (many thanks to all translators). - -= 1.9.60 - 2015-02-24 = - -* FEATURE: When using "Backup Now", and keeping the UpdraftPlus settings page open, a broken WP scheduler will not prevent the backup's progress. -* FEATURE: Amazon's "EU Central 1" Frankfurt region now supported again (Amazon began requiring their new signature method at this location - probably a sign of things to come everywhere). PHP 5.3.3 required for this region. -* FEATURE: Database backup files can now be handled when uncompressed - i.e., you can remove the gzip compression, and use the resulting file (useful for large backups on slow/limited hosting: pre-decompressing the file will reduce the processing time needed) -* FEATURE: Introduced new in-page auto-backup widget that can resume, and thus cope with a backup of any size. This feature is in preparation of WP 4.2's major re-write of the updating user experience flow. -* TWEAK: Update PHP-Opencloud (Rackspace) and dependency libraries to current versions. -* TWEAK: Make sure that activity is recorded periodically when adding database tables to the final database backup (found a site with over 7,500 tables) -* TWEAK: Don't bother to attempt to detect double-gz compression on setups where it can't be handled (where gzseek() has been disabled in the PHP configuration) -* TWEAK: Added free/Premium comparison table to the free version -* TWEAK: Importer (part of UpdraftPlus Premium) can now import generic .sql, .sql.gz and .sql.bz2 files -* TWEAK: Don't show the "memory limit" warning in a case where the value could not be accurately detected -* TWEAK: If the user chooses the "email" remote storage method, then a warning will be added if the archive being sent is bigger than most mailservers can carry (and will be removed if it is successfully sent), thus giving the user a hint as to the failure cause (if they overlooked the existing warning in the email settings). -* TWEAK: The importer (part of UpdraftPlus Premium) can now import the latest BackupWordPress format databases -* TWEAK: Flush output buffer explicitly when closing the browser connection - prevents delayed feedback on some setups -* TWEAK: Automatic backups are now offered if you go to the 'Plugins' page, and update via the 'More information' iframe -* TWEAK: Trim spaces from Google Drive client_id/secret - some users didn't spot that they introduced whitespace when copy-pasting -* TWEAK: Add "Simple Visitor Stats" and "Simple Feed Stats" tables to the list of tables that may have large amounts of data, and which don't need search/replacing when migrating -* TWEAK: When restoring plugins and themes, log the list of entities being restored (helps with tracing problems) -* TWEAK: Deal with CloudFTP/StorageMadeEasy returning directory listings in a non-standard format, when rescanning remote backups -* TWEAK: Version numbering scheme for paid versions changed; see: https://updraftplus.com/change-in-updraftpluss-version-numbering-scheme-for-paid-versions/ -* TRANSLATIONS: Updated translations in several languages (many thanks for our translators) -* FIX: For imported 3rd-party backups, the 'Existing Backups' tab (only) was showing "Unknown Source" instead of the detected source. - -= 1.9.52 - 2015-02-07 = - -* FIX: Fix issue when migrating (hence, in Premium versions only) backups with very large options tables output by mysqldump, which could cause the site URL to finish with an incorrect value - -= 1.9.51 - 2015-02-03 = - -* SECURITY: Prevent nonce leak that could allow logged-in users who aren't admins (if you have any) to access backups, UpdraftPlus settings and perform other harmful actions. No issue exists for users of UpdraftPlus Premium, or if you have the stand-alone "Automatic Backups" or "No Adverts" add-ons, or if your site has no untrusted users who can log in (or whilst have dismissed the "Automatic Backups" notice on the updates page). Credit to Sucuri (http://sucuri.net) for identifying this issue, and notifying us of it. - -= 1.9.50 - 2015-01-29 = - -* TWEAK: Importer now supports a previously-unseen format for WordPress Backup 2 Dropbox backups -* TWEAK: Fix cron calculation that could have prevented UpdraftPlus loading when using ALTERNATE_WP_CRON (see 1.9.45) -* TWEAK: If insufficient permissions exist when restoring, then exit maintenance mode when this is detected; and handle the case of having create but not drop permissions more elegantly -* TWEAK: Defeat some other plugins/themes which load their CSS code onto UpdraftPlus settings page and break things -* TWEAK: Prevent a "not tested on this version of WP" message showing for a short time after install of a new version, when it only applied to the previous version -* TWEAK: Reduce HTTP timeout when checking for available plugin updates (paid versions) from 10 to 5 seconds -* TWEAK: Tidy up the post-restore screen a little - one less info box. -* TWEAK: When a restore finishes, WP Super Cache's cache will be emptied (if present), to prevent confusion caused by cached pre-restore pages. -* TWEAK: Slight change to how the 'mothership' for updates is calculated, for more flexibility in our in-house testing -* TWEAK: Log more informative error if user chooses 'FTP' for their remote storage, but adds no FTP settings -* TWEAK: Change "any other directory" to "any other file/directory" in the "more files" add-on, to better reflect its capabilities -* TWEAK: Make sure that "more files" will skip UD's temporary directory, if asked to backup a parent directory of it -* TWEAK: Default to https for updates checking, with fallback to http (only relevant to versions from updraftplus.com) -* TWEAK: Prevent 'Strict Standards' PHP coding notice with WebDAV on PHP 5.5 -* TWEAK: Provide clickable link through to the admin email address in the reporting settings -* TWEAK: If the gzopen or gzread functions are disabled in the PHP install, then the message saying so omitted to say which one (or both) -* FIX: WebDAV upload method could very occasionally fail to detect upload error conditions - -= 1.9.46 - 2014-12-29 = - -* FEATURE: Chunked/resumable uploads are now supported for SFTP -* FIX: Scan for existing backup sets added manually to local storage in recent versions could overlook some unless clicked twice, in non-GMT timezones -* TWEAK: Work-around issue in Manage WP worker plugin which caused a crash when authenticating with Dropbox -* TWEAK: Prevent PHP notice when listing files on SFTP server -* TWEAK: Reset an internal upload counter used to detect activity when a cloud storage switch is made due to no apparent activity - could prevent some large, long-running uploads on hosts with little available execution time - -= 1.9.45 - 2014-12-20 = - -* FIX: Fix case in which the database imported from a BackWPUp backup could be falsely identified as missing (introduced in 1.9.40) -* FIX: WordPress installs with ALTERNATE_WP_CRON set could skip some scheduled backups (since 1.9.19, so it appears that the conditions required to cause this bug are rare) - -= 1.9.44 - 2014-12-13 = - -* TRANSLATIONS: Add new incomplete translations (ready for the wordpress.org change to take translation availability into account when searching for plugins): Norwegian Bokmål, Norwegian Nynorsk, Finnish, Hebrew, Catalan, Vietnamese, Bengali -* FIX: Fix a failure to detect the progress properly for large backup sets, introduced in 1.9.40 - -= 1.9.43 - 2014-12-11 = -* FIX: Fix bug in 'lock admin' feature causing lock-outs even with the correct password -* TWEAK: Site is put in maintenance mode whilst database restore takes place - -= 1.9.42 - 2014-12-08 = -* FIX: Fix bug in 1.9.40 that caused some cloud storage uploads to be terminated. -* FIX: Restore functionality for Premium users on older WP versions (3.1 - 3.5) - -= 1.9.40 - 2014-12-04 = - -* FEATURE: The auto-backup addon (UpdraftPlus Premium) can now run before -WordPress automatic updates -* FEATURE: Lock access to your UpdraftPlus settings (Premium) - -https://updraftplus.com/lock-updraftplus-settings/ -* FEATURE: The full log file viewer is now real-time - it updates as the -backup progresses -* FEATURE: When downloading from remote storage via the settings page, stalled -downloads are now automatically restarted (relevant for large backups with low -web-server PHP time-outs) -* FIX: Manual search/replace expert tool was broken in early downloads of -1.9.31 -* FIX: Suppress bogus messages about missing files from 3rd party tar backups -when restoring -* FIX: If backing up multiple "more files" locations (Premium), then paths -that were identical in both locations could be omitted from the second -location -* FIX: With the reporting add-on, any mails sent by other plugins after -UpdraftPlus had finished (which has never been seen in the wild) would have -corrupted contents -* TWEAK: The tab for downloading/restoring backups has been simplified -* TWEAK: Item for UpdraftPlus now appears in the network admin menu, for -super-admins on network installs -* TWEAK: Labels (Premium) are now maintained and can be detected for -locally-imported database backups -* TWEAK: Automatic backups are now labelled -* TWEAK: The "retain" settings now do not apply for auto-backups (Premium), -unless at least the specified number of non-auto-backups has already been -retained. -* TWEAK: Time selector now accepts typed hours without the trailing zero -* TWEAK: Extended BackWPUp importer to handle older BackWPUp backups lacking -manifests -* TWEAK: Removed Bitcasa storage option, due to closing down of API (November -15th 2014) -* TWEAK: When an invalid JSON response is received during restore stage 2, -display the data directly (not just in the error console, where not everyone -will think of looking). -* TWEAK: 3rd party backups which are missing expected entities are now handled -more gracefully -* TWEAK: The fancy report now tells the user what paths are in the zips for -any additional paths they configured to add to the backup -* TWEAK: Add a swifter resumption in one corner case (efficiency) -* TWEAK: If a zip error is encountered on cPanel, then the free disk space is -checked, to potentially give the user more information on probable causes -* TWEAK: You can now remove your updraftplus.com password from the settings -(paid version) without losing your access to updates -* TWEAK: Suppress top advert if the user is on their first go (free version - -danger of too many notices) -* TWEAK: Don't display the post-restoration message "if your backup set...", -since we can work this out ourselves -* TWEAK: Supply extra help to the user if the backup directory is not -writable. -* TWEAK: SCP remote storage now logs chunk progress -* TWEAK: Provide a database encryption phrase text entry in the restore -options (rather than needing to enter it in the settings) -* TWEAK: Set the PclZip temporary directory to the Updraft directory if unset, -to keep its temporary files out of the way more easily -* COMPATIBILITY: Tested with the forthcoming WordPress 4.1 -* TRANSLATIONS: New Dansk / Danish translation by Lars Lund and Lasse Jensen - -= 1.9.31 - 2014-10-24 = - -* TWEAK: Bitcasa now gives a deprecation warning (Bitcasa are closing down -their API on November 15th 2014) -* TWEAK: Fix bug causing PHP notices in Migrator add-on search/replace -* TWEAK: Add support for Amazon S3's new Frankfurt region -* TWEAK: Add work-around for bug in the ancient PHP 5.2.6 (May 2008!) if -binary zip not available - -= 1.9.30 - 2014-10-21 = - -* FEATURE: Add the capability to handle BackupWordPress database zip backups -(https://updraftplus.com/shop/importer/) -* FEATURE: Add capability to apply labels to backups (Premium - -https://updraftplus.com/shop/updraftplus-premium/) -* TWEAK: Logs are now shown in a pop-out window with a separate download -button -* TWEAK: Detect select/poll lengthy timeouts when uploading to Dropbox, and -prevent overlapping activity -* TWEAK: Add constant UPDRAFTPLUS_NOAUTOBACKUPS to programatically disable the -automatic backups add-on -* TWEAK: Rename UpdraftPlus Dropbox class, to avoid clash with Ninja Forms -upload add-on -* TWEAK: Made the output of the HTTP (curl) debugging tool more informative -* TWEAK: Add web.config file to make updraft directory unviewable on IIS -* TWEAK: If the user tries to import a WordPress Network backup into a -single-site install, then detect and warn -* TWEAK: In the free version, avoid unexpectedly changing the daily backup -time when other settings are saved -* TWEAK: Improve the immediate retry strategy in case of zip failure (saves -time and/or notifying the user to manually check the log) -* TWEAK: Correctly detect language on WP 4.0 onwards, when suggesting helping -with translation -* TWEAK: When connecting for updates to updraftplus.com (Premium), indicate if -it was the username or password that was wrong -* TWEAK: Alert user if they are trying to use Google Drive with a direct-IP -address site URL forbidden by Google's policy -* TWEAK: Prevent a corner-case where excessive logging could occur upon -restoration -* TWEAK: Be less strict with case when looking for supported patterns in the -Importer add-on (https://updraftplus.com/shop/importer/) -* TWEAK: Search/replace the postmeta table faster -* DEPRECATED: Bitcasa support has been deprecated, and the links removed from -the free version. (Existing Premium users using Bitcasa will continue to be -able to do so). See: -https://updraftplus.com/bitcasas-current-direction-unclear/ -* FIX: Fix corner-case in URL search/replace when migrating a site that had WP -in a different directory to the site home, and migration to a sub-directory of -the original site. -* FIX: Autobackup setting (https://updraftplus.com/shop/autobackup/) failed to -save properly if turned off on first usage -* TRANSLATION: New Farsi (Persian, fa_IR) translation, courtesy of -Jamshidpour, Ashkan Ghadimi, Mohammad (2online4.ir) and Nasiri Amirreza - -= 1.9.26 - 2014/09/22 = - -* TWEAK: There are still some Amazon S3 servers validated via a Verisign -1024-bit certificate, causing backup to fail due to SSL validation failure. -Revert to previous collection of root SSL certificates in order to still allow -access to these servers (see: -https://blog.mozilla.org/security/2014/09/08/phasing-out-certificates-with-1024-bit-rsa-keys/) -* TWEAK: If Google Drive reports that the quota will be exceeded, then make -this feedback more obvious to the user without reading the log -* TWEAK: If the user enters an S3 path with an erroneous preceding slash, then -remove it -* FIX: Amazon S3 RRS settings (Premium) were not being applied on archives -smaller than 5Mb -* TRANSLATION: New Română (Romanian, ro_RO) translation, courtesy of -Augustin-Mihai Mufturel and Teodor Muraru - -= 1.9.25 - 2014/09/17 = - -* FEATURE: Copy (https://copy.com) cloud storage support (Premium - -https://updraftplus.com/shop/updraftplus-premium/) -* FEATURE: The search/replace expert tool can now work on selected tables only -* PERFORMANCE: Use PageVisibility API to be more intelligent about when we -need to poll for progress in the dashboard -* FIX: The Migrator add-on would fetch more database rows than it should, -increasing the (low) risk of hitting memory limits, and increasing the time -needed on enormous sites -* FIX: Some Google Drive backups could get uploaded twice, if you were using -multiple storage backends -* FIX: If user set the option to not verify SSL certificates, then this option -was not honoured for all methods -* FIX: If user had never saved their settings (and hence using no cloud -backup), then old backup sets were not pruned -* TWEAK: Inform the user of possible plugin compatibility issues if they are -about to restore a site onto a webserver running a PHP major version older -than the original backup. -* TWEAK: Detect database disconnection when search/replacing, and reconnect if -possible; and to try less rows in case it was a memory limit -* TWEAK: Allow wildcards at either end in exclusion items (e.g. *backups*) -* TWEAK: Add option to control how many rows are search/replaced at once -* TWEAK: Prevent PHP notice being generated on first Google Drive -authentication -* TWEAK: Update Bitcasa console link to new location -* TRANSLATIONS: New Portuguese (Portugal) translation (pt_PT) - thanks to -Pedro Mendonça -* TRANSLATIONS: Updated translations for Dutch, Italian, Swedish, Russian, -Czech, Greek, Portuguese (Brazilian) - -= 1.9.19 - 2014/08/19 = - -* FEATURE: Omit any directory from the backup (recursively) by creating a file -called .donotbackup within it -* PERFORMANCE: Lazy-load more code -* PERFORMANCE: Prevent no-op search/replacements when restoring -* FIX: Fix a corner-case where a backup might be able to continue but no -attempt was made after using PclZip -* FIX: Fix a corner-case (race condition) where UD might try to upload the -same archive twice -* FIX: Detection of pre-WP 3.5 hard-coded uploads paths upon site clone had -stopped working -* FIX: Fix bug in Importer add-on which could halt restorations of 3rd-party -backups from the BackupWordPress plugin -* FIX: Fix bug in the informational disk space consumption calculation in the -expert tools section -* TWEAK: Catch + log errors thrown by phpMailer -* TWEAK: Merge Google SDK tweak from -https://github.com/google/google-api-php-client/pull/189 to deal with buggy -curl/proxy combinations with Google Drive -* TWEAK: Prevent PHP log notice being generated on "Backup Now" -* TWEAK: Change default zip split size to 500Mb on new installs -* TWEAK: Scheduling algorithm tweak for more efficiency with very large -backups when PHP is allowed to run long -* TWEAK: Do not rely on PHP's disk_free_space() when it returns (int)0 -* TWEAK: Check database connection after auto-backup -* TWEAK: More helpful message if uploading a backup when local storage is not -writable -* TWEAK: Extra logic to survive out-of-memory MySQL conditions in extreme -cases; plus introduce UPDRAFTPLUS_ALWAYS_TRY_MYSQLDUMP constant -* TWEAK: Tweak Amazon S3 logic so that it can cope with a situation where -there is no permission to request its location (but there is permission for -all other operations) -* TWEAK: Workaround for PHP bug #62119 which could cause some files beginning -with a non-ASCII character to be dropped -* TWEAK: Warn the user if they are running on Apache without mod_rewrite and -restore a site with permalinks requiring mod_rewrite -* TWEAK: If Premium user was backing up non-WP tables, then optimize the -backup table order -* TWEAK: Deal with case when uploading very large backups to Google Drive on -overloaded servers with unreliable network where activity check might misfire -* TRANSLATIONS: Updated translations: Hungarian, Swedish, Russian, Brazilian -(Portuguese), Spanish, Czeck, Dutch, Turkish, German - -= 1.9.17 - 2014/07/16 = - -* FEATURE: Can now import/restore/migrate backups created by WordPress Backup -To Dropbox (Premium) -* FIX: Fix bug in Importer add-on that prevented some potential warnings about -the integrity of the 3rd party backup zip being displayed -* FIX: Some errors upon S3 downloads were not being passed back up to the -settings page for display -* FIX: Update "Rackspace Enhanced" add-on for compatibility with current -Rackspace API -* TWEAK: Prevent spurious messages about quota for users who have upgraded -their Bitcasa account to infinite storage -* TWEAK: Prevent some unnecessary duplication of work when resuming a database -backup (since 1.9.13) -* TWEAK: Dropbox now supports use of WP_PROXY_ settings (proxy needs to -support HTTP GET/POST/PUT) -* TWEAK: Add work-around for "Google Drive as CDN" plugin's inclusion of -incompatible Google SDK -* TWEAK: "More Files" add-on now lets you list single files for inclusion -* TRANSLATIONS: Many translations updated - -= 1.9.15 - 2014/06/09 = - -* FEATURE: New search/replace expert tool (Premium) -* TWEAK: UI has been simplified - see: -https://updraftplus.com/gentle-re-design/ -* TWEAK: "Backup Now" now avoids the WordPress scheduler - thus meaning it can -work on sites where the WordPress scheduler is broken (e.g. Heart Internet) -* TWEAK: Make sure that server HTTP-level errors are shown directly to the -user at the 'Processing files...' stage of a restore -* TWEAK: Amend SQL which prevented options/sitemeta tables backing up at full -speed on large sites -* TWEAK: Dropbox will now display some error messages more prominently, where -relevant -* TWEAK: Dropbox account user's name is stored when you authorise -* TWEAK: Show link to FAQ if user's zip upload is corrupt -* TWEAK: Work around annoying Google Drive issue whereby Google's end -sometimes returns an incomplete list of folders -* TWEAK: Interpret time in imported backup sets as being in destination WP -install's timezone -* TWEAK: Auto-correct Dropbox folder configuration if the user erroneously -enters a full URL instead of a folder path -* TWEAK: Bitcasa back-end now checks account quota and logs a warning if it -looks like it will be exceeded -* TWEAK: Email reports created by UpdraftPlus (free) now include the latest -blog headlines from updraftplus.com -* TWEAK: Make sure all relevant restoration options in restore dialogue are -shown (works around Firefox issue upon page reload/navigation) -* FIX: Reporting add-on could mis-display number of warnings when saying "X -errors, Y warnings". -* TRANSLATION: New Tagalog translation (thanks to Kristen Macasero) - -= 1.9.13 - 2014/05/19 = - -* FEATURE: Google Drive now works without the PHP curl module being needed -* FEATURE: UpdraftPlus Premium can now backup non-WordPress tables and -external databases; database encryption is also now a Premium feature; see: -https://updraftplus.com/backing-external-databases/ -* FIX: Work around conflicts with the Google Analyticator and Appointments+ -plugins when using Google Drive since 1.9.4 (see: -http://wordpress.org/support/topic/dont-unconditionally-load-old-google-sdk) -* FIX: Work around conflict with some XCache setups that prevented activation -since 1.9.4 -* FIX: Make all S3 SSL settings take effect -* FIX: Fix packet size calculation issue upon restore that could cause false -detection of over-large packets -* FIX: Prevent unnecessary abortion of restore if PHP's (deprecated) safe_mode -is on (PHP 5.3 and below) -* FIX: When migrating a multisite with a different table prefix, make sure the -user role list is maintained on each site -* FIX: Rescan of remote FTP storage was not using configured path -* TWEAK: Now tested on PHP 5.5 -* TWEAK: Migrator can now cope with situations where the development site was -developed under multiple URLs without the developer cleaning up -* TWEAK: Remove several PHP strict coding standards messages, and a -deprecation warning on PHP 5.5+ when using Bitcasa -* TWEAK: Add Counterize tables to the custom lists of tables that do not need -search/replacing upon migration / are non-vital data -* TWEAK: Check for DB connection having been dropped before pruning old -backups (WP 3.9+) -* TWEAK: Make sure that if the user has not configured the Google Drive API in -their Google account, then they are alerted -* TRANSLATIONS: Updated Greek, Czech, German, Spanish, French, Dutch, -Portuguese (Brazilian), Russian, Swedish and Turkish translations - -= 1.9.5 - 2014/04/25 = - -* FIX: Backups were not uploaded successfully if you were using both an -encrypted database and Google Drive storage in 1.9.4 - -= 1.9.4 - 2014/04/23 = - -* FEATURE: New remote storage back-end for OpenStack Swift -* FEATURE: New remote storage back-end for Bitcasa (Premium - -https://updraftplus.com/shop/updraftplus-premium/) -* FEATURE: New Google Drive back-end now uses new SDK; resulting new -capabilities include ability to rescan remote storage, and chunked downloading -for huge files; also requires a shorter list of permissions -* FEATURE: Restore backups that were created by the plugin BackWPup (Premium - -https://updraftplus.com/shop/updraftplus-premium/) -* FIX: WebDAV storage: remove requirement for PEAR to be pre-installed on -server -* FIX: Fix restoration on sites where WP did not have direct filesystem access -* FIX: Fix regex which prevented download progress of mu-plugins zip -displaying correctly -* FIX: Fix issue preventing some useful information about URL changes being -included in the migration log file -* FIX: Restore compatibility with WordPress 3.2 (if you're using that, you're -overdue an upgrade by some years!) -* TWEAK: Enable new locations for plupload Flash/Silverlight widgets (for -non-HTML5 browsers) in WP3.9+ (later reverted by core devs, but is harmless in -case they re-introduce) -* TWEAK: Take advantage of WP 3.9+'s new method (if available) for maintaining -DB connectivity on very long runs -* TWEAK: Add filter so that programmers can allow the options page to be shown -to non-admins -* TWEAK: Add filter allowing programmers to forbid a backup -* TWEAK: Detect and adapt to cases where the site is moved to a system with -different case-sensitivity and the database record of the theme is now wrong -* TWEAK: Prevent erroneous warning about a missing table in the database -backup on some WPMU installs that began life as a very old WP version -* TWEAK: Introduce constant allowing users of pre-release WP installs to -disable notices about using a version of WP that UpdraftPlus has not been -tested on. -* TWEAK: Make Dropbox uploads at least 25% faster (in our testing) by -increasing the chunk size -* TWEAK: Reduce number of rows fetched from MySQL if no activity took place on -the previous resumption -* TWEAK: AWS image in settings page will now use https if dashboard access is -https - prevents non-https warnings in recent browsers -* TWEAK: Hook into Better WP Security so that it doesn't tell the user that -they have no backup plugin -* TWEAK: New debugging tool to test remote HTTP connectivity -* TWEAK: Tweak the MySQL version detection in the 'debug' section of the admin -page to prevent a PHP message being thrown on PHP 5.5+/WP3.9+ -* TRANSLATION: New Czech (cs_CZ) translation; thanks to Martin Křížek -* TRANSLATION: Updated Russian, Swedish, Dutch and Portuguese translations - -= 1.9.0 - 2014/03/26 = - -* COMPATIBILITY: Tested on and updated for forthcoming WordPress 3.9 -* FIX: Prevent SQL errors on restore if SQL command was over-sized and split -awkwardly (very hard to trigger) -* FIX: Fix subtle race condition that prevented uploads of large archives on -Amazon S3 in some very limited situations -* FEATURE: Ability to restore and migrate from backups produced by other -backup plugins (Premium) (supported: BackUpWordPress and Simple Backups -(zip-based; though, if you have a tar-backup, you can re-pack it easily)) -* FEATURE: Feature to re-scan remote storage (allows detection of existing -backups after a restore to an earlier site version, and allows quicker moving -of data from site to site when migrating) -* FEATURE: SFTP add-on (https://updraftplus.com/shop/sftp/) now supports -key-based logins (as well as password-based) -* TWEAK: Add a warning message and link to helpful page for people whose WP -schedulers don't seem to be working (at least 4 overdue jobs in the queue) -* TWEAK: Introduce a filter allowing users to add a bespoke scheduling option -(e.g. every 2 days) -* TWEAK: When backup is sent by email attachment, the email now indicates the -originating site more clearly -* TWEAK: Display a dashboard warning if you are using a version of UpdraftPlus -that has not been tested on your current WordPress version -* TWEAK: Add work-around for bad hard-coded data in Elegant Themes Theme -Builder when restoring -* TWEAK: Log a message when Dropbox authentication completes (prevent user -confusion if the most recent message is pre-success) -* TRANSLATIONS: New Arabic translation (thanks to Omar Amassine - me at -omar.ma, Ahmed Fahmy and Riyadh Altayib) -* TRANSLATIONS: Updated Spanish translation (thanks to Pablo Laguna - -laguna.sanchez at gmail.com) -* TRANSLATIONS: Updated Nederlands / Dutch translation (thanks to Dennis -Hunink - dennishunink at me.com) -* TRANSLATIONS: New Turkish translation (various translators - not yet -complete) - -= 1.8.13 - 2014/03/07 = - -* FIX: Fix bug that prevented changes to your schedule being saved on network -(WPMU) installs (Multisite add-on) - -= 1.8.12 - 2014/02/27 = - -* FIX: Prevent spurious warning message showing when authenticating new -Dropbox connections (introduced in 1.8.11) -* TWEAK: Add support for Amazon S3's China + government zones - -= 1.8.11 - 2014/02/27 = - -* FIX: Deal with some unlikely multisite migration combinations -* FEATURE: Allow the 'exclude' options (for omitting files/directories from -the backup) to go to any level (i.e. can now exclude entities which are deep -in the directory tree) -* FEATURE: "More Files" add-on (and hence Premium) now allows adding as many -non-WP directories as you like -* FEATURE: Allow use of Amazon S3's Reduced Redundancy Storage (via -add-on/Premium) -* FEATURE: Allow all messages to be centrally logged in syslog/Event Log (via -add-on/Premium) -* RELIABILITY: Allow skipping of data from tables whose data is explicitly -known to be inessential if at least 2 attempts to backup the data fail (e.g. -lack of resources on low-budget hosts with huge tables, e.g. StatPress data) - -as an alternative to total backup failure. -* TWEAK: Prevent spurious warning message if the site (uploads) is empty and -using /usr/bin/zip -* TWEAK: Work-around for quirky FTP server for which PHP loses the -communication if SIZE is called for a non-existent file -* TWEAK: Show table prefix in debugging information, and add quick links to -install useful debugging plugins -* TWEAK: Limit amount of to-database logging when backing up uploads if the -user is not using dated directories (speed-up) -* TWEAK: Split zip earlier if progress made in the past but not recently -(should help with some ultra-low-resource hosts, e.g. one.com) -* TWEAK: "Custom Content Type Manager" plugin has bad hard-coded cache data; -detect + fix this on restore -* TRANSLATIONS: Updated translations for Russian, Dutch, German and Portuguese -(Brazilian) - -= 1.8.8 - 2014/01/27 = - -* FIX: Correctly detect table prefix on some WPMU installs that had been -upgraded from an earlier version than 3.0 (i.e. very old) -* FIX: Files directly in wp-content/uploads (from a 1.8.5 backup and not in -any sub-directory) were being restored one directory too high -* UPDATED: Updated Swedish, Portuguese and Dutch translations -* UPDATED: Update root certificates to match latest CURL/Mozilla version -* TWEAK: Automatically change http(s):// to webdav(s):// in WebDAV URLs if the -user overlooks the instructions -* TWEAK: If SHOW TABLES returns no tables, then schedule a re-try later -(presume the DB connection had been dropped) -* TWEAK: Preceed warnings in the log file with [Warning] -* TWEAK: Prevent a very rare PHP segfault due to -https://bugs.php.net/bug.php?id=51425 -* TWEAK: Show the filename being unpacked during restore (helps with -troubleshooting if there are very many zips) -* TWEAK: Premium plugin now shows information about pending/past -update/support expiries + links to renewal page -* TWEAK: Show all defined constants in the debug dialog -* TWEAK: Detect + deal with situations where the webserver double-gzipped the -database file -* TWEAK: Display a warning in the FTP configuration section if the hosting -company disabled FTP functions -* TWEAK: Make sure that WebDAV notices are included in UD's log file - -= 1.8.5 - 2014/01/09 = - -* FEATURE: Add option to exclude specified files from the 'uploads' backup. -The default option will omit backing up backups created by at least 2 other -backup plugins. -* FEATURE: New Brazilian Portuguese translation - thanks to Lucien Raven and -Tom Fonseca -* FEATURE: Migrator search/replace now handles JSON and object-encoded data -* UPDATED: Updated Swedish translation -* FIX: When advising the user that his remaining Dropbox quota is -insufficient, take into account parts of the file already uploaded -* FIX: Delete Old Directories button in 1.8.2 was using a PHP 5.3+ feature: -restore PHP 5.2 compatibility -* FIX: Reporting add-on was incorrectly inflating the number displayed for the -total error count if there were warnings -* FIX: Prevent a bogus warning appearing when the user has filtered the base -table prefix -* TWEAK: Give more feedback to user when FTP login fails. Also, improve -automatic switch to non-SSL FTP if SSL FTP fails to cover more situations. -* TWEAK: Add informational text about the implications of not choosing any -remote storage method -* TWEAK: Supply the "Delete Old Directories" button directly with the message -advising users to press it -* TWEAK: If using WP Slimstats, don't search/replace the slimstats table when -migrating (referer data should be left intact); and this table is often -gigantic, so this hugely speeds up restores/migrations -* TWEAK: Handle odd file permissions setups more skilfully when -restoring/migrating -* TWEAK: Automatically rescan for new backup sets if none were previously -known (saves a click when manually importing) -* TWEAK: Force a shorter pathname to be used when unpacking zip files (prevent -maximum pathname limits being hit) -* TWEAK: Tweak CSS to work-around other plugins that dump their CSS code on -all settings pages and break modals (in this case, Events Manager) -* TWEAK: Hide the instruction for users of Opera unless the user agent header -indicates Opera -* TWEAK: Speed migrations by skipping redundant search/replace scan on -term_relationships table (which can never have URLs in it) - -= 1.8.2 - 2013/12/13 = - -* FIX: Various small fixes to the initial release of 1.8.1 -* TWEAK: Restorer now switches theme if database is restored to indicate a -non-existent theme, and Migrator temporarily disables cacheing plugins during -Migration -* TWEAK: Improve handling of MySQL's maximum packet size - attempt to raise -it, and leave some margin -* TWEAK: Move the Rackspace SDK around to prevent problems on systems with -limited maximum pathname lengths -* TWEAK: Provide a link to the log file at the top of the restoration page - -= 1.8.1 - 2013/12/10 = - -* FEATURE: New "Reporting" add-on - more sophisticated/flexible backup reports -(https://updraftplus.com/shop/reporting/) -* FEATURE: New enhanced add-on for Rackspace Cloud Files users, allowing them -to create a new sub-user with exclusive access to the backup container -(https://updraftplus.com/shop/cloudfiles-enhanced/) (PHP 5.3.3+ required for -this feature) -* FEATURE: Add region-selection (Dallas/Chicago/Northern Virginia/Sydney/Hong -Kong) to Rackspace Cloud Files (PHP 5.3.3+ required for this feature) -* FEATURE: Add option to 'Backup Now' dialog to not despatch this backup to -the cloud -* FIX: Fix bug in restore of wpcore (Premium) with certain options when backup -set was from a previously restored backup with the same certain options -* FIX: After restoring a site, only delete the backup set from local storage -if it was also stored in the cloud (prevents the user having to upload the -backup set twice if they want to re-run the restore) -* FIX: Improve detection of extremely long-running/slow jobs -* FIX: Fix issue with Rackspace Cloudfiles on WPMU installs -* TWEAK: Mark as tested up to WordPress 3.8 -* TWEAK: Restore operations are now logged -* TWEAK: Detect the database connection dropping and recover (seen on a very -slow site where PHP ran continuously for 30 mins) -* TWEAK: Change how permalinks are flushed post-restore. This spares the user -from having to manually visit the permalinks page if they had plugins that -altered their permalink structure (e.g. WooCommerce). -* TWEAK: Require fewer file permissions when restoring/migrating -* TWEAK: Remove various spurious PHP notices caught by the post-1.7.41 extra -logging -* TWEAK: Compress the log file before emailing it, if it is over 6Mb -* TWEAK: Make sure some potential error messages from Dropbox are displayed -properly -* TWEAK: Work around sites with site/home URL settings in the WP DB that -erroneously have a trailing slash -* TWEAK: Log PHP notices for all job types - -= 1.7.41 - 2013/11/16 = - -* FIX: Work around bug in some old PHP versions on Windows when creating -database dump -* FIX: If binary mysqldump failed, then retry -* TWEAK: Log PHP notices in the log file -* TWEAK: Allow primitive exclusion based on filename suffixes - -= 1.7.39 - 2013/11/11 = - -* FIX: Correct calculation of which old backups to delete when automatic -pre-plugin/theme backups run -* FIX: Binzip could block if the zip binary produced a lot of unexpected -output -* FIX: Fix a corner-case where a setting needed manual updating post-migration -on WP 3.4+earlier sites with custom uploads path -* FIX: Prevent the settings page needing a refresh if the server temporarily -goes away -* TWEAK: For reasons unknown, Google's new cloud console removes parameters -after the first from the redirect_uri; this breaks new Google Drive -authentications. To work around this, we have adjusted our redirect_uri to use -only one parameter. -* TWEAK: Removed a couple of clicks from the install procedure for add-ons -* TWEAK: Handle migration URL rewrites where content directory location has -been manually altered -* TWEAK: Change default number of backups to retain on new installs from 1 to -2 -* TWEAK: Add extra file permissions check before restoring (prevent unexpected -aborts) -* TWEAK: Suppress a spurious 'insufficient visitors' warning for some sites -with long-running backups -* TWEAK: Prevent spurious message about unexpected SQL if restoring a backup -with very large tables produced by mysqldump -* TWEAK: Catch some more untranslated strings -* TRANSLATIONS: New Russian translation; updated German and Polish -translations - -= 1.7.35 - 2013/10/26 = - -* FIX: Fix potential problem whereby some tables could be missed from the -backup on WPMU (WP multisite) installs. Strongly recommended that all WPMU -users update and take a fresh backup. -* FIX: Work around http://bugs.mysql.com/62077 (could cause permanently stuck -lock on databases with wrong collocations - if you have this problem, then no -backups happen) -* TWEAK: Don't use binzip method on OpenVZ with low memory -* TWEAK: Suppress a couple of spurious messages in the log -* TWEAK: Add facility to quickly download log files in the 'expert settings' -section - -= 1.7.34 - 2013/10/21 = - -* FEATURE: Options in the "Backup Now" dialog to exclude files or database -from the backup (https://updraftplus.com/more-flexibility-in-backup-now/) -* FEATURE: Use binary mysqldump, if available, for much faster dumps of large -tables -* FEATURE: New Ελληνική / Greek translation (el): Κώστας Θερμογιάννης (Kostas -Thermoyiannis) - http://tovivlio.net -* FIX: Fix a JavaScript error in Internet Explorer 8 -* FIX: Under very unusual circumstances, it was still possible for multiple -backup jobs to occur -* FIX: For non-English installs, the email indicating backup success sometimes -failed to send -* FIX: Fix obscure table name error if WP was using bespoke database setup -without delete access -* FIX: On multi-site installs, settings changes could be lost if they were -made during an ongoing backup -* TWEAK: Now marked as WordPress 3.7 compatible -* TWEAK: Raw files list in expert section now makes log files directly -downloadable -* TWEAK: Detect available disk quota in CPanel account (if relevant), log, and -warn if low -* TWEAK: Amazon S3 backend now can use WP proxy settings (if any) -* TWEAK: All multisite settings pages have now been moved to the network admin -section -* TWEAK: Restorer now handles hand-moved non-default WP site directories -(where they differ from the website base directory) -* TWEAK: Migrator can now migrate sub-domain-based WPMU installs with no -manual steps required -* TWEAK: Internationalised the add-ons management page (Premium) -* TWEAK: Switch zip engines from ZipArchive earlier if it appears to be broken -* TWEAK: Now cleans up some previously un-caught temporary files if the backup -aborted unexpectedly -* TWEAK: Remove bogus warning about W3TC object cache -* TWEAK: Backup log file now includes SHA1 checksums -* TWEAK: Add warning for user if their max_execution_time is very low -* TWEAK: Make fewer HTTP requests when tracking download status -* TWEAK: Under certain conditions, the report email could wrongly state that -files were included in a db-only backup -* TWEAK: Improve detection of recent activity on resumptions when zips split -* TWEAK: Prevent some warning messages from being shown twice -* TWEAK: Remove the "that's a lot of rows" warning once the table successfully -finishes being dumped -* TWEAK: Cache the results of looking for a zip executable for the duration of -the job -* TWEAK: Some badly-written plugins place their own code on UD's settings -page, and break the layout; overcome this -* TWEAK: Add a warning for people using encryption without mcrypt installed -(slow) -* TWEAK: Suppress useless warning when using BinZip and only empty directories -exist in 'others' backup - -= 1.7.20 - 2013/09/20 = -* TWEAK: Add semaphore locking to prevent WP's cron system kicking off -multiple jobs on overloaded systems -* TWEAK: Catch and display some previously uncaught AJAX notices when -restoring, and display information on the restore process earlier - -= 1.7.18 - 2013/09/17 = -* FEATURE: New "more -storage" add-on, enabling backing up to multiple storage destinations -* FEATURE: New progress meter on dashboard page when a backup is running -* FEATURE: SCP support (in the SFTP/FTPS/SCP add-on) -* FEATURE: If (and only if) your settings page is open, then UpdraftPlus will -automatically perform tricks to help backups run even if your WordPress -install has its scheduler disabled (of course, enabling your scheduler would -better). -* FIX: Fix bug whereby clicking on 'rescan' lost track of backups sent to -remote storage -* FIX: Fix obscure bug that could cause WPMU installs to not backup all -tables -* FIX: Fix unwanted warning message if the uploads folder was empty -* FIX: Show timestamps of available backup sets in local time zone -* FIX: Email subjects and contents use local time zone -* FIX: Fix mangled pathnames for PclZip one-shot attempts -* FIX: Fix bug that caused files to be dropped if one was in a sub-directory -of the entity and named (entire name) "0" -* FIX: Show correct title on page when upgrading -* FIX: Fix one-character typo that could cause Dropbox uploads to not continue -if Dropbox threw a transient error from their end -* FIX: Permanent solution to conflict with W3TC's object cache (and removal of -advisory notice) -* FIX: Correctly show estimated size of 'others' backup within the expert -section -* FIX: Fix small typo in inline decrypter that led to viewer reading an -incomplete message -* TWEAK: Warn the user if they seem to be a on a dev website that is not -visited + so can't backup -(https://updraftplus.com/faqs/why-am-i-getting-warnings-about-my-site-not-having-enough-visitors/) -* TWEAK: More detection of possible overlaps (use temporary files as evidence) -* TWEAK: Extra check that the directory is writable before unpacking zip in -restore (so user gets friendly error message instead of trickier one) -* TWEAK: Provide option to remember the "automatic backup" setting -* TWEAK: The WebDAV add-on -now has support for WebDAV servers that don't support Content-Range (e.g. -ownCloud) - -= 1.7.3 - 2013/08/26 = -* FIX: Some Dropbox connect errors were being lost -* FIX: Fix detection of availability of binary zip method on PHP installs -where popen() is available put proc_open() is disabled -* FIX: (Premium): WP Core and More Files remaining locally/not being -despatched to cloud storage -* TWEAK: More logging of the success (or not) of backups sent via email -* TWEAK: Remember hint from previous job if PHP is allowed to run for more -than 300 seconds at a time - -= 1.7.1 - 2013/08/20 = -* FIX: Fix error preventing file backups in 1.7.0 for PHP installs without the -ZipArchive class. -* TWEAK: Only include phpseclib in the path when required - -= 1.7.0 - 2013/08/20 = -* FEATURE: Split large sites into multiple zips (see: -https://updraftplus.com/splitting-large-sites-into-multiple-archives/) -* FEATURE: Fix time add-on can now also choose the day of the week -* FEATURE: New add-on/Premium feature - Automatic Backups (automatically take -backups before plugin/theme updates) - https://updraftplus.com/shop/autobackup/ -* FEATURE: Svensk / Swedish translation (sv_SE) by Steve Sandström -(http://www.brandicon.se) -* FEATURE: Français / French translation (fr_FR) by ufo3D - http://ufo-3d.fr/ -and Thomas Jacobsen - http://123informatique.ch/ - with help from Françoise -Lhermitte - http://www.ajwan.net -* TWEAK: Save the result of looking for a binary zip (don't re-test) -* TWEAK: Show 'Last log message' in dashboard using local time zone -* TWEAK: Log file times are now recorded relative to the backup start, rather -than the current resumption start -* TWEAK: More code-tidying and optimisation -* TWEAK: Warn the user if the WordPress scheduler is disabled -* TWEAK: Many + various extra sanity-checks for possible problems -* TWEAK: Warn user if trying to upload an above-limit (>10Gb) file to Google -Drive -* TWEAK: Reduce memory usage during restore -* TWEAK: No longer require mbstring extension for Dropbox -* TWEAK: Move JavaScript into separate file, and make strings translatable -* INTERNALS: PclZip and BinZip methods now have feature parity with ZipArchive -(can resume+split, more logging) -* TWEAK/FIX: When restoring/migrating, split SQL commands to avoid exceeding -MySQL's max_allowed_packet -* FIX: Make sure output buffering is off when sending files from the browser -(prevents memory exhaustion) -* FIX: Prevent double-backup (very unusual combination of circumstances) -* FIX: Some Windows webserver configurations could have corruption of -filenames in WordPress core backups (recoverable) -* FIX: Remove temporary files created by PclZip (where PclZip is used) - -= 1.6.46 - 2013/07/11 = -* FEATURE: New storage back-end for any S3-compatible provider (e.g. Google -Cloud Storage, Eucalyptus, Cloudian, many more - tested with Dreamobjects and -original S3) -* FEATURE: Delete existing backup sets manually (deletes both local + cloud -copies). Also show backup set debugging info in expert options; and counter -now dynamically updates without refresh. -* FEATURE: Restorations + migrations can now be attempted even if the user -lacks CREATE TABLE or DROP TABLE permissions -* FEATURE: Italiano/Italian translation by Francesco Carpana (f.carpana at -gmail.com) -* FEATURE: Chinese (zh_CN) translation by K L Wang (http://klwang.info) -* FEATURE: Re-worked error handling internally, leading to users now being -notified prominently of warning-level conditions (non-fatal conditions, but -things the user should be advised of) -* FEATURE: Allow some hiding of secrets in the admin area (see: -https://updraftplus.com/faqs/in-the-administration-section-it-shows-my-amazon-ftp-etc-passwords-without-using-stars-is-this-safe/) -* FEATURE: Restorer now obtains files at an earlier stage, allowing analysis + -more intelligent presentation of options and applicable warnings pre-restore. -Now warns if you are migrating without having chosen search/replace of DB. -Also pre-decrypts the database, which lessens the risk of timeouts. -* FEATURE: Allow entries in the list of files to exclude from backup to end in -a wildcard (*). Change default exclusion for content dir to include backup* to -catch other backup plugins' archives. -* FIX: "Wipe settings" wipes S3 + DreamObjects settings (they were retained -previously) -* FIX: Suppress spurious "Table prefix has changed" message -* FIX: Now copes on restores/migrations if you've moved around your -WP_CONTENT_DIR/WP_PLUGIN_DIR/UPLOADS folder -* FIX: Escape output of logging lines (prevents on-page JavaScript breakage if -error from cloud service contained unescaped quotes) -* FIX: Fix syntax error in rarely-triggered part of scheduling calculation -algorithm that could cause a dramatic slow-down -* FIX: Tweak the no-activity-for-a-while-when-writing-zip detector to not fire -prematurely (found an extreme corner-case where this caused a problem) -* FIX: The "Test (cloud method) settings" button would fail if credentials -contained a backslash (\), due to WP's automatic doubling of backslashes -* FIX: When restoring, don't make failure to remove a temporary directory an -abortion condition -* FIX: Database dump now retains NULL values for string fields (instead of -making them empty strings) -* FIX: Remove directories that cause an error during restoration when user -restores from a backup made from a previously-restored site without removing -old directories when requested. -* TWEAK: Detect WP installs with broken plugins that add extra white-space -(thus breaking AJAX output) -* TWEAK: When running on (old) MySQL 4.1, replace TYPE= with ENGINE= for -compatibility with later MySQL versions -* TWEAK: Detect which MySQL engines are available on the restoring side, and -switch if the requested engine is not available; remove PAGE_CHECKSUM and -TRANSACTIONAL options if engine was (M)aria. Always remove (removed from -upstream) PAGE_CHECKSUM from MyISAM. -* TWEAK: Batch database rows by the 1000 instead of 100 - proved to be 3x -faster on massive MyISAM tables -* TWEAK: Abort a restoration if the first CREATE TABLE command produces an -error (rather than continue and likely have many more) -* TWEAK: Replace one use of method_exists() to prevent triggering segfault in -some faulty PHP installs -* TWEAK: Allow an extra attempt if in "over-time" - allows recovery from -transient errors (e.g. cloud service temporary outage) in over-time. -* TWEAK: Work-around WP installs with broken cacheing setups where cache -deletion is not working -* TWEAK: If ZipArchive::close() fails, then log the list of files we were -trying to add at the time -* TWEAK: Detect generous amounts of time allowed for running better, and -schedule appropriately -* TWEAK: Add detection of jQuery errors in the admin page, and direct users -with such errors to a help page -* TWEAK: More aggressively clean up temporary files (can lower temporary disk -space requirements) -* TWEAK: Provide the error message sent by Google upon initial Drive -authentication errors. -* TWEAK: Found a case where PHP's is_writable() erroneously returns true - -actually test a write -* TWEAK: Use pseudo-namespacing on the CloudFiles library to prevent clashes -with other plugins (pre-emptive - no known conflicts exist) -* TWEAK: Use higher priority on cron backup schedules call to prevent other -plugins which call WP wrongly from over-writing new cron schedules (e.g. -BackupBuddy) - -= 1.6.17 - 2013/06/06 = -* FEATURE: News blog - https://updraftplus.com/news/ - please subscribe if you -want to stay up to date with news of new features, tips, and special offers. -RSS link: http://feeds.feedburner.com/UpdraftPlus -* FEATURE: Restoration/migration now copes with a change of table prefix, and -asks WordPress to recreate your .htaccess/web.config file -* FEATURE: Add support for DreamHost DreamObjects -(http://dreamhost.com/cloud/dreamobjects/) -* FEATURE: Polski / Polish (pl_PL) translation: thanks to Bartosz Kaczmarek -(barth.kaczmarek at gmail.com) -* FEATURE: Add expert options to count expected uncompressed backup size, -show/delete active jobs, and PHP info -* FEATURE: Send backup reports to multiple addresses (comma-separate the -addresses you wish to use) -* FIX: Inform users of Dropbox tokens which stop working -* FIX: Don't flag an error if mu-plugins are selected, but none are found and -WordPress agrees that none exist -* COMPATIBILITY: WordPress multisite post-3.5 does not store blog uploads -separately from main uploads directory -* COMPATIBILITY: Now marked as compatible with WordPress 3.6 -* TWEAK: When errors occur, list them in the notification email and attach the -log file -* TWEAK: Use only one transient per job, and clean it up upon completion -* TWEAK: Added a "Clone/Migrate" button to give a visual clue for people -wanting to do this -* TWEAK: More verbose error reporting from PclZip -* TWEAK: After database restoration, permalinks are flushed (often helps -regenerate .htaccess+web.config files) -* TWEAK: Database backups now put the options table first, to allow earlier -changing of site URL upon migration -* TWEAK: Show PHP + web server versions in the debug information -* TWEAK: More sophisticated attempts to get a writable backup directory, and -more helpful messages if we can't -* TWEAK: Some more logging, data-gathering and algorithm-tweaking to -especially improve the chances for people with astonishingly slow web hosting, -but also tweaks that improve efficiency everywhere, especially for larger -backup sets. -* TWEAK: Migrator plugin now does search+replace after each table (instead of -after them all) -* TWEAK: Clean up temporary files earlier where safe+possible (can lower disk -space requirements) -* TWEAK: Re-scan of known sets now removes those known to be gone from the -list -* TWEAK: Made a few things use AJAX instead of full page loads -* TWEAK: Replace Rackspace logo with current version -* TWEAK: Make missing PHP component warnings more prominent -* TWEAK: Warn users if they have W3 Total Cache's object cache (which has a -bug that affects scheduled tasks) active. -* TWEAK: Add a notice for users who have turned on debugging (some forget to -turn it off, then ask for support when they see lots of debugging notices) - -= 1.6.2 - 05/11/2013 = -* FIX: Prevent PHP fatal error on some database restores - -= 1.6.1 - 05/06/2013 = -* FEATURE: New "Migrator" add-on for moving sites from one WordPress install -to another (https://updraftplus.com/shop/) -* FEATURE: The "More files" add-on can now backup any files from anywhere on -your filesystem (not just parts of WordPress) -* FEATURE: The "More files" add-on can now exclude specified directories from -the backup of WordPress core -* FEATURE: Dropbox and Google Drive now check available quota before uploading -* FEATURE: Nederlands / Dutch (nl_NL) translation: thanks to Hans van der -Vlist - hansvandervlist at gmail.com -* FEATURE: The SFTP/FTPS add-on now supports implicit encryption (so now both -explicit + implicit are supported) -* FIX: Google Drive now requires additional permissions to download your files -- you will need to re-authenticate if you are downloading or restoring. -* FIX: Fix serious corruption issue in larger Rackspace Cloud Files backups -(fixed a bug in Rackspace's Cloud Files library) -* FIX: Fix mcrypt call in Dropbox module to be compatible with PHP 5.2 on -Windows, and with ancient FreeBSD versions which have no /dev/urandom -* FIX: Allow top-level "Restore" button even if no backup sets currently known -(to allow uploading some) -* FIX: Fixed issues hindering restoration on web hosting setups with file -permissions that invoked WP's remote filesystem methods -* TWEAK: Database backup now includes more info about original WP install -(e.g. WP/PHP versions) -* TWEAK: The "More files" add-on now allows the user to choose whether to -restore wp-config.php or not (and gives help) -* TWEAK: Added an approximate expected row count when beginning to dump out a -table -* TWEAK: Remove the Google Drive URL prefix automatically for those who don't -spot the instruction to do so - -= 1.5.22 - 04/16/2013 = -* FIX: 1.5.21 broke Dropbox authentication for some users. Upgrade if you had -that issue. - -= 1.5.21 - 04/15/2013 = -* FEATURE: Now restores databases (we recommend the MySQL command-line for -versions created with previous versions of UpdraftPlus) -* FEATURE: Rackspace Cloud Files support -(http://www.rackspace.com/cloud/files/) -* FEATURE: Built-in multi-uploader, allowing easier restoration of old backup -sets -* FEATURE: Allow instant downloading of the most recently modified log file -* FEATURE: Built in drag-and-drop database decrypter for manual decryption -* FEATURE: Deutsch / German translation: thanks to Marcel Herrguth - mherrguth -at mrgeneration.de -* FEATURE: Magyar / Hungarian translation: thanks to Szépe Viktor - -http://www.szepe.net -* FEATURE: Spanish / Español translation: thanks to Fernando Villasmil - -villasmil.fernando at gmail.com -* FEATURE: Added encryption (used by default) to Amazon S3 communications -* FEATURE: New "more files" add-on, allowing backup of WordPress core and -non-WordPress files -* RELIABILITY: Various algorithm tweaks to help larger sites on lower -resources. Largest site a known user has: 1.5Gb -* RELIABILITY/FEATURE: Ship up-to-date SSL certificates, and added expert -options to prefer server SSL CA certificates, and to disable peer verification -* SPEED: Batch INSERT commands in database backups, for much faster -restoration (typically 95% faster) -* SPEED/RELIABILITY: FTP and FTPS (not SFTP) are now chunked and resumable -(both download and upload), subject to your FTP server responding correctly to -SIZE -* SPEED: Re-factoring of admin-area and some backup code into separate -lazy-loaded files, to reduce memory consumption on sites generally -* FIX: Clear PHP's file stat cache when checking for zip file activity - fixes -potential halt on very enormous sites or sites with very low PHP timeouts. -* FIX: Caught some untranslated strings -* FIX: Respect WordPress's WP_MAX_MEMORY_LIMIT constant -* FIX: Remove timezone display from local time - WordPress's get_date_from_gmt -function does not completely do what the manual says it does -* FIX: A small typo slipped into 1.5.5 which prevented some Google Drive users -from setting up new installations -* FIX: Fix strict coding warnings on PHP 5.4 -* TWEAK: In fix-time add-on, fade UI when relevant -* TWEAK: Improved UI of downloader -* TWEAK: Decrease FTP timeouts to improve our chances of getting back an error -before PHP aborts -* TWEAK: Tweaked al relevant methods to follow the general SSL CA certificate -options - -= 1.5.5 - 03/26/2013 = -* Now translatable - .pot file included (translators welcome!) -* When restoring, you can now select only some components to restore -* History of previous backups can re-scan to find backups manually imported -(e.g. via FTP) (trunk has drag-and-drop uploader) -* Multisite add-on (https://updraftplus.com/shop/) now stores/restores blogs -and mu-plugins separately -* Display UpdraftPlus's disk space usage -* Internationalisation hooks in main body of plugin -* Correctly remove old 'other' directories from a restoration when requested -* Various layout + niceness fixes upon restoration -* Prevent deletion of local archives upon failed restoration when there was no -cloud storage -* Various usability tweaks for the admin UI, including showing multisite -warning only on UD's pages -* Fix incorrect restoration (since 1.4.0) of directory-less paths from -'others' zip -* Fix prevention of Dropbox re-authentication when Dropbox returns 5xx first -time (library error) -* Clear Dropbox credentials if the user explicitly re-authenticates -* Clean up temporary files left behind by zipArchive::addFile -* Tweak Dropbox library to work from behind very weird proxies that -double-surround the HTTP header -* Improved help for people with broken schedulers -* Fix FTP download error - -= 1.4.48 - 03/11/2013 = -* Improve batching on zip creation for sites with very large files -* Unlimited early resumption if zip file creation takes too long -* Suppress some warning notices that can break JavaScript on sites with -notices sent to the browser -* Earlier warning/failure if backup directory was not writable -* Hooks for Dropbox folders add-on -* More scheduler/overlap tweaks, to assist enormous uploads -* When the temporary directory is within the site, store+display relatively -(removes need to modify upon site move) -* Sort existing backups display by date -* Use WordPress time for creation of filenames -* Fix bug in 1.4.47 which caused problems on new site installs -* Prevent erroneous warning when backup zip (usually uploads) has no files - -= 1.4.30 - 03/04/2013 = -* Hooks for WebDAV support via add-on - -= 1.4.29 - 02/23/2013 = -* Now remembers what cloud service you used for historical backups, if you -later switch -* Now performs user downloads from the settings page asynchronously, meaning -that enormous backups can be fetched this way -* Fixed bug which forced GoogleDrive users to re-authenticate unnecessarily -* Fixed apparent race condition that broke some backups -* Include disk free space warning -* More intelligent scheduling of resumptions, leading to faster completion on -hosts with low max_execution_time values -* Polls and updates in-page backup history status (no refresh required) -* Hooks for SFTP + encrypted FTP add-on - -= 1.4.14 - 02/19/2013 = -* Display final status message in email -* Clean-up any old temporary files detected - -= 1.4.13 - 02/18/2013 = -* Some extra hooks for "fix time" add-on -(https://updraftplus.com/shop/fix-time/) -* Some internal simplification -* Small spelling + text fixes - -= 1.4.11 - 02/13/2013 = -* Various branding tweaks - launch of -updraftplus.com -* Important fix for people with non-encrypted database backups - -= 1.4.9 - 02/12/2013 = -* Do more when testing Amazon S3 connectivity (catches users with bucket but -not file access) -* Tweak algorithm for detecting useful activity to further help gigantic sites - -= 1.4.7 - 02/09/2013 = -* Tweak for some Amazon EU West 1 bucket users - -= 1.4.6 - 02/07/2013 = -* Amazon S3 now works for users with non-US buckets -* Further tweak to overlap detection - -= 1.4.2 - 02/06/2013 = -* More Amazon S3 logging which should help people with wrong details -* More race/overlap detection, and more flexible rescheduling - -= 1.4.0 - 02/04/2013 = -* Zip file creation is now resumable; and thus the entire backup operation is; -there is now no "too early to resume" point. So even the most enormous site -backups should now be able to proceed. -* Prefer PHP's native zip functions if available - 25% speed-up on zip -creation - -= 1.3.22 - 01/31/2013 = -* More help for really large uploads; dynamically alter the maximum number of -resumption attempts if something useful is still happening - -= 1.3.20 - 01/30/2013 = -* Add extra error checking in S3 method (can prevent logging loop) - -= 1.3.19 - 01/29/2013 = -* Since 1.3.3, the 'Last Backup' indicator in the control panel had not been -updating - -= 1.3.18 - 01/28/2013 = -* Made 'expert mode' easier to operate, and tidier options for non-expert -users. -* Some (not total) compliance with PHP's strict coding standards mode -* More detail provided when failing to authorise with Google - -= 1.3.15 - 01/26/2013 = -* Various changes to Google Drive authentication to help those who don't enter -the correct details first time, or who later need to change accounts. - -= 1.3.12 - 01/25/2013 = -* 1.3.0 to 1.3.8 had a fatal flaw for people with large backups. -* 1.3.0 to 1.3.9 gave erroneous information in the email reports on what the -backup contained. -* Fixed DropBox authentication for some users who were having problems - -= 1.3.8 - 01/24/2013 = -* Fixed faulty assumptions in 'resume' code, now leading to more reliable -resuming -* Removed some duplicate code; first attempt and resumptions now uses same -code -* Added further parameters that should be removed on a wipe operation -* More logging of detected double runs - -= 1.3.2 - 01/23/2013 = -* Internal reorganisation, enabling UpdraftPlus Premium - -= 1.2.46 - 01/22/2013 = -* Easier Dropbox setup (we are now an official production app) -* New button to delete all existing settings -* Admin console now displays rolling status updates -* Feature: choose how many files and databases to retain separately -* Fixed bug with checking access token on Google Drive restore -* Fixed bug producing copious warnings in PHP log -* Fixed bug in automated restoration processes -* Possibly fixed settings saving bug in RTL installations -* Fix erroneous display of max_execution_time warning -* Better logging when running a DB debug session -* Better detection/handling of overlapping/concurrent runs - -= 1.2.31 - 01/15/2013 = -* Fixed bug with Dropbox deletions -* Fixed cases where Dropbox failed to resume chunked uploading -* Can now create uncreated zip files on a resumption attempt -* FTP method now supports SSL (automatically detected) -* New "Test FTP settings" button -* Less noise when debugging is turned off -* Fix bug (in 1.2.30) that prevented some database uploads completing - -= 1.2.20 - 01/12/2013 = -* Dropbox no longer limited to 150Mb uploads -* Dropbox can upload in chunks and resume uploading chunks -* Improved Dropbox help text - -= 1.2.18 - 01/11/2013 = -* Revert Dropbox to CURL-only - was not working properly with WordPress's -built-in methods -* Add note that only up to 150Mb is possible for a Dropbox upload, until we -change our API usage -* Fix unnecessary repetition of database dump upon resumption of a failed -backup - -= 1.2.14 - 01/08/2013 = -* Dropbox support (no chunked uploading yet, but otherwise complete) -* Make the creation of the database dump also resumable, for people with -really slow servers -* Database table backups are now timed -* FTP logging slightly improved -* Dropbox support uses WordPress's built-in HTTP functions - -= 1.1.16 - 01/07/2013 = -* Requested feature: more frequent scheduling options requested -* Fixed bug which mangled default suggestion for backup working directory on -Windows -* Provide a 'Test S3 Settings' button for Amazon S3 users - -= 1.1.11 - 01/04/2013 = -* Bug fix: some backup runs were erroneously being identified as superfluous -and cancelled - -= 1.1.9 - 12/31/2012 = -* Big code re-factoring; cloud access methods now modularised, paving way for -easier adding of new methods. Note that Google Drive users may need to -re-authenticate - please check that your backups are working. -* Fix bug whereby some resumptions of failed backups were erroneously -cancelled -* Database encryption made part of what is resumable - -= 1.0.16 - 12/24/2012 = -* Improve race detection and clean up already-created files when detected - -= 1.0.15 - 12/22/2012 = -* Fixed bug that set 1Tb (instead of 1Mb) chunk sizes for Google Drive uploads -* Added link to some screenshots to help with Google Drive setup -* Allowed use of existing Amazon S3 buckets with restrictive policies -(previously, we tested for the bucket's existence by running a create -operation on it, which may not be permitted) -* Use WordPress's native HTTP functions for greater reliability when -performing Google Drive authorisation -* Deal with WP-Cron racey double events (abort superceeded backups) -* Allow user to download logs from admin interface - -= 1.0.5 - 12/13/2012 = -* Tweaked default Google Drive options - -= 1.0.4 - 12/10/2012 = -* Implemented resumption/chunked uploading on Google Drive - much bigger sites -can now be backed up -* Fixed bug whereby setting for deleting local backups was lost -* Now marked as 1.0, since we are feature-complete with targeted features for -this release -* Made description fuller - -= 0.9.20 - 12/06/2012 = -* Updated to latest S3.php library with chunked uploading patch -* Implemented chunked uploading on Amazon S3 - much bigger sites can now be -backed up with S3 - -= 0.9.10 - 11/22/2012 = -* Completed basic Google Drive support (thanks to Sorin Iclanzan, code taken -from "Backup" plugin under GPLv3+); now supporting uploading, purging and -restoring - i.e. full UpdraftPlus functionality -* Licence change to GPLv3+ (from GPLv2+) to allow incorporating Sorin's code -* Tidied/organised the settings screen further - -= 0.9.2 - 11/21/2012 = -* Failed uploads can now be re-tried, giving really big blogs a better -opportunity to eventually succeed uploading - -= 0.8.51 - 11/19/2012 = -* Moved screenshot into assets, reducing plugin download size - -= 0.8.50 - 10/13/2012 = -* Important new feature: backup other directories found in the WP content -(wp-content) directory (not just plugins/themes/uploads, as in original -Updraft) - -= 0.8.37 - 10/12/2012 = -* Don't whinge about Google Drive authentication if that method is not current - -= 0.8.36 - 10/03/2012 = -* Support using sub-directories in Amazon S3 -* Some more debug logging for Amazon S3 - -= 0.8.33 - 09/19/2012 = -* Work around some web hosts with invalid safe_mode configurations - -= 0.8.32 - 09/17/2012 = -* Fix a subtle bug that caused database tables from outside of this WordPress -install to be backed up - -= 0.8.31 - 09/08/2012 = -* Fixed error deleting old S3 backups. If your expired S3 backups were not -deleted, they should be in future - but you will need to delete manually those -that expired before you installed this update. -* Fixed minor bug closing log file -* Marked as working with WordPress 3.4.2 - -= 0.8.29 - 06/29/2012 = -* Marking as tested up to WordPress 3.4.1 - -= 0.8.28 - 06/06/2012 = -* Now experimentally supports Google Drive (thanks to Sorin Iclanzan, code -re-used from his Google Drive-only 'backup' plugin) -* New feature: backup files and database on separate schedules -* Tidied and improved retain behaviour - -= 0.7.7 - 05/29/2012 = -* Implementation of a logging mechanism to allow easier debugging and -development - -= 0.7.4 - 05/21/2012 = -* Removed CloudFront method; I have no way of testing this -* Backup all tables found in the database that have this site's table prefix -* If encryption fails, then abort (don't revert to not encrypting) -* Added ability to decrypt encrypted database backups -* Added ability to opt out of backing up each file group -* Now adds database character set, the lack of which before made database -backups unusable without modifications -* Version number bump to make clear that this is an improvement on the -original Updraft, and is now tried and tested - -= 0.1.3 - 01/16/2012 = -* Force backup of all tables found in database (vanilla Updraft only backed up -WP core tables) -* Tweak notification email to include site name - -= 0.1 - 08/10/2011 = - -* A fork of Updraft Backup/Restore 0.6.1 by Paul Kehrer with the following -improvements -* Replaced deprecated function calls (in WordPress 3.2.1) -* Removed all warnings from basic admin page with WP_DEBUG on -* Implemented encrypted backup (but not yet automatic restoration) on database -* Some de-uglification of admin interface diff --git a/wordpress/wp-content/plugins/updraftplus/class-updraftplus.php b/wordpress/wp-content/plugins/updraftplus/class-updraftplus.php deleted file mode 100644 index a17904b65c9725241f05e8097ea35a05e5a02d98..0000000000000000000000000000000000000000 --- a/wordpress/wp-content/plugins/updraftplus/class-updraftplus.php +++ /dev/null @@ -1,5842 +0,0 @@ - 'UpdraftPlus Vault', - 'dropbox' => 'Dropbox', - 's3' => 'Amazon S3', - 'cloudfiles' => 'Rackspace Cloud Files', - 'googledrive' => 'Google Drive', - 'onedrive' => 'Microsoft OneDrive', - 'ftp' => 'FTP', - 'azure' => 'Microsoft Azure', - 'sftp' => 'SFTP / SCP', - 'googlecloud' => 'Google Cloud', - 'backblaze' => 'Backblaze', - 'webdav' => 'WebDAV', - 's3generic' => 'S3-Compatible (Generic)', - 'openstack' => 'OpenStack (Swift)', - 'dreamobjects' => 'DreamObjects', - 'email' => 'Email' - ); - - public $errors = array(); - - public $nonce; - - public $file_nonce; - - public $logfile_name = ""; - - public $logfile_handle = false; - - public $backup_time; - - public $job_time_ms; - - public $opened_log_time; - - private $backup_dir; - - private $jobdata; - - public $something_useful_happened = false; - - public $have_addons = false; - - // Used to schedule resumption attempts beyond the tenth, if needed - public $current_resumption; - - public $newresumption_scheduled = false; - - public $cpanel_quota_readable = false; - - public $error_reporting_stop_when_logged = false; - - private $combine_jobs_around; - - // Used for reporting - private $attachments; - - private $remotestorage_extrainfo = array(); - - /** - * Class constructor - */ - public function __construct() { - global $pagenow; - // Initialisation actions - takes place on plugin load - - if ($fp = fopen(UPDRAFTPLUS_DIR.'/updraftplus.php', 'r')) { - $file_data = fread($fp, 1024); - if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) { - $this->version = $matches[1]; - } - fclose($fp); - } - - $load_classes = array( - 'UpdraftPlus_Backup_History' => 'includes/class-backup-history.php', - 'UpdraftPlus_Encryption' => 'includes/class-updraftplus-encryption.php', - 'UpdraftPlus_Manipulation_Functions' => 'includes/class-manipulation-functions.php', - 'UpdraftPlus_Filesystem_Functions' => 'includes/class-filesystem-functions.php', - 'UpdraftPlus_Storage_Methods_Interface' => 'includes/class-storage-methods-interface.php', - 'UpdraftPlus_Job_Scheduler' => 'includes/class-job-scheduler.php', - ); - - foreach ($load_classes as $class => $relative_path) { - if (!class_exists($class)) include_once(UPDRAFTPLUS_DIR.'/'.$relative_path); - } - - // Create admin page - add_action('init', array($this, 'handle_url_actions')); - add_action('init', array($this, 'updraftplus_single_site_maintenance_init')); - // Run earlier than default - hence earlier than other components - // admin_menu runs earlier, and we need it because options.php wants to use $updraftplus_admin before admin_init happens - add_action(apply_filters('updraft_admin_menu_hook', 'admin_menu'), array($this, 'admin_menu'), 9); - // Not a mistake: admin-ajax.php calls only admin_init and not admin_menu - add_action('admin_init', array($this, 'admin_menu'), 9); - add_action('admin_init', array($this, 'wordpress_55_updates_potential_migration')); - - // The two actions which we schedule upon - add_action('updraft_backup', array($this, 'backup_files')); - add_action('updraft_backup_database', array($this, 'backup_database')); - - // The three actions that can be called from "Backup Now" - add_action('updraft_backupnow_backup', array($this, 'backupnow_files')); - add_action('updraft_backupnow_backup_database', array($this, 'backupnow_database')); - add_action('updraft_backupnow_backup_all', array($this, 'backup_all')); - - // backup_all as an action is legacy (Oct 2013) - there may be some people who wrote cron scripts to use it - add_action('updraft_backup_all', array($this, 'backup_all')); - - // This is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc. - add_action('updraft_backup_resume', array($this, 'backup_resume'), 10, 3); - - // If files + db are on different schedules but are scheduled for the same time, then combine them - add_filter('schedule_event', array($this, 'schedule_event')); - - add_action('plugins_loaded', array($this, 'plugins_loaded')); - - // Since the WordPress version 5.5, we are no longer forcing an auto update by hooking the auto_update_plugin filter because WordPress does something different to its auto-update interface - if (version_compare($this->get_wordpress_version(), '5.5', '<')) { - // Auto update plugin - add_filter('auto_update_plugin', array($this, 'maybe_auto_update_plugin'), 20, 2); - } - - // Prevent iThemes Security from telling people that they have no backups (and advertising them another product on that basis!) - add_filter('itsec_has_external_backup', '__return_true', 999); - add_filter('itsec_external_backup_link', array($this, 'itsec_external_backup_link'), 999); - add_filter('itsec_scheduled_external_backup', array($this, 'itsec_scheduled_external_backup'), 999); - - add_action('updraft_report_remotestorage_extrainfo', array($this, 'report_remotestorage_extrainfo'), 10, 3); - - // Prevent people using WP < 5.5 upgrading from being baffled by WP's obscure error message. See: https://core.trac.wordpress.org/ticket/27196 - - if (version_compare($this->get_wordpress_version(), '5.4.99999999', '<')) { - add_filter('upgrader_source_selection', array($this, 'upgrader_source_selection'), 10, 4); - } - - // register_deactivation_hook(__FILE__, array($this, 'deactivation')); - if (!empty($_POST) && !empty($_GET['udm_action']) && 'vault_disconnect' == $_GET['udm_action'] && !empty($_POST['udrpc_message']) && !empty($_POST['reset_hash'])) { - add_action('wp_loaded', array($this, 'wp_loaded_vault_disconnect'), 1); - } - - // Remove the notice on the Updates page that confuses users who already have backups installed - if ('update-core.php' == $pagenow) { - // added filter here instead of admin.php because the jetpack_just_in_time_msgs filter applied in init hook - add_filter('jetpack_just_in_time_msgs', '__return_false', 20); - } - } - - /** - * Enables automatic updates for the plugin. - * - * @access public - * @see __construct - * @internal uses auto_update_plugin filter - * - * @param bool $update Whether the item has automatic updates enabled - * @param object $item Object holding the asset to be updated - * @return bool True of automatic updates enabled, false if not - */ - public function maybe_auto_update_plugin($update, $item) { - if (!isset($item->plugin) || basename(UPDRAFTPLUS_DIR).'/updraftplus.php' !== $item->plugin) return $update; - $option_auto_update_settings = (array) get_site_option('auto_update_plugins', array()); - return in_array($item->plugin, $option_auto_update_settings, true); - } - - /** - * Called by the WP action updraft_report_remotestorage_extrainfo - * - * @param String $service - * @param String $info_html - the HTML version of the extra info - * @param String $info_plain - the plain text version of the extra info - */ - public function report_remotestorage_extrainfo($service, $info_html, $info_plain) { - $this->remotestorage_extrainfo[$service] = array('pretty' => $info_html, 'plain' => $info_plain); - } - - /** - * WP filter upgrader_source_selection. We use it to tweak the error message shown when an install of a new version is prevented by the existence of an existing version (i.e. us!), to give the user some actual useful information instead of WP's default. - * - * @param String $source File source location. - * @param String $remote_source Remote file source location. - * @param WP_Upgrader $upgrader_object WP_Upgrader instance. - * @param Array $hook_extra Extra arguments passed to hooked filters. - * - * @return String - filtered value - */ - public function upgrader_source_selection($source, $remote_source, $upgrader_object, $hook_extra = array()) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use - - static $been_here_already = false; - - if ($been_here_already || !is_array($hook_extra) || empty($hook_extra['type']) || 'plugin' !== $hook_extra['type'] || empty($hook_extra['action']) || 'install' !== $hook_extra['action'] || empty($source) || 'updraftplus' !== basename(untrailingslashit($source)) || !class_exists('ReflectionObject')) return $source; - - $been_here_already = true; - - $reflect = new ReflectionObject($upgrader_object); - - $properties = $reflect->getProperty('strings'); - - if (!$properties->isPublic() || !is_array($upgrader_object->strings) || empty($upgrader_object->strings['folder_exists'])) return $source; - - $upgrader_object->strings['folder_exists'] .= ' '.__('A version of UpdraftPlus is already installed. WordPress will only allow you to install your new version after first de-installing the existing one. That is safe - all your settings and backups will be retained. So, go to the "Plugins" page, de-activate and de-install UpdraftPlus, and then try again.', 'updraftplus'); - - return $source; - - } - - /** - * WordPress filter itsec_scheduled_external_backup - from iThemes Security - * - * @return Boolean - filtered value - */ - public function itsec_scheduled_external_backup() { - return wp_next_scheduled('updraft_backup') ? true : false; - } - - /** - * WordPress filter itsec_external_backup_link - from iThemes security - * - * @return String - filtered value - */ - public function itsec_external_backup_link() { - return UpdraftPlus_Options::admin_page_url().'?page=updraftplus'; - } - - /** - * This method will disconnect UpdraftVault accounts. - * - * @return Array - returns the saved options if an error is encountered. - */ - public function wp_loaded_vault_disconnect() { - $opts = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault'); - - if (is_wp_error($opts)) { - if ('recursion' !== $opts->get_error_code()) { - $msg = "UpdraftVault (".$opts->get_error_code()."): ".$opts->get_error_message(); - $this->log($msg); - error_log("UpdraftPlus: $msg"); - } - // The saved options had a problem; so, return the new ones - return $opts; - } elseif (!empty($opts['settings'])) { - - foreach ($opts['settings'] as $storage_options) { - if (!empty($storage_options['token']) && $storage_options['token']) { - $site_id = $this->siteid(); - $hash = hash('sha256', $site_id.':::'.$storage_options['token']); - if ($hash == $_POST['reset_hash']) { - $this->log('This site has been remotely disconnected from UpdraftPlus Vault'); - include_once(UPDRAFTPLUS_DIR.'/methods/updraftvault.php'); - $vault = new UpdraftPlus_BackupModule_updraftvault(); - $vault->ajax_vault_disconnect(); - // Die, as the vault method has already sent output - die; - } else { - $this->log('An invalid request was received to disconnect this site from UpdraftPlus Vault'); - } - } - echo json_encode(array('disconnected' => 0)); - } - } - die; - } - - /** - * Gets an RPC object, and sets some defaults on it that we always want - * - * @param string $indicator_name indicator name - * @return array - */ - public function get_udrpc($indicator_name = 'migrator.updraftplus.com') { - if (!class_exists('UpdraftPlus_Remote_Communications')) include_once(apply_filters('updraftplus_class_udrpc_path', UPDRAFTPLUS_DIR.'/includes/class-udrpc.php', $this->version)); - $ud_rpc = new UpdraftPlus_Remote_Communications($indicator_name); - $ud_rpc->set_can_generate(true); - return $ud_rpc; - } - - /** - * Ensure that the indicated phpseclib classes are available - * - * @param String|Array $classes - a class, or list of classes. There used to be a second parameter with paths to include; but this is now inferred from $classes; and there's no backwards compatibility problem because sending more parameters than are used is acceptable in PHP. - * - * @return Boolean|WP_Error - */ - public function ensure_phpseclib($classes = array()) { - - $classes = (array) $classes; - - $this->no_deprecation_warnings_on_php7(); - - $any_missing = false; - - foreach ($classes as $cl) { - if (!class_exists($cl)) $any_missing = true; - } - - if (!$any_missing) return true; - - $ret = true; - - // From phpseclib/phpseclib/phpseclib/bootstrap.php - we nullify it there, but log here instead - if (extension_loaded('mbstring')) { - // 2 - MB_OVERLOAD_STRING - // @codingStandardsIgnoreLine - if (ini_get('mbstring.func_overload') & 2) { - // We go on to try anyway, in case the caller wasn't using an affected part of phpseclib - // @codingStandardsIgnoreLine - $ret = new WP_Error('mbstring_func_overload', 'Overloading of string functions using mbstring.func_overload is not supported by phpseclib.'); - } - } - - $phpseclib_dir = UPDRAFTPLUS_DIR.'/vendor/phpseclib/phpseclib/phpseclib'; - if (false === strpos(get_include_path(), $phpseclib_dir)) set_include_path(get_include_path().PATH_SEPARATOR.$phpseclib_dir); - foreach ($classes as $cl) { - $path = str_replace('_', '/', $cl); - if (!class_exists($cl)) include_once($phpseclib_dir.'/'.$path.'.php'); - } - - return $ret; - } - - /** - * Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on - */ - private function no_deprecation_warnings_on_php7() { - // PHP_MAJOR_VERSION is defined in PHP 5.2.7+ - // We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to). - // @codingStandardsIgnoreLine - if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) { - $old_level = error_reporting(); - // @codingStandardsIgnoreLine - $new_level = $old_level & ~E_DEPRECATED; - if ($old_level != $new_level) error_reporting($new_level); - $this->no_deprecation_warnings = true; - } - } - - /** - * Attempt to close the connection to the browser, optionally with some output sent first, whilst continuing execution - * - * @param String $txt - output to send - */ - public function close_browser_connection($txt = '') { - // Close browser connection so that it can resume AJAX polling - header('Content-Length: '.(empty($txt) ? '0' : 4+strlen($txt))); - header('Connection: close'); - header('Content-Encoding: none'); - if (function_exists('session_id') && session_id()) session_write_close(); - echo "\r\n\r\n"; - echo $txt; - // These two added - 19-Feb-15 - started being required on local dev machine, for unknown reason (probably some plugin that started an output buffer). - $ob_level = ob_get_level(); - while ($ob_level > 0) { - ob_end_flush(); - $ob_level--; - } - flush(); - if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); - } - - /** - * Returns the number of bytes free, if it can be detected; otherwise, false - * Presently, we only detect CPanel. If you know of others, then feel free to contribute! - */ - public function get_hosting_disk_quota_free() { - if (!@is_dir('/usr/local/cpanel') || $this->detect_safe_mode() || !function_exists('popen') || (!@is_executable('/usr/local/bin/perl') && !@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) || (defined('UPDRAFTPLUS_SKIP_CPANEL_QUOTA_CHECK') && UPDRAFTPLUS_SKIP_CPANEL_QUOTA_CHECK)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $perl = (@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ? '/usr/local/cpanel/3rdparty/bin/perl' : '/usr/local/bin/perl';// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $exec = "UPDRAFTPLUSKEY=updraftplus $perl ".UPDRAFTPLUS_DIR."/includes/get-cpanel-quota-usage.pl"; - - $handle = @popen($exec, 'r');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (!is_resource($handle)) return false; - - $found = false; - $lines = 0; - while (false === $found && !feof($handle) && $lines<100) { - $lines++; - $w = fgets($handle); - // Used, limit, remain - if (preg_match('/RESULT: (\d+) (\d+) (\d+) /', $w, $matches)) { - $found = true; - } - } - $ret = pclose($handle); - if (false === $found || 0 != $ret) return false; - - if ((int) $matches[2]<100 || ($matches[1] + $matches[3] != $matches[2])) return false; - - $this->cpanel_quota_readable = true; - - return $matches; - } - - /** - * Fetch information about the most recently modified log file - * - * @return Array - lists the modification time, the full path to the log file, and the log's nonce (ID) - */ - public function last_modified_log() { - $updraft_dir = $this->backups_dir_location(); - - $log_file = ''; - $mod_time = false; - $nonce = ''; - - if ($handle = @opendir($updraft_dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - while (false !== ($entry = readdir($handle))) { - // The latter match is for files created internally by zipArchive::addFile - if (preg_match('/^log\.([a-z0-9]+)\.txt$/i', $entry, $matches)) { - $mtime = filemtime($updraft_dir.'/'.$entry); - if ($mtime > $mod_time) { - $mod_time = $mtime; - $log_file = $updraft_dir.'/'.$entry; - $nonce = $matches[1]; - } - } - } - @closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - return array($mod_time, $log_file, $nonce); - } - - /** - * This function may get called multiple times, so write accordingly - */ - public function admin_menu() { - // We are in the admin area: now load all that code - global $updraftplus_admin; - if (empty($updraftplus_admin)) include_once(UPDRAFTPLUS_DIR.'/admin.php'); - - if (isset($_GET['wpnonce']) && isset($_GET['page']) && isset($_GET['action']) && 'updraftplus' == $_GET['page'] && 'downloadlatestmodlog' == $_GET['action'] && wp_verify_nonce($_GET['wpnonce'], 'updraftplus_download')) { - - list($mod_time, $log_file, $nonce) = $this->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - - if ($mod_time >0) { - if (is_readable($log_file)) { - header('Content-type: text/plain'); - readfile($log_file); - exit; - } else { - add_action('all_admin_notices', array($this, 'show_admin_warning_unreadablelog')); - } - } else { - add_action('all_admin_notices', array($this, 'show_admin_warning_nolog')); - } - } - - } - - /** - * WP action http_api_curl - * - * @param Resource $handle A curl handle returned by curl_init() - * - * @return the handle (having potentially had some options set upon it) - */ - public function http_api_curl($handle) { - if (defined('UPDRAFTPLUS_IPV4_ONLY') && UPDRAFTPLUS_IPV4_ONLY) { - curl_setopt($handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - } - return $handle; - } - - /** - * Used as a central location (to avoid repetition) to register or de-register hooks into the WP HTTP API - * - * @param Boolean $register - true to register, false to de-register - */ - public function register_wp_http_option_hooks($register = true) { - if ($register) { - add_filter('http_request_args', array($this, 'modify_http_options')); - add_action('http_api_curl', array($this, 'http_api_curl')); - } else { - remove_filter('http_request_args', array($this, 'modify_http_options')); - remove_action('http_api_curl', array($this, 'http_api_curl')); - } - } - - /** - * Used as a WordPress options filter (http_request_args) - * - * @param Array $opts - existing options - * - * @return Array - modified options - */ - public function modify_http_options($opts) { - - if (!is_array($opts)) return $opts; - - if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) $opts['sslcertificates'] = UPDRAFTPLUS_DIR.'/includes/cacert.pem'; - - $opts['sslverify'] = UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify') ? false : true; - - return $opts; - - } - - /** - * Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?action=updraftmethod-googledrive-auth&page=updraftplus - * Nov 2013: Google's new cloud console, for reasons as yet unknown, only allows you to enter a redirect_uri with a single URL parameter... thus, we put page second, and re-add it if necessary. Apr 2014: Bitcasa already do this, so perhaps it is part of the OAuth2 standard or best practice somewhere. - * Also handle action=downloadlog - * - * @return Void - may not necessarily return at all, depending on the action - */ - public function handle_url_actions() { - - // First, basic security check: must be an admin page, with ability to manage options, with the right parameters - // Also, only on GET because WordPress on the options page repeats parameters sometimes when POST-ing via the _wp_referer field - if (isset($_SERVER['REQUEST_METHOD']) && ('GET' == $_SERVER['REQUEST_METHOD'] || 'POST' == $_SERVER['REQUEST_METHOD']) && isset($_GET['action'])) { - if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php') && UpdraftPlus_Options::user_can_manage()) { - $_GET['page'] = 'updraftplus'; - $_REQUEST['page'] = 'updraftplus'; - $method = $matches[1]; - $call_method = "action_".$matches[2]; - $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($method)); - - $instance_id = isset($_GET['updraftplus_instance']) ? $_GET['updraftplus_instance'] : ''; - - if ("POST" == $_SERVER['REQUEST_METHOD'] && isset($_POST['state'])) { - $state = urldecode($_POST['state']); - } elseif (isset($_GET['state'])) { - $state = $_GET['state']; - } - - // If we don't have an instance_id but the state is set then we are coming back to finish the auth and should extract the instance_id from the state - if ('' == $instance_id && isset($state) && false !== strpos($state, ':')) { - $parts = explode(':', $state); - $instance_id = $parts[1]; - } - - if (isset($storage_objects_and_ids[$method]['instance_settings'][$instance_id])) { - $opts = $storage_objects_and_ids[$method]['instance_settings'][$instance_id]; - $backup_obj = $storage_objects_and_ids[$method]['object']; - $backup_obj->set_options($opts, false, $instance_id); - } else { - include_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php'); - $call_class = "UpdraftPlus_BackupModule_".$method; - $backup_obj = new $call_class; - } - - $this->register_wp_http_option_hooks(); - - try { - if (method_exists($backup_obj, $call_method)) { - call_user_func(array($backup_obj, $call_method)); - } - } catch (Exception $e) { - $this->log(sprintf(__("%s error: %s", 'updraftplus'), $method, $e->getMessage().' ('.$e->getCode().')', 'error')); - } - $this->register_wp_http_option_hooks(false); - } elseif (isset($_GET['page']) && 'updraftplus' == $_GET['page'] && 'downloadlog' == $_GET['action'] && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/", $_GET['updraftplus_backup_nonce']) && UpdraftPlus_Options::user_can_manage()) { - // No WordPress nonce is needed here or for the next, since the backup is already nonce-based - $updraft_dir = $this->backups_dir_location(); - $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt'; - if (is_readable($log_file)) { - header('Content-type: text/plain'); - if (!empty($_GET['force_download'])) header('Content-Disposition: attachment; filename="'.basename($log_file).'"'); - readfile($log_file); - exit; - } else { - add_action('all_admin_notices', array($this, 'show_admin_warning_unreadablelog')); - } - } elseif (isset($_GET['page']) && 'updraftplus' == $_GET['page'] && 'downloadfile' == $_GET['action'] && isset($_GET['updraftplus_file']) && preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?+\.(gz\.crypt)$/i', $_GET['updraftplus_file']) && UpdraftPlus_Options::user_can_manage()) { - // Though this (venerable) code uses the action 'downloadfile', in fact, it's not that general: it's just for downloading a decrypted copy of encrypted databases, and nothing else - $updraft_dir = $this->backups_dir_location(); - $file = $_GET['updraftplus_file']; - $spool_file = $updraft_dir.'/'.basename($file); - if (is_readable($spool_file)) { - $dkey = isset($_GET['decrypt_key']) ? stripslashes($_GET['decrypt_key']) : ''; - $this->spool_file($spool_file, $dkey); - exit; - } else { - add_action('all_admin_notices', array($this, 'show_admin_warning_unreadablefile')); - } - } elseif ('updraftplus_spool_file' == $_GET['action'] && !empty($_GET['what']) && !empty($_GET['backup_timestamp']) && is_numeric($_GET['backup_timestamp']) && UpdraftPlus_Options::user_can_manage()) { - // At some point, it may be worth merging this with the previous section - $updraft_dir = $this->backups_dir_location(); - - $findex = isset($_GET['findex']) ? (int) $_GET['findex'] : 0; - $backup_timestamp = $_GET['backup_timestamp']; - $what = $_GET['what']; - - $backup_set = UpdraftPlus_Backup_History::get_history($backup_timestamp); - - $filename = null; - if (!empty($backup_set)) { - if ('db' != substr($what, 0, 2)) { - $backupable_entities = $this->get_backupable_file_entities(); - if (!isset($backupable_entities[$what])) $filename = false; - } - if (false !== $filename && isset($backup_set[$what])) { - if (is_string($backup_set[$what]) && 0 == $findex) { - $filename = $backup_set[$what]; - } elseif (isset($backup_set[$what][$findex])) { - $filename = $backup_set[$what][$findex]; - } - } - } - if (empty($filename) || !is_readable($updraft_dir.'/'.basename($filename))) { - echo json_encode(array('result' => __('UpdraftPlus notice:', 'updraftplus').' '.__('The given file was not found, or could not be read.', 'updraftplus'))); - exit; - } - - $dkey = isset($_GET['decrypt_key']) ? stripslashes($_GET['decrypt_key']) : ""; - - $this->spool_file($updraft_dir.'/'.basename($filename), $dkey); - exit; - - } - } - } - - /** - * This function will check if this is a multisite and if our maintenance mode file is present if so return a service unavailable - * - * @return void - */ - public function updraftplus_single_site_maintenance_init() { - - if (!is_multisite()) return; - - $wp_upload_dir = wp_upload_dir(); - $subsite_dir = $wp_upload_dir['basedir'].'/'; - - if (!file_exists($subsite_dir.'.maintenance')) return; - - $timestamp = file_get_contents($subsite_dir.'.maintenance'); - $time = time(); - - if ($time - $timestamp > 3600) { - unlink($subsite_dir.'.maintenance'); - return; - } - - wp_die('

    '.__('Under Maintenance', 'updraftplus') .'

    '.__('Briefly unavailable for scheduled maintenance. Check back in a minute.', 'updraftplus').'

    '); - } - - /** - * Get the installation's base table prefix, optionally allowing the result to be filtered - * - * @param Boolean $allow_override - allow the result to be filtered - * - * @return String - */ - public function get_table_prefix($allow_override = false) { - global $wpdb; - if (is_multisite() && !defined('MULTISITE')) { - // In this case (which should only be possible on installs upgraded from pre WP 3.0 WPMU), $wpdb->get_blog_prefix() cannot be made to return the right thing. $wpdb->base_prefix is not explicitly marked as public, so we prefer to use get_blog_prefix if we can, for future compatibility. - $prefix = $wpdb->base_prefix; - } else { - $prefix = $wpdb->get_blog_prefix(0); - } - return $allow_override ? apply_filters('updraftplus_get_table_prefix', $prefix) : $prefix; - } - - public function siteid() { - $sid = get_site_option('updraftplus-addons_siteid'); - if (!is_string($sid) || empty($sid)) { - $sid = md5(rand().microtime(true).home_url()); - update_site_option('updraftplus-addons_siteid', $sid); - } - return $sid; - } - - public function show_admin_warning_unreadablelog() { - global $updraftplus_admin; - $updraftplus_admin->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.__('The log file could not be read.', 'updraftplus')); - } - - public function show_admin_warning_nolog() { - global $updraftplus_admin; - $updraftplus_admin->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.__('No log files were found.', 'updraftplus')); - } - - public function show_admin_warning_unreadablefile() { - global $updraftplus_admin; - $updraftplus_admin->show_admin_warning(''.__('UpdraftPlus notice:', 'updraftplus').' '.__('The given file was not found, or could not be read.', 'updraftplus')); - } - - /** - * Runs upon the WP action plugins_loaded - */ - public function plugins_loaded() { - - // Tell WordPress where to find the translations - load_plugin_textdomain('updraftplus', false, basename(dirname(__FILE__)).'/languages/'); - - // The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us - if ((defined('DOING_CRON') && DOING_CRON) || (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['subaction']) && 'backupnow' == $_REQUEST['subaction']) || (isset($_GET['page']) && 'updraftplus' == $_GET['page'] )) { - remove_action('init', 'ganalyticator_stats_init'); - // Appointments+ does the same; but provides a cleaner way to disable it - @define('APP_GCAL_DISABLE', true);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - add_filter('updraftplus_remotecontrol_command_classes', array($this, 'updraftplus_remotecontrol_command_classes')); - add_action('updraftcentral_command_class_wanted', array($this, 'updraftcentral_command_class_wanted')); - add_action('updraftcentral_listener_pre_udrpc_action', array($this, 'updraftcentral_listener_pre_udrpc_action')); - add_action('updraftcentral_listener_post_udrpc_action', array($this, 'updraftcentral_listener_post_udrpc_action')); - - if (file_exists(UPDRAFTPLUS_DIR.'/central/bootstrap.php')) { - include_once(UPDRAFTPLUS_DIR.'/central/bootstrap.php'); - } - - $load_classes = array(); - - if (defined('UPDRAFTPLUS_THIS_IS_CLONE')) { - $load_classes['UpdraftPlus_Temporary_Clone_Dash_Notice'] = 'includes/updraftclone/temporary-clone-dash-notice.php'; - $load_classes['UpdraftPlus_Temporary_Clone_User_Notice'] = 'includes/updraftclone/temporary-clone-user-notice.php'; - $load_classes['UpdraftPlus_Temporary_Clone_Restore'] = 'includes/updraftclone/temporary-clone-restore.php'; - $load_classes['UpdraftPlus_Temporary_Clone_Auto_Login'] = 'includes/updraftclone/temporary-clone-auto-login.php'; - $load_classes['UpdraftPlus_Temporary_Clone_Status'] = 'includes/updraftclone/temporary-clone-status.php'; - } - - foreach ($load_classes as $class => $relative_path) { - if (!class_exists($class)) include_once(UPDRAFTPLUS_DIR.'/'.$relative_path); - } - - } - - /** - * Get the character set for the current database connection - * - * @uses WPDB::determine_charset() - exists on WP 4.6+ - * - * @param Object|Null $wpdb - WPDB object; if none passed, then use the global one - * - * @return String - */ - public function get_connection_charset($wpdb = null) { - if (null === $wpdb) { - global $wpdb; - } - - $charset = (defined('DB_CHARSET') && DB_CHARSET) ? DB_CHARSET : 'utf8mb4'; - - if (method_exists($wpdb, 'determine_charset')) { - $charset_collate = $wpdb->determine_charset($charset, ''); - if (!empty($charset_collate['charset'])) $charset = $charset_collate['charset']; - } - - return $charset; - } - - /** - * Runs upon the action updraftcentral_listener_pre_udrpc_action - */ - public function updraftcentral_listener_pre_udrpc_action() { - $this->register_wp_http_option_hooks(); - } - - /** - * Runs upon the action updraftcentral_listener_post_udrpc_action - */ - public function updraftcentral_listener_post_udrpc_action() { - $this->register_wp_http_option_hooks(false); - } - - /** - * Register our class. WP filter updraftplus_remotecontrol_command_classes. - * - * @param Array $command_classes sends across the command class - * - * @return Array - filtered value - */ - public function updraftplus_remotecontrol_command_classes($command_classes) { - if (is_array($command_classes)) $command_classes['updraftplus'] = 'UpdraftCentral_UpdraftPlus_Commands'; - if (is_array($command_classes)) $command_classes['updraftvault'] = 'UpdraftCentral_UpdraftVault_Commands'; - return $command_classes; - } - - /** - * Load the class when required - * - * @param string $command_php_class Sends across the php class type - */ - public function updraftcentral_command_class_wanted($command_php_class) { - if ('UpdraftCentral_UpdraftPlus_Commands' == $command_php_class) { - include_once(UPDRAFTPLUS_DIR.'/includes/class-updraftcentral-updraftplus-commands.php'); - } elseif ('UpdraftCentral_UpdraftVault_Commands' == $command_php_class) { - include_once(UPDRAFTPLUS_DIR.'/includes/updraftvault.php'); - } - } - - /** - * This function allows you to manually set the nonce and timestamp for the current backup job. If none are provided then it will create new ones. - * - * @param boolean|string $nonce - the nonce you want to set - * @param boolean|string $timestamp - the timestamp you want to set - * - * @return string - returns the backup nonce that has been set - */ - public function backup_time_nonce($nonce = false, $timestamp = false) { - $this->job_time_ms = microtime(true); - if (false === $timestamp) $timestamp = time(); - if (false === $nonce) $nonce = substr(md5(time().rand()), 20); - $this->backup_time = $timestamp; - $this->file_nonce = apply_filters('updraftplus_incremental_backup_file_nonce', $nonce); - $this->nonce = $nonce; - return $nonce; - } - - /** - * Get the WordPress version - * - * @return String - the version - */ - public function get_wordpress_version() { - static $got_wp_version = false; - if (!$got_wp_version) { - global $wp_version; - @include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $got_wp_version = $wp_version; - } - return $got_wp_version; - } - - /** - * Get the UpdraftPlus version and convert it to the correct format to be used in filenames - * - * @return String - the file version number - */ - public function get_updraftplus_file_version() { - - if ($this->use_unminified_scripts()) return ''; - - $version_parts = explode('.', $this->version); - $version_parts = array_slice($version_parts, 0, 3); - $version = implode('.', $version_parts); - - return '-'.str_replace('.', '-', $version).'.min'; - } - - /** - * Opens the log file, writes a standardised header, and stores the resulting name and handle in the class variables logfile_name/logfile_handle/opened_log_time (and possibly backup_is_already_complete) - * - * @param string $nonce - Used in the log file name to distinguish it from other log files. Should be the job nonce. - * @returns void - */ - public function logfile_open($nonce) { - - $this->logfile_name = $this->get_logfile_name($nonce); - - $this->backup_is_already_complete = $this->found_backup_complete_in_logfile($nonce, false); - - $this->logfile_handle = fopen($this->logfile_name, 'a'); - - $this->opened_log_time = microtime(true); - - $this->write_log_header(array($this, 'log')); - - } - - /** - * Opens the log file, and finds if backup_is_already_complete - * - * @param string $nonce - Used in the log file name to distinguish it from other log files. Should be the job nonce. - * @param boolean $use_existing_result - Whether to use any existing result or not - * - * @return boolean - returns true if the backup is complete otherwise returns false - */ - public function found_backup_complete_in_logfile($nonce, $use_existing_result = true) { - - static $checked_files = array(); - - if (isset($checked_files[$nonce]) && $use_existing_result) return $checked_files[$nonce]; - $logfile_name = $this->get_logfile_name($nonce); - - if (!file_exists($logfile_name)) return false; - - $backup_is_already_complete = false; - - $seek_to = max((filesize($logfile_name) - 340), 1); - $handle = fopen($logfile_name, 'r'); - if (is_resource($handle)) { - // Returns 0 on success - if (0 === @fseek($handle, $seek_to)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $bytes_back = filesize($logfile_name) - $seek_to; - // Return to the end of the file - $read_recent = fread($handle, $bytes_back); - // Move to end of file - ought to be redundant - if (false !== strpos($read_recent, ') The backup apparently succeeded') && false !== strpos($read_recent, 'and is now complete')) { - $backup_is_already_complete = true; - } - } - fclose($handle); - } - - $checked_files[$nonce] = $backup_is_already_complete; - - return $backup_is_already_complete; - } - - /** - * Returns the logfile name for a given job - * - * @param string $nonce - Used in the log file name to distinguish it from other log files. Should be the job nonce. - * @return string - */ - public function get_logfile_name($nonce) { - $updraft_dir = $this->backups_dir_location(); - return $updraft_dir."/log.$nonce.txt"; - } - - /** - * Writes a standardised header to the log file, using the specified logging function, which needs to be compatible with (or to be) UpdraftPlus::log() - * - * @param callable $logging_function - */ - public function write_log_header($logging_function) { - - global $wpdb; - - $updraft_dir = $this->backups_dir_location(); - - call_user_func($logging_function, 'Opened log file at time: '.date('r').' on '.network_site_url()); - - $wp_version = $this->get_wordpress_version(); - $mysql_version = $wpdb->get_var('SELECT VERSION()'); - if ('' == $mysql_version) $mysql_version = $wpdb->db_version(); - $safe_mode = $this->detect_safe_mode(); - - $memory_limit = ini_get('memory_limit'); - $memory_usage = round(@memory_get_usage(false)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - // Attempt to raise limit to avoid false positives - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $max_execution_time = (int) @ini_get("max_execution_time");// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $logline = "UpdraftPlus WordPress backup plugin (https://updraftplus.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".PHP_SAPI.", ".(function_exists('php_uname') ? @php_uname() : PHP_OS).") MySQL: $mysql_version WPLANG: ".get_locale()." Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M) multisite: ".(is_multisite() ? (is_subdomain_install() ? 'Y (sub-domain)' : 'Y (sub-folder)') : 'N')." openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N')." mcrypt: ".(function_exists('mcrypt_encrypt') ? 'Y' : 'N')." LANG: ".getenv('LANG')." ZipArchive::addFile: ";// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - // method_exists causes some faulty PHP installations to segfault, leading to support requests - if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) { - $logline .= 'Y'; - } else { - $logline .= (class_exists('ZipArchive') && method_exists('ZipArchive', 'addFile')) ? "Y" : "N"; - } - - if (0 === $this->current_resumption) { - $memlim = $this->memory_check_current(); - if ($memlim<65 && $memlim>0) { - $this->log(sprintf(__('The amount of memory (RAM) allowed for PHP is very low (%s Mb) - you should increase it to avoid failures due to insufficient memory (consult your web hosting company for more help)', 'updraftplus'), round($memlim, 1)), 'warning', 'lowram'); - } - if ($max_execution_time>0 && $max_execution_time<20) { - call_user_func($logging_function, sprintf(__('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid backup failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', 'updraftplus'), $max_execution_time, 90), 'warning', 'lowmaxexecutiontime'); - } - - } - - call_user_func($logging_function, $logline); - - $hosting_bytes_free = $this->get_hosting_disk_quota_free(); - if (is_array($hosting_bytes_free)) { - $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1); - $quota_free = ' / '.sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %"); - if ($hosting_bytes_free[3] < 1048576*50) { - $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1); - call_user_func($logging_function, sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'updraftplus'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb); - } - } else { - $quota_free = ''; - } - - $disk_free_space = function_exists('disk_free_space') ? @disk_free_space($updraft_dir) : false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - // == rather than === here is deliberate; support experience shows that a result of (int)0 is not reliable. i.e. 0 can be returned when the real result should be false. - if (false == $disk_free_space) { - call_user_func($logging_function, "Free space on disk containing Updraft's temporary directory: Unknown".$quota_free); - } else { - call_user_func($logging_function, "Free space on disk containing Updraft's temporary directory: ".round($disk_free_space/1048576, 1)." MB".$quota_free); - $disk_free_mb = round($disk_free_space/1048576, 1); - if ($disk_free_space < 50*1048576) call_user_func($logging_function, sprintf(__('Your free disk space is very low - only %s Mb remain', 'updraftplus'), round($disk_free_space/1048576, 1)), 'warning', 'lowdiskspace'.$disk_free_mb); - } - - } - - /** - * This function will read the next chunk from the log file and return it's contents and last read byte position - * - * @param string $nonce - the UpdraftPlus file nonce - * - * @return array - an empty array if there is no log file or an array with log file contents and last read byte position - */ - public function get_last_log_chunk($nonce) { - - $this->logfile_name = $this->get_logfile_name($nonce); - - if (file_exists($this->logfile_name)) { - $contents = ''; - $seek_to = max(0, $this->jobdata_get('clone_first_byte', 0)); - $first_byte = $seek_to; - $handle = fopen($this->logfile_name, 'r'); - if (is_resource($handle)) { - // Returns 0 on success - if (0 === @fseek($handle, $seek_to)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - while (strlen($contents) < 1048576 && ($buffer = fgets($handle, 262144)) !== false) { - $contents .= $buffer; - $seek_to += 262144; - } - $this->jobdata_set('clone_first_byte', $seek_to); - } - fclose($handle); - } - return array('log_contents' => $contents, 'first_byte' => $first_byte); - } - return array(); - } - - /** - * - * Verifies that the indicated amount of memory is available - * - * @param Integer $how_many_bytes_needed - how many bytes need to be available - * - * @return Boolean - whether the needed number of bytes is available - */ - public function verify_free_memory($how_many_bytes_needed) { - // This returns in MB - $memory_limit = $this->memory_check_current(); - if (!is_numeric($memory_limit)) return false; - $memory_limit = $memory_limit * 1048576; - $memory_usage = round(@memory_get_usage(false)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if ($memory_limit - $memory_usage > $how_many_bytes_needed && $memory_limit - $memory_usage2 > $how_many_bytes_needed) return true; - return false; - } - - /** - * Logs the given line, adding (relative) time stamp and newline - * Note these subtleties of log handling: - * - Messages at level 'error' are not logged to file - it is assumed that a separate call to log() at another level will take place. This is because at level 'error', messages are translated; whereas the log file is for developers who may not know the translated language. Messages at level 'error' are for the user. - * - Messages at level 'error' do not persist through the job (they are only saved with save_backup_to_history(), and never restored from there - so only the final save_backup_to_history() errors - * persist); we presume that either a) they will be cleared on the next attempt, or b) they will occur again on the final attempt (at which point they will go to the user). But... - * - messages at level 'warning' persist. These are conditions that are unlikely to be cleared, not-fatal, but the user should be informed about. The $uniq_id field (which should not be numeric) can then be used for warnings that should only be logged once - * $skip_dblog = true is suitable when there's a risk of excessive logging, and the information is not important for the user to see in the browser on the settings page - * The uniq_id field is also used with PHP event detection - it is set then to 'php_event' - which is useful for anything hooking the action to detect - * - * @param string $line the log line - * @param string $level the log level: notice, warning, error. If suffixed with a hyphen and a destination, then the default destination is changed too. - * @param boolean $uniq_id each of these will only be logged once - * @param boolean $skip_dblog if true, then do not write to the database - * @return null - */ - public function log($line, $level = 'notice', $uniq_id = false, $skip_dblog = false) { - - $destination = 'default'; - if (preg_match('/^([a-z]+)-([a-z]+)$/', $level, $matches)) { - $level = $matches[1]; - $destination = $matches[2]; - } - - if ('error' == $level || 'warning' == $level) { - if ('error' == $level && 0 == $this->error_count()) $this->log('An error condition has occurred for the first time during this job'); - if ($uniq_id) { - $this->errors[$uniq_id] = array('level' => $level, 'message' => $line); - } else { - $this->errors[] = array('level' => $level, 'message' => $line); - } - // Errors are logged separately - if ('error' == $level) return; - // It's a warning - $warnings = $this->jobdata_get('warnings'); - if (!is_array($warnings)) $warnings = array(); - if ($uniq_id) { - $warnings[$uniq_id] = $line; - } else { - $warnings[] = $line; - } - $this->jobdata_set('warnings', $warnings); - } - - if (false === ($line = apply_filters('updraftplus_logline', $line, $this->nonce, $level, $uniq_id, $destination))) return; - - if ($this->logfile_handle) { - // Record log file times relative to the backup start, if possible - $rtime = (!empty($this->job_time_ms)) ? microtime(true)-$this->job_time_ms : microtime(true)-$this->opened_log_time; - fwrite($this->logfile_handle, sprintf("%08.03f", round($rtime, 3))." (".$this->current_resumption.") ".(('notice' != $level) ? '['.ucfirst($level).'] ' : '').$line."\n"); - } - - switch ($this->jobdata_get('job_type')) { - case 'download': - // Download messages are keyed on the job (since they could be running several), and type - // The values of the POST array were checked before - $findex = empty($_POST['findex']) ? 0 : $_POST['findex']; - - if (!empty($_POST['timestamp']) && !empty($_POST['type'])) $this->jobdata_set('dlmessage_'.$_POST['timestamp'].'_'.$_POST['type'].'_'.$findex, $line); - break; - - case 'restore': - // if ('debug' != $level) echo $line."\n"; - break; - - default: - if (!$skip_dblog && 'debug' != $level) UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date_i18n('M d H:i:s').")", false); - break; - } - - if (defined('UPDRAFTPLUS_CONSOLELOG') && UPDRAFTPLUS_CONSOLELOG) echo $line."\n"; - if (defined('UPDRAFTPLUS_BROWSERLOG') && UPDRAFTPLUS_BROWSERLOG) echo htmlentities($line)."
    \n"; - } - - /** - * Remove any logged warnings with the specified identifier. (The use case for this is that you can warn of something that may be about to happen (with a probably crash if it does), and then remove the warning if it did not happen). - * - * @see self::log() - * - * @param String $uniq_id - the identifier, previously passed to self::log() - */ - public function log_remove_warning($uniq_id) { - $warnings = $this->jobdata_get('warnings'); - if (!is_array($warnings)) $warnings = array(); - unset($warnings[$uniq_id]); - $this->jobdata_set('warnings', $warnings); - unset($this->errors[$uniq_id]); - } - - /** - * For efficiency, you can also feed false or a string into this function - * - * @param Boolean|String|WP_Error $err - the errors - * @param Boolean $echo - whether to echo() the error(s) - * @param Boolean $logerror - whether to pass errors to UpdraftPlus::log() - * @return Boolean - returns false for convenience - */ - public function log_wp_error($err, $echo = false, $logerror = false) { - if (false === $err) return false; - if (is_string($err)) { - $this->log("Error message: $err"); - if ($echo) $this->log(sprintf(__('Error: %s', 'updraftplus'), $err), 'notice-warning'); - if ($logerror) $this->log($err, 'error'); - return false; - } - foreach ($err->get_error_messages() as $msg) { - $this->log("Error message: $msg"); - if ($echo) $this->log(sprintf(__('Error: %s', 'updraftplus'), $msg), 'notice-warning'); - if ($logerror) $this->log($msg, 'error'); - } - $codes = $err->get_error_codes(); - if (is_array($codes)) { - foreach ($codes as $code) { - $data = $err->get_error_data($code); - if (!empty($data)) { - $ll = (is_string($data)) ? $data : serialize($data); - $this->log("Error data (".$code."): ".$ll); - } - } - } - // Returns false so that callers can return with false more efficiently if they wish - return false; - } - - /** - * This function will construct the restore information log line using the passed in parameters and then log the line using $this->log(); - * - * @param array $restore_information - an array of restore information - * - * @return void - */ - public function log_restore_update($restore_information) { - $this->log("RINFO:".json_encode($restore_information), 'notice-progress'); - } - - /** - * Outputs data to the browser. - * Will also fill the buffer on nginx systems after a specified amount of time. - * - * @param string $line The text to output - * @return void - */ - public function output_to_browser($line) { - echo $line; - if (false === stripos($_SERVER['SERVER_SOFTWARE'], 'nginx')) return; - static $strcount = 0; - static $time = 0; - $buffer_size = 65536; // The default NGINX config uses a buffer size of 32 or 64k, depending on the system. So we use 64K. - if (0 == $time) $time = time(); - $strcount += strlen($line); - if ((time() - $time) >= 8) { - // if the string count is > the buffer size, we reset, as it's likely the string was already sent. - if ($strcount > $buffer_size) { - $time = time(); - $strcount = $strcount - $buffer_size; - return; - } - echo str_repeat(" ", ($buffer_size-$strcount)); - // reset values - $time = time(); - $strcount = 0; - } - } - /** - * Get the maximum packet size on the WPDB MySQL connection, in bytes, after (optionally) attempting to raise it to 32MB if it appeared to be lower. - * A default value equal to 1MB is returned if the true value could not be found - it has been found reasonable to assume that at least this is available. - * - * @param Boolean $first_raise - * @param Boolean $log_it - * - * @return Integer - */ - public function max_packet_size($first_raise = true, $log_it = true) { - global $wpdb; - $mp = (int) $wpdb->get_var("SELECT @@session.max_allowed_packet"); - // Default to 1MB - $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576; - // 32MB - if ($first_raise && $mp < 33554432) { - $save = $wpdb->show_errors(false); - $req = @$wpdb->query("SET GLOBAL max_allowed_packet=33554432");// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $wpdb->show_errors($save); - if (!$req) $this->log("Tried to raise max_allowed_packet from ".round($mp/1048576, 1)." MB to 32 MB, but failed (".$wpdb->last_error.", ".serialize($req).")"); - $mp = (int) $wpdb->get_var("SELECT @@session.max_allowed_packet"); - // Default to 1MB - $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576; - } - if ($log_it) $this->log("Max packet size: ".round($mp/1048576, 1)." MB"); - return $mp; - } - - /** - * Q. Why is this abstracted into a separate function? A. To allow poedit and other parsers to pick up the need to translate strings passed to it (and not pick up all of those passed to log()). - * 1st argument = the line to be logged (obligatory) - * Further arguments = parameters for sprintf() - * - * @return null - */ - public function log_e() { - $args = func_get_args(); - // Get first argument - $pre_line = array_shift($args); - // Log it whilst still in English - if (is_wp_error($pre_line)) { - $this->log_wp_error($pre_line); - } else { - // Now run (v)sprintf on it, using any remaining arguments. vsprintf = sprintf but takes an array instead of individual arguments - $this->log(vsprintf($pre_line, $args)); - // This is slightly hackish, in that we have no way to use a different level or destination. In that case, the caller should instead call log() twice with different parameters, instead of using this convenience function. - $this->log(vsprintf($pre_line, $args), 'notice-restore'); - } - } - - /** - * This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile - * - * @param Number $percent - the amount of the file uploaded - * @param String $extra - anything extra to include in the log message - * @param Boolean $file_path - the full path to the file being uploaded - * @param Boolean $log_it - whether to pass the message to UpdraftPlus::log() - * @return Void - */ - public function record_uploaded_chunk($percent, $extra = '', $file_path = false, $log_it = true) { - - // Touch the original file, which helps prevent overlapping runs - if ($file_path) touch($file_path); - - // What this means in effect is that at least one of the files touched during the run must reach this percentage (so lapping round from 100 is OK) - if ($percent > 0.7 * ($this->current_resumption - max($this->jobdata_get('uploaded_lastreset'), 9))) UpdraftPlus_Job_Scheduler::something_useful_happened(); - - // Log it - global $updraftplus_backup; - $log = (!empty($updraftplus_backup->current_service)) ? ucfirst($updraftplus_backup->current_service)." chunked upload: $percent % uploaded" : ''; - if ($log && $log_it) $this->log($log.(($extra) ? " ($extra)" : '')); - // If we are on an 'overtime' resumption run, and we are still meaningfully uploading, then schedule a new resumption - // Our definition of meaningful is that we must maintain an overall average of at least 0.7% per run, after allowing 9 runs for everything else to get going - // i.e. Max 100/.7 + 9 = 150 runs = 760 minutes = 12 hrs 40, if spaced at 5 minute intervals. However, our algorithm now decreases the intervals if it can, so this should not really come into play - // If they get 2 minutes on each run, and the file is 1GB, then that equals 10.2MB/120s = minimum 59KB/s upload speed required - - $upload_status = $this->jobdata_get('uploading_substatus'); - if (is_array($upload_status)) { - $upload_status['p'] = $percent/100; - $this->jobdata_set('uploading_substatus', $upload_status); - } - - } - - /** - * Method for helping remote storage methods to upload files in chunks without needing to duplicate all the overhead - * - * @param object $caller the object to call back to do the actual network API calls; needs to have a chunked_upload() method. - * @param string $file the basename of the file - * @param string $cloudpath this is passed back to the callback function; within this function, it is used only for logging - * @param string $logname the prefix used on log lines. Also passed back to the callback function. - * @param integer $chunk_size the size, in bytes, of each upload chunk - * @param integer $uploaded_size how many bytes have already been uploaded. This is passed back to the callback function; within this method, it is only used for logging. - * @param boolean $singletons when the file, given the chunk size, would only have one chunk, should that be uploaded (true), or instead should 1 be returned (false) ? - * @return boolean - */ - public function chunked_upload($caller, $file, $cloudpath, $logname, $chunk_size, $uploaded_size, $singletons = false) { - - $fullpath = $this->backups_dir_location().'/'.$file; - $orig_file_size = filesize($fullpath); - - if ($uploaded_size >= $orig_file_size && !method_exists($caller, 'chunked_upload_finish')) return true; - - $chunks = floor($orig_file_size / $chunk_size); - // There will be a remnant unless the file size was exactly on a chunk boundary - if ($orig_file_size % $chunk_size > 0) $chunks++; - - $this->log("$logname upload: $file (chunks: $chunks, of size: $chunk_size) -> $cloudpath ($uploaded_size)"); - - if (0 == $chunks) { - return 1; - } elseif ($chunks < 2 && !$singletons) { - return 1; - } - - // We have multiple chunks - if ($uploaded_size < $orig_file_size) { - - if (false == ($fp = @fopen($fullpath, 'rb'))) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $this->log("$logname: failed to open file: $fullpath"); - $this->log("$file: ".sprintf(__('%s Error: Failed to open local file', 'updraftplus'), $logname), 'error'); - return false; - } - - $upload_start = 0; - $upload_end = -1; - $chunk_index = 1; - // The file size minus one equals the byte offset of the final byte - $upload_end = min($chunk_size - 1, $orig_file_size - 1); - $errors_on_this_chunk = 0; - - while ($upload_start < $orig_file_size) { - - // Don't forget the +1; otherwise the last byte is omitted - $upload_size = $upload_end - $upload_start + 1; - - fseek($fp, $upload_start); - - /* - * Valid return values for $uploaded are many, as the possibilities have grown over time. - * This could be cleaned up; but, it works, and it's not hugely complex. - * - * WP_Error : an error occured. The only permissible codes are: reduce_chunk_size (only on the first chunk), try_again - * (bool)true : What was requested was done - * (int)1 : What was requested was done, but do not log anything - * (bool)false : There was an error - * (Object) : Properties: - * (bool)log: (bool) - if absent, defaults to true - * (int)new_chunk_size: advisory amount for the chunk size for future chunks - * NOT IMPLEMENTED: (int)bytes_uploaded: Actual number of bytes uploaded (needs to be positive - o/w, should return an error instead) - * N.B. Consumers should consult $fp and $upload_start to get data; they should not re-calculate from $chunk_index, which is not an indicator of file position. - */ - $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size); - - // Try again? (Just once - added in 1.12.6 (can make more sophisticated if there is a need)) - if (is_wp_error($uploaded) && 'try_again' == $uploaded->get_error_code()) { - // Arbitrary wait - sleep(3); - $this->log("Re-trying after wait (to allow apparent inconsistency to clear)"); - $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size); - } - - // This is the only other supported case of a WP_Error - otherwise, a boolean must be returned - // Note that this is only allowed on the first chunk. The caller is responsible to remember its chunk size if it uses this facility. - if (1 == $chunk_index && is_wp_error($uploaded) && 'reduce_chunk_size' == $uploaded->get_error_code() && false != ($new_chunk_size = $uploaded->get_error_data()) && is_numeric($new_chunk_size)) { - $this->log("Re-trying with new chunk size: ".$new_chunk_size); - return $this->chunked_upload($caller, $file, $cloudpath, $logname, $new_chunk_size, $uploaded_size, $singletons); - } - - $uploaded_amount = $chunk_size; - - /* - // Not using this approach for now. Instead, going to allow the consumers to increase the next chunk size - if (is_object($uploaded) && isset($uploaded->bytes_uploaded)) { - if (!$uploaded->bytes_uploaded) { - $uploaded = false; - } else { - $uploaded_amount = $uploaded->bytes_uploaded; - $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1; - } - } - */ - if (is_object($uploaded) && isset($uploaded->new_chunk_size)) { - if ($uploaded->new_chunk_size >= 1048576) $new_chunk_size = $uploaded->new_chunk_size; - $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1; - } - - // The joys of WP/PHP: is_wp_error() is not false-y. - if ($uploaded && !is_wp_error($uploaded)) { - $perc = round(100*($upload_end + 1)/max($orig_file_size, 1), 1); - // Consumers use a return value of (int)1 (rather than (bool)true) to suppress logging - $log_it = (1 === $uploaded) ? false : true; - $this->record_uploaded_chunk($perc, $chunk_index, $fullpath, $log_it); - - // $uploaded_bytes = $upload_end + 1; - - // If there was an error, then we re-try the same chunk; we don't move on to the next one. Otherwise, we would need more code to handle potential 'intermediate' failed chunks (in case PHP dies before this method eventually returns false, and thus the intermediate chunk failure never gets detected) - $chunk_index++; - $errors_on_this_chunk = 0; - $upload_start = $upload_end + 1; - $upload_end += isset($new_chunk_size) ? $uploaded_amount + $new_chunk_size - $chunk_size : $uploaded_amount; - $upload_end = min($upload_end, $orig_file_size - 1); - - } else { - - $errors_on_this_chunk++; - - // Either $uploaded is false-y, or is a WP_Error - if (is_wp_error($uploaded)) { - $this->log("$logname: Chunk upload ($chunk_index) failed (".$uploaded->get_error_code().'): '.$uploaded->get_error_message()); - } else { - $this->log("$logname: Chunk upload ($chunk_index) failed"); - } - - if ($errors_on_this_chunk >= 3) { - @fclose($fp);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - return false; - } - } - - } - - @fclose($fp);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - } - - // All chunks are uploaded - now combine the chunks - $ret = true; - - // The action calls here exist to aid debugging - if (method_exists($caller, 'chunked_upload_finish')) { - do_action('updraftplus_pre_chunked_upload_finish', $file, $caller); - $ret = $caller->chunked_upload_finish($file); - if (!$ret) { - $this->log("$logname - failed to re-assemble chunks"); - $this->log(sprintf(__('%s error - failed to re-assemble chunks', 'updraftplus'), $logname), 'error'); - } - do_action('updraftplus_post_chunked_upload_finish', $file, $caller, $ret); - } - - if ($ret) { - // We allow chunked_upload_finish to return (int)1 to indicate that it took care of any logging. - if (true === $ret) $this->log("$logname upload: success"); - $ret = true; - // UpdraftPlus_RemoteStorage_Addons_Base calls this itself - if (!is_a($caller, 'UpdraftPlus_RemoteStorage_Addons_Base_v2')) $this->uploaded_file($file); - } - - return $ret; - - } - - /** - * Provides a convenience function allowing remote storage methods to download a file in chunks, without duplicated overhead. - * - * @param string $file - The basename of the file being downloaded - * @param object $method - This remote storage method object needs to have a chunked_download() method to call back - * @param integer $remote_size - The size, in bytes, of the object being downloaded - * @param boolean $manually_break_up - Whether to break the download into multiple network operations (rather than just issuing a GET with a range beginning at the end of the already-downloaded data, and carrying on until it times out) - * @param Mixed $passback - A value to pass back to the callback function - * @param integer $chunk_size - Break up the download into chunks of this number of bytes. Should be set if and only if $manually_break_up is true. - */ - public function chunked_download($file, $method, $remote_size, $manually_break_up = false, $passback = null, $chunk_size = 1048576) { - - try { - - $fullpath = $this->backups_dir_location().'/'.$file; - $start_offset = file_exists($fullpath) ? filesize($fullpath) : 0; - - if ($start_offset >= $remote_size) { - $this->log("File is already completely downloaded ($start_offset/$remote_size)"); - return true; - } - - // Some more remains to download - so let's do it - // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php - if (!($fh = fopen($fullpath, 'c+'))) { - $this->log("Error opening local file: $fullpath"); - $this->log($file.": ".__("Error", 'updraftplus').": ".__('Error opening local file: Failed to download', 'updraftplus'), 'error'); - return false; - } - - $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size) : $remote_size; - - // This only affects logging - $expected_bytes_delivered_so_far = true; - - while ($start_offset < $remote_size) { - $headers = array(); - // If resuming, then move to the end of the file - - $requested_bytes = $last_byte-$start_offset; - - if ($expected_bytes_delivered_so_far) { - $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next $requested_bytes bytes"); - } else { - $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next chunk (${start_offset}-)"); - } - - if ($start_offset > 0 || $last_byte<$remote_size) { - fseek($fh, $start_offset); - // N.B. Don't alter this format without checking what relies upon it - $last_byte_start = $last_byte - 1; - $headers['Range'] = "bytes=$start_offset-$last_byte_start"; - } - - /* - * The most common method is for the remote storage module to return a string with the results in it. In that case, the final $fh parameter is unused. However, since not all SDKs have that option conveniently, it is also possible to use the file handle and write directly to that; in that case, the method can either return the number of bytes written, or (boolean)true to infer it from the new file *pointer*. - * The method is free to write/return as much data as it pleases. - */ - $ret = $method->chunked_download($file, $headers, $passback, $fh); - if (true === $ret) { - clearstatcache(); - // Some SDKs (including AWS/S3) close the resource - // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php - if (is_resource($fh)) { - $ret = ftell($fh); - } else { - $ret = filesize($fullpath); - // fseek returns - on success - if (false == ($fh = fopen($fullpath, 'c+')) || 0 !== fseek($fh, $ret)) { - $this->log("Error opening local file: $fullpath"); - $this->log($file.": ".__("Error", 'updraftplus').": ".__('Error opening local file: Failed to download', 'updraftplus'), 'error'); - return false; - } - } - if (is_integer($ret)) $ret -= $start_offset; - } - - // Note that this covers a false code returned either by chunked_download() or by ftell. - if (false === $ret) return false; - - $returned_bytes = is_integer($ret) ? $ret : strlen($ret); - - if ($returned_bytes > $requested_bytes || $returned_bytes < $requested_bytes - 1) $expected_bytes_delivered_so_far = false; - - if (!is_integer($ret) && !fwrite($fh, $ret)) throw new Exception('Write failure (start offset: '.$start_offset.', bytes: '.strlen($ret).'; requested: '.$requested_bytes.')'); - - clearstatcache(); - $start_offset = ftell($fh); - $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size) : $remote_size; - - } - - } catch (Exception $e) { - $this->log('Error ('.get_class($e).') - failed to download the file ('.$e->getCode().', '.$e->getMessage().', line '.$e->getLine().' in '.$e->getFile().')'); - $this->log("$file: ".__('Error - failed to download the file', 'updraftplus').' ('.$e->getCode().', '.$e->getMessage().')', 'error'); - return false; - } - - // April 1st 2020 - Due to a bug during uploads to Dropbox some backups had string "null" appended to the end which caused warnings, this removes the string "null" from these backups - if ('dropbox' == $method->get_id()) { - fseek($fh, -4, SEEK_END); - $data = fgets($fh, 5); - if ("null" == $data) { - ftruncate($fh, filesize($fullpath) - 4); - } - } - - fclose($fh); - - return true; - } - - /** - * Detect if safe_mode is on. N.B. This is abolished from PHP 7.0 - * - * @return Integer - 1 or 0 - */ - public function detect_safe_mode() { - // @codingStandardsIgnoreLine - return (@ini_get('safe_mode') && 'off' != strtolower(@ini_get('safe_mode'))) ? 1 : 0; - } - - /** - * Find, if possible, a working mysqldump executable - * - * @param Boolean $log_it - whether to log the workings or not - * @param Boolean $cacheit - whether to cache the results for subsequent queries or not - * - * @return String|Boolean - either a path to an executable, or false for failure - */ - public function find_working_sqldump($log_it = true, $cacheit = true) { - - // The hosting provider may have explicitly disabled the popen or proc_open functions - if ($this->detect_safe_mode() || !function_exists('popen') || !function_exists('escapeshellarg')) { - if ($cacheit) $this->jobdata_set('binsqldump', false); - return false; - } - $existing = $this->jobdata_get('binsqldump', null); - // Theoretically, we could have moved machines, due to a migration - if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $updraft_dir = $this->backups_dir_location(); - global $wpdb; - $table_name = $wpdb->get_blog_prefix().'options'; - $tmp_file = md5(time().rand()).".sqltest.tmp"; - $pfile = md5(time().rand()).'.tmp'; - file_put_contents($updraft_dir.'/'.$pfile, "[mysqldump]\npassword=".DB_PASSWORD."\n"); - - $result = false; - foreach (explode(',', UPDRAFTPLUS_MYSQLDUMP_EXECUTABLE) as $potsql) { - - if (!@is_executable($potsql)) continue;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if ($log_it) $this->log("Testing potential mysqldump binary: $potsql"); - - if ('win' == strtolower(substr(PHP_OS, 0, 3))) { - $exec = "cd ".escapeshellarg(str_replace('/', '\\', $updraft_dir))." & "; - $siteurl = "'siteurl'"; - if (false !== strpos($potsql, ' ')) $potsql = '"'.$potsql.'"'; - } else { - $exec = "cd ".escapeshellarg($updraft_dir)."; "; - $siteurl = "\\'siteurl\\'"; - if (false !== strpos($potsql, ' ')) $potsql = "'$potsql'"; - } - - // Allow --max_allowed_packet to be configured via constant. Experience has shown some customers with complex CMS or pagebuilder setups can have extrememly large postmeta entries. - $msqld_max_allowed_packet = (defined('UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET') && (is_int(UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET) || is_string(UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET))) ? UPDRAFTPLUS_MYSQLDUMP_MAX_ALLOWED_PACKET : '1M'; - - $exec .= "$potsql --defaults-file=$pfile --max_allowed_packet=$msqld_max_allowed_packet --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --where=option_name=$siteurl --user=".escapeshellarg(DB_USER)." --host=".escapeshellarg(DB_HOST)." ".DB_NAME." ".escapeshellarg($table_name).""; - - $handle = popen($exec, "r"); - if ($handle) { - if (!feof($handle)) { - $output = fread($handle, 8192); - if ($output && $log_it) { - $log_output = (strlen($output) > 512) ? substr($output, 0, 512).' (truncated - '.strlen($output).' bytes total)' : $output; - $this->log("Output: ".str_replace("\n", '\\n', trim($log_output))); - } - } else { - $output = ''; - } - $ret = pclose($handle); - if (0 != $ret) { - if ($log_it) { - $this->log("Binary mysqldump: error (code: $ret)"); - } - } else { -// $dumped = file_get_contents($updraft_dir.'/'.$tmp_file, false, null, 0, 4096); - if (stripos($output, 'insert into') !== false) { - if ($log_it) $this->log("Working binary mysqldump found: $potsql"); - $result = $potsql; - break; - } - } - } else { - if ($log_it) $this->log("Error: popen failed"); - } - } - - @unlink($updraft_dir.'/'.$pfile);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($updraft_dir.'/'.$tmp_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if ($cacheit) $this->jobdata_set('binsqldump', $result); - - return $result; - } - - /** - * This function will work out which zip object we want to use and return it's name - * - * @return string - the name of the zip object we want to use - */ - public function get_zip_object_name() { - - if (!class_exists('UpdraftPlus_BinZip')) include_once(UPDRAFTPLUS_DIR . '/includes/class-zip.php'); - - $zip_object = 'UpdraftPlus_ZipArchive'; - - // In tests, PclZip was found to be 25% slower than ZipArchive - if (((defined('UPDRAFTPLUS_PREFERPCLZIP') && UPDRAFTPLUS_PREFERPCLZIP == true) || !class_exists('ZipArchive') || !class_exists('UpdraftPlus_ZipArchive') || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile')))) { - $zip_object = 'UpdraftPlus_PclZip'; - } - - return $zip_object; - } - - /** - * We require -@ and -u -r to work - which is the usual Linux binzip - * - * @param Boolean $log_it - whether to record the results with UpdraftPlus::log() - * @param Boolean $cacheit - whether to cache the results as job data - * @return String|Boolean - the path to a working zip binary, or false - */ - public function find_working_bin_zip($log_it = true, $cacheit = true) { - if ($this->detect_safe_mode()) return false; - // The hosting provider may have explicitly disabled the popen or proc_open functions - if (!function_exists('popen') || !function_exists('proc_open') || !function_exists('escapeshellarg')) { - if ($cacheit) $this->jobdata_set('binzip', false); - return false; - } - - $existing = $this->jobdata_get('binzip', null); - // Theoretically, we could have moved machines, due to a migration - if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $updraft_dir = $this->backups_dir_location(); - foreach (explode(',', UPDRAFTPLUS_ZIP_EXECUTABLE) as $potzip) { - if (!@is_executable($potzip)) continue;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if ($log_it) $this->log("Testing: $potzip"); - - // Test it, see if it is compatible with Info-ZIP - // If you have another kind of zip, then feel free to tell me about it - @mkdir($updraft_dir.'/binziptest/subdir1/subdir2', 0777, true);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - if (!file_exists($updraft_dir.'/binziptest/subdir1/subdir2')) return false; - - file_put_contents($updraft_dir.'/binziptest/subdir1/subdir2/test.html', 'UpdraftPlus is a great backup and restoration plugin for WordPress.'); - @unlink($updraft_dir.'/binziptest/test.zip');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - if (is_file($updraft_dir.'/binziptest/subdir1/subdir2/test.html')) { - - $exec = "cd ".escapeshellarg($updraft_dir)."; $potzip"; - if (defined('UPDRAFTPLUS_BINZIP_OPTS') && UPDRAFTPLUS_BINZIP_OPTS) $exec .= ' '.UPDRAFTPLUS_BINZIP_OPTS; - $exec .= " -v -u -r binziptest/test.zip binziptest/subdir1"; - - $all_ok=true; - $handle = popen($exec, "r"); - if ($handle) { - while (!feof($handle)) { - $w = fgets($handle); - if ($w && $log_it) $this->log("Output: ".trim($w)); - } - $ret = pclose($handle); - if (0 != $ret) { - if ($log_it) $this->log("Binary zip: error (code: $ret)"); - $all_ok = false; - } - } else { - if ($log_it) $this->log("Error: popen failed"); - $all_ok = false; - } - - // Now test -@ - if (true == $all_ok) { - file_put_contents($updraft_dir.'/binziptest/subdir1/subdir2/test2.html', 'UpdraftPlus is a really great backup and restoration plugin for WordPress.'); - - $exec = $potzip; - if (defined('UPDRAFTPLUS_BINZIP_OPTS') && UPDRAFTPLUS_BINZIP_OPTS) $exec .= ' '.UPDRAFTPLUS_BINZIP_OPTS; - $exec .= " -v -@ binziptest/test.zip"; - - $all_ok = true; - - $descriptorspec = array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w') - ); - $handle = proc_open($exec, $descriptorspec, $pipes, $updraft_dir); - if (is_resource($handle)) { - if (!fwrite($pipes[0], "binziptest/subdir1/subdir2/test2.html\n")) { - @fclose($pipes[0]);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @fclose($pipes[1]);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @fclose($pipes[2]);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - $all_ok = false; - } else { - fclose($pipes[0]); - while (!feof($pipes[1])) { - $w = fgets($pipes[1]); - if ($w && $log_it) $this->log("Output: ".trim($w)); - } - fclose($pipes[1]); - - while (!feof($pipes[2])) { - $last_error = fgets($pipes[2]); - if (!empty($last_error) && $log_it) $this->log("Stderr output: ".trim($w)); - } - fclose($pipes[2]); - - $ret = proc_close($handle); - if (0 != $ret) { - if ($log_it) $this->log("Binary zip: error (code: $ret)"); - $all_ok = false; - } - - } - - } else { - if ($log_it) $this->log("Error: proc_open failed"); - $all_ok = false; - } - - } - - // Do we now actually have a working zip? Need to test the created object using PclZip - // If it passes, then remove dirs and then return $potzip; - $found_first = false; - $found_second = false; - if ($all_ok && file_exists($updraft_dir.'/binziptest/test.zip')) { - if (function_exists('gzopen')) { - if (!class_exists('PclZip')) include_once(ABSPATH.'/wp-admin/includes/class-pclzip.php'); - $zip = new PclZip($updraft_dir.'/binziptest/test.zip'); - if (($list = $zip->listContent()) != 0) { - foreach ($list as $obj) { - if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test.html' == $obj['stored_filename'] && 131 == $obj['size']) $found_first=true; - if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test2.html' == $obj['stored_filename'] && 138 == $obj['size']) $found_second=true; - } - } - } else { - // PclZip will die() if gzopen is not found - // Obviously, this is a kludge - we assume it's working. We could, of course, just return false - but since we already know now that PclZip can't work, that only leaves ZipArchive - $this->log("gzopen function not found; PclZip cannot be invoked; will assume that binary zip works if we have a non-zero file"); - if (filesize($updraft_dir.'/binziptest/test.zip') > 0) { - $found_first = true; - $found_second = true; - } - } - } - $this->remove_binzip_test_files($updraft_dir); - if ($found_first && $found_second) { - if ($log_it) $this->log("Working binary zip found: $potzip"); - if ($cacheit) $this->jobdata_set('binzip', $potzip); - return $potzip; - } - - } - $this->remove_binzip_test_files($updraft_dir); - } - if ($cacheit) $this->jobdata_set('binzip', false); - return false; - } - - /** - * Remove potentially existing test files after binzip testing - * - * @param String $updraft_dir - directory to find the files in - */ - private function remove_binzip_test_files($updraft_dir) { - @unlink($updraft_dir.'/binziptest/subdir1/subdir2/test.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($updraft_dir.'/binziptest/subdir1/subdir2/test2.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @rmdir($updraft_dir.'/binziptest/subdir1/subdir2');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @rmdir($updraft_dir.'/binziptest/subdir1');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @unlink($updraft_dir.'/binziptest/test.zip');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @rmdir($updraft_dir.'/binziptest');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - } - - public function option_filter_get($which) { - global $wpdb; - $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $which)); - // Has to be get_row instead of get_var because of funkiness with 0, false, null values - return (is_object($row)) ? $row->option_value : false; - } - - /** - * Indicate which checksums to take for backup files. Abstracted for extensibilty and future changes. - * - * @returns array - a list of hashing algorithms, as understood by PHP's hash() function - */ - public function which_checksums() { - return apply_filters('updraftplus_which_checksums', array('sha1', 'sha256')); - } - - /** - * Pretty printing of the raw backup information - * - * @param String $description - * @param Array $history - * @param String $entity - * @param Array $checksums - * @param Array $jobdata - * @param Boolean $smaller - * @return String - */ - public function printfile($description, $history, $entity, $checksums, $jobdata, $smaller = false) { - - if (empty($history[$entity])) return; - - // PHP 7.2+ throws a warning if you try to count() a string - $how_many = is_string($history[$entity]) ? 1 : count($history[$entity]); - - if ($smaller) { - $pfiles = "".$description." (".sprintf(__('files: %s', 'updraftplus'), $how_many).")
    \n"; - } else { - $pfiles = "

    ".$description." (".sprintf(__('files: %s', 'updraftplus'), $how_many).")

    \n\n"; - } - - $is_incremental = (!empty($jobdata) && !empty($jobdata['job_type']) && 'incremental' == $jobdata['job_type'] && 'db' != substr($entity, 0, 2)) ? true : false; - - if ($is_incremental) { - $backup_timestamp = $jobdata['backup_time']; - $backup_history = UpdraftPlus_Backup_History::get_history($backup_timestamp); - $pfiles .= "
    "; - foreach ($backup_history['incremental_sets'] as $timestamp => $backup) { - if (isset($backup[$entity])) { - $pfiles .= "
    ".get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $timestamp), 'M d, Y G:i')."\n
    \n"; - foreach ($backup[$entity] as $ind => $file) { - $pfiles .= "
    ".$this->get_entity_row($file, $history, $entity, $checksums, $jobdata, $ind)."\n
    \n"; - } - } - } - $pfiles .= "
    \n"; - } else { - - $pfiles .= "
      "; - $files = $history[$entity]; - if (is_string($files)) $files = array($files); - - foreach ($files as $ind => $file) { - $pfiles .= "
    • ".$this->get_entity_row($file, $history, $entity, $checksums, $jobdata, $ind)."\n
    • \n"; - } - $pfiles .= "
    \n"; - } - - return $pfiles; - } - - /** - * This function will use the passed in information to prepare a pretty string describing the backup from the raw backup history - * - * @param String $file - the backup file - * @param Array $history - the backup history - * @param String $entity - the backup entity - * @param Array $checksums - checksums for the backup file - * @param Array $jobdata - the jobdata for this backup - * @param integer $ind - the index of the file - * - * @return String - returns the entity output string - */ - public function get_entity_row($file, $history, $entity, $checksums, $jobdata, $ind) { - $op = htmlspecialchars($file); - $skey = $entity.((0 == $ind) ? '' : $ind).'-size'; - - $op = apply_filters('updraft_report_downloadable_file_link', $op, $entity, $ind, $jobdata); - - $op .= "\n"; - - $meta = ''; - if ('db' == substr($entity, 0, 2) && 'db' != $entity) { - $dind = substr($entity, 2); - if (is_array($jobdata) && !empty($jobdata['backup_database']) && is_array($jobdata['backup_database']) && !empty($jobdata['backup_database'][$dind]) && is_array($jobdata['backup_database'][$dind]['dbinfo']) && !empty($jobdata['backup_database'][$dind]['dbinfo']['host'])) { - $dbinfo = $jobdata['backup_database'][$dind]['dbinfo']; - $meta .= sprintf(__('External database (%s)', 'updraftplus'), $dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'])."
    "; - } - } - if (isset($history[$skey])) $meta .= sprintf(__('Size: %s MB', 'updraftplus'), round($history[$skey]/1048576, 1)); - $ckey = $entity.$ind; - foreach ($checksums as $ck) { - $ck_plain = false; - if (isset($history['checksums'][$ck][$ckey])) { - $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'updraftplus'), strtoupper($ck), $history['checksums'][$ck][$ckey]); - $ck_plain = true; - } - if (isset($history['checksums'][$ck][$ckey.'.crypt'])) { - if ($ck_plain) $meta .= ' '.__('(when decrypted)'); - $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'updraftplus'), strtoupper($ck), $history['checksums'][$ck][$ckey.'.crypt']); - } - } - - $fileinfo = apply_filters("updraftplus_fileinfo_$entity", array(), $ind); - if (is_array($fileinfo) && !empty($fileinfo)) { - if (isset($fileinfo['html'])) { - $meta .= $fileinfo['html']; - } - } - - // if ($meta) $meta = " ($meta)"; - if ($meta) $meta = "
    $meta"; - - return $op.$meta; - } - - /** - * This important function returns a list of file entities that can potentially be backed up (subject to users settings), and optionally further meta-data about them - * - * @param boolean $include_others - * @param boolean $full_info - * @return array - */ - public function get_backupable_file_entities($include_others = true, $full_info = false) { - - $wp_upload_dir = $this->wp_upload_dir(); - - if ($full_info) { - $arr = array( - 'plugins' => array('path' => untrailingslashit(WP_PLUGIN_DIR), 'description' => __('Plugins', 'updraftplus')), - 'themes' => array('path' => WP_CONTENT_DIR.'/themes', 'description' => __('Themes', 'updraftplus')), - 'uploads' => array('path' => untrailingslashit($wp_upload_dir['basedir']), 'description' => __('Uploads', 'updraftplus')) - ); - } else { - $arr = array( - 'plugins' => untrailingslashit(WP_PLUGIN_DIR), - 'themes' => WP_CONTENT_DIR.'/themes', - 'uploads' => untrailingslashit($wp_upload_dir['basedir']) - ); - } - - $arr = apply_filters('updraft_backupable_file_entities', $arr, $full_info); - - // We then add 'others' on to the end - if ($include_others) { - if ($full_info) { - $arr['others'] = array('path' => WP_CONTENT_DIR, 'description' => __('Others', 'updraftplus')); - } else { - $arr['others'] = WP_CONTENT_DIR; - } - } - - // Entries that should be added after 'others' - $arr = apply_filters('updraft_backupable_file_entities_final', $arr, $full_info); - - return $arr; - - } - - public function php_error_to_logline($errno, $errstr, $errfile, $errline) { - switch ($errno) { - case 1: - $e_type = 'E_ERROR'; - break; - case 2: - $e_type = 'E_WARNING'; - break; - case 4: - $e_type = 'E_PARSE'; - break; - case 8: - $e_type = 'E_NOTICE'; - break; - case 16: - $e_type = 'E_CORE_ERROR'; - break; - case 32: - $e_type = 'E_CORE_WARNING'; - break; - case 64: - $e_type = 'E_COMPILE_ERROR'; - break; - case 128: - $e_type = 'E_COMPILE_WARNING'; - break; - case 256: - $e_type = 'E_USER_ERROR'; - break; - case 512: - $e_type = 'E_USER_WARNING'; - break; - case 1024: - $e_type = 'E_USER_NOTICE'; - break; - case 2048: - $e_type = 'E_STRICT'; - break; - case 4096: - $e_type = 'E_RECOVERABLE_ERROR'; - break; - case 8192: - $e_type = 'E_DEPRECATED'; - break; - case 16384: - $e_type = 'E_USER_DEPRECATED'; - break; - case 30719: - $e_type = 'E_ALL'; - break; - default: - $e_type = "E_UNKNOWN ($errno)"; - break; - } - - if (!is_string($errstr)) $errstr = serialize($errstr); - - if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH)); - - if ('E_DEPRECATED' == $e_type && !empty($this->no_deprecation_warnings)) { - return false; - } - - return "PHP event: code $e_type: $errstr (line $errline, $errfile)"; - - } - - public function php_error($errno, $errstr, $errfile, $errline) { - if (0 == error_reporting()) return true; - $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline); - if (false !== $logline) $this->log($logline, 'notice', 'php_event'); - // Pass it up the chain - return $this->error_reporting_stop_when_logged; - } - - /** - * Proceed with a backup; before calling this, at least all the initial job data must be set up - * - * @param Integer $resumption_no - which resumption this is; from 0 upwards - * @param String $bnonce - the backup job identifier - */ - public function backup_resume($resumption_no, $bnonce) { - - // Theoretically (N.B. has been seen in the real world), the WP scheduler might call us more than once within the same context (e.g. an incremental run followed by a main backup resumption), leaving us with incorrect internal state if we don't reset. - static $last_bnonce = null; - if ($last_bnonce) $this->jobdata_reset(); - $last_bnonce = $bnonce; - - set_error_handler(array($this, 'php_error'), E_ALL & ~E_STRICT); - - $this->current_resumption = $resumption_no; - - @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - @ignore_user_abort(true);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged - - $runs_started = array(); - $time_now = microtime(true); - - UpdraftPlus_Backup_History::always_get_from_db(); - - // Restore state - $resumption_extralog = ''; - $prev_resumption = $resumption_no - 1; - $last_successful_resumption = -1; - $job_type = 'backup'; - - if (0 == $resumption_no) { - $label = $this->jobdata_get('label'); - if ($label) $resumption_extralog = apply_filters('updraftplus_autobackup_extralog', ", label=$label"); - } else { - $this->nonce = $bnonce; - $file_nonce = $this->jobdata_get('file_nonce'); - $this->file_nonce = $file_nonce ? $file_nonce : $bnonce; - $this->backup_time = $this->jobdata_get('backup_time'); - $this->job_time_ms = $this->jobdata_get('job_time_ms'); - - // Get the warnings before opening the log file, as opening the log file may generate new ones (which then leads to $this->errors having duplicate entries when they are copied over below) - $warnings = $this->jobdata_get('warnings'); - - $this->logfile_open($this->file_nonce); - - if (!$this->get_backup_job_semaphore_lock($this->nonce, $resumption_no)) { - $this->log('Failed to get backup job lock; possible overlapping resumptions - will abort this instance'); - die; - } - - // Import existing warnings. The purpose of this is so that when save_backup_to_history() is called, it has a complete set - because job data expires quickly, whilst the warnings of the last backup run need to persist - if (is_array($warnings)) { - foreach ($warnings as $warning) { - $this->errors[] = array('level' => 'warning', 'message' => $warning); - } - } - - $runs_started = $this->jobdata_get('runs_started'); - if (!is_array($runs_started)) $runs_started =array(); - $time_passed = $this->jobdata_get('run_times'); - if (!is_array($time_passed)) $time_passed = array(); - - foreach ($time_passed as $run => $passed) { - if (isset($runs_started[$run]) && $runs_started[$run] + $time_passed[$run] + 30 > $time_now) { - // We don't want to increase the resumption if WP has started two copies of the same resumption off - if ($run && $run == $resumption_no) { - $increase_resumption = false; - $this->log("It looks like WordPress's scheduler has started multiple instances of this resumption"); - } else { - $increase_resumption = true; - } - UpdraftPlus_Job_Scheduler::terminate_due_to_activity('check-in', round($time_now, 1), round($runs_started[$run] + $time_passed[$run], 1), $increase_resumption); - } - } - - $useful_checkins = $this->jobdata_get('useful_checkins', array()); - if (!empty($useful_checkins)) { - $last_successful_resumption = min(max($useful_checkins), $prev_resumption); - } - - if (isset($time_passed[$prev_resumption])) { - // N.B. A check-in occurred; we haven't yet tested if it was useful - $resumption_extralog = ", previous check-in=".round($time_passed[$prev_resumption], 1)."s"; - } - - // This is just a simple test to catch restorations of old backup sets where the backup includes a resumption of the backup job - if ($time_now - $this->backup_time > 172800 && true == apply_filters('updraftplus_check_obsolete_backup', true, $time_now, $this)) { - - // We have seen cases where the get_site_option() call that self::get_jobdata() relies on returns nothing, even though the data was there in the database. This appears to be sometimes reproducible for the people who get it, but stops being reproducible if they change their backup times - which suggests that they're having failures at times of extreme load. We can attempt to detect this case, and reschedule, instead of aborting. - if (empty($this->backup_time) && empty($this->backup_is_already_complete) && !empty($this->logfile_name) && is_readable($this->logfile_name)) { - $first_log_bit = file_get_contents($this->logfile_name, false, null, 0, 250); - if (preg_match('/\(0\) Opened log file at time: (.*) on /', $first_log_bit, $matches)) { - $first_opened = strtotime($matches[1]); - // The value of 1000 seconds here is somewhat arbitrary; but allows for the problem to occur in ~ the first 15 minutes. In practice, the problem is extremely rare; if this does not catch it, we can tweak the algorithm. - if (time() - $first_opened < 1000) { - $this->log("This backup task (".$this->nonce.") failed to load its job data (possible database server malfunction), but appears to be only recently started: scheduling a fresh resumption in order to try again, and then ending this resumption ($time_now, ".$this->backup_time.") (existing jobdata keys: ".implode(', ', array_keys($this->jobdata)).")"); - UpdraftPlus_Job_Scheduler::reschedule(120); - die; - } - } - } - - // If we are doing a local upload then we do not want to abort the backup as it's possible they are uploading a backup that is older than two days - if (empty($this->jobdata['local_upload'])) { - $this->log("This backup task (" . $this->nonce . ") is either complete or began over 2 days ago: ending ($time_now, " . $this->backup_time . ") (existing jobdata keys: " . implode(', ', array_keys($this->jobdata)) . ")"); - die; - } - } - - } - - $this->last_successful_resumption = $last_successful_resumption; - - $runs_started[$resumption_no] = $time_now; - if (!empty($this->backup_time)) $this->jobdata_set('runs_started', $runs_started); - - // Schedule again, to run in 5 minutes again, in case we again fail - // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping - $resume_interval = max((int) $this->jobdata_get('resume_interval'), 100); - - $btime = $this->backup_time; - - $job_type = $this->jobdata_get('job_type'); - - do_action('updraftplus_resume_backup_'.$job_type); - - $updraft_dir = $this->backups_dir_location(); - - $time_ago = time()-$btime; - - $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, file_nonce=".$this->file_nonce." begun at=$btime (${time_ago}s ago), job type=$job_type".$resumption_extralog); - - // This works round a bizarre bug seen in one WP install, where delete_transient and wp_clear_scheduled_hook both took no effect, and upon 'resumption' the entire backup would repeat. - // Argh. In fact, this has limited effect, as apparently (at least on another install seen), the saving of the updated transient via jobdata_set() also took no effect. Still, it does not hurt. - if ($resumption_no >= 1 && 'finished' == $this->jobdata_get('jobstatus')) { - $this->log('Terminate: This backup job is already finished (1).'); - die; - } elseif ('clouduploading' != $this->jobdata_get('jobstatus') && 'backup' == $job_type && !empty($this->backup_is_already_complete)) { - $this->jobdata_set('jobstatus', 'finished'); - $this->log('Terminate: This backup job is already finished (2).'); - die; - } - - if ($resumption_no > 0 && isset($runs_started[$prev_resumption])) { - $our_expected_start = $runs_started[$prev_resumption] + $resume_interval; - // If the previous run increased the resumption time, then it is timed from the end of the previous run, not the start - if (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption]>0) $our_expected_start += $time_passed[$prev_resumption]; - $our_expected_start = apply_filters('updraftplus_expected_start', $our_expected_start, $job_type); - // More than 12 minutes late? - if ($time_now > $our_expected_start + 720) { - $this->log('Long time past since expected resumption time: approx expected='.round($our_expected_start, 1).", now=".round($time_now, 1).", diff=".round($time_now-$our_expected_start, 1)); - $this->log(__('Your website is visited infrequently and UpdraftPlus is not getting the resources it hoped for; please read this page:', 'updraftplus').' https://updraftplus.com/faqs/why-am-i-getting-warnings-about-my-site-not-having-enough-visitors/', 'warning', 'infrequentvisits'); - } - } - - $this->jobdata_set('current_resumption', $resumption_no); - - $first_run = apply_filters('updraftplus_filerun_firstrun', 0); - - // We just do this once, as we don't want to be in permanent conflict with the overlap detector - if ($resumption_no >= $first_run + 8 && $resumption_no < $first_run + 15 && $resume_interval >= 300) { - - // $time_passed is set earlier - list($max_time, $timings_string, $run_times_known) = UpdraftPlus_Manipulation_Functions::max_time_passed($time_passed, $resumption_no - 1, $first_run); - - // Do this on resumption 8, or the first time that we have 6 data points - if (($first_run + 8 == $resumption_no && $run_times_known >= 6) || (6 == $run_times_known && !empty($time_passed[$prev_resumption]))) { - $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time)"); - // Remember that 30 seconds is used as the 'perhaps something is still running' detection threshold, and that 45 seconds is used as the 'the next resumption is approaching - reschedule!' interval - if ($max_time + 52 < $resume_interval) { - $resume_interval = round($max_time + 52); - $this->log("Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds"); - $this->jobdata_set('resume_interval', $resume_interval); - } - // This next condition was added in response to HS#9174, a case where on one resumption, PHP was allowed to run for >3000 seconds - but other than that, up to 500 seconds. As a result, the resumption interval got stuck at a large value, whilst resumptions were only allowed to run for a much smaller amount. - // This detects whether our last run was less than half the resume interval, but was non-trivial (at least 50 seconds - so, indicating it didn't just error out straight away), but with a resume interval of over 300 seconds. In this case, it is reduced. - } elseif (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption] > 50 && $resume_interval > 300 && $time_passed[$prev_resumption] < $resume_interval/2 && 'clouduploading' == $this->jobdata_get('jobstatus')) { - $resume_interval = round($time_passed[$prev_resumption] + 52); - $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time). Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds"); - $this->jobdata_set('resume_interval', $resume_interval); - } - - } - - // A different argument than before is needed otherwise the event is ignored - $next_resumption = $resumption_no+1; - if ($next_resumption < $first_run + 10) { - if (true === $this->jobdata_get('one_shot')) { - if (true === $this->jobdata_get('reschedule_before_upload') && 1 == $next_resumption) { - $this->log('A resumption will be scheduled for the cloud backup stage'); - $schedule_resumption = true; - } else { - $this->log('We are in "one shot" mode - no resumptions will be scheduled'); - } - } else { - $schedule_resumption = true; - } - } else { - - // We're in over-time - we only reschedule if something useful happened last time (used to be that we waited for it to happen this time - but that meant that temporary errors, e.g. Google 400s on uploads, scuppered it all - we'd do better to have another chance - // 'useful_checkin' is < 1.16.35 (Nov 2020). It is only supported here for resumptions that span upgrades. Later it can be removed. - $useful_checkin = max($this->jobdata_get('useful_checkin', 0), max((array) $this->jobdata_get('useful_checkins', 0))); - - $last_resumption = $resumption_no - 1; - $fail_on_resume = $this->jobdata_get('fail_on_resume'); - - if (empty($useful_checkin) || $useful_checkin < $last_resumption) { - if (empty($fail_on_resume)) { - $this->log(sprintf('The current run is resumption number %d, and there was nothing useful done on the last run (last useful run: %s) - will not schedule a further attempt until we see something useful happening this time', $resumption_no, $useful_checkin)); - // Internally, we do actually schedule a resumption; but only in order to be able to nicely handle and log the failure, which otherwise may not be logged - $this->jobdata_set('fail_on_resume', $next_resumption); - $schedule_resumption = 1; - } - } else { - // Something useful happened last time - if (!empty($fail_on_resume)) { - $this->jobdata_delete('fail_on_resume'); - $fail_on_resume = false; - } - $schedule_resumption = true; - } - - if (!isset($time_passed[$prev_resumption])) { - $this->no_checkin_last_time = true; - } - - if (!empty($fail_on_resume) && $fail_on_resume == $this->current_resumption) { - $this->log('The backup is being aborted for a repeated failure to progress.', 'updraftplus'); - $this->log(__('The backup is being aborted for a repeated failure to progress.', 'updraftplus'), 'error'); - $this->backup_finish(true, true); - die; - } - } - - // Sanity check - if (empty($this->backup_time)) { - $this->log('The backup_time parameter appears to be empty (usually caused by resuming an already-complete backup).'); - return false; - } - - if (!empty($schedule_resumption)) { - $schedule_for = time() + $resume_interval; - if (1 === $schedule_resumption) { - $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for); but the job will then be aborted unless something happens this time"); - } else { - $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted"); - } - wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce)); - $this->newresumption_scheduled = $schedule_for; - } - - $backup_files = $this->jobdata_get('backup_files'); - - global $updraftplus_backup; - // Bring in all the backup routines - include_once(UPDRAFTPLUS_DIR.'/backup.php'); - $updraftplus_backup = new UpdraftPlus_Backup($backup_files, apply_filters('updraftplus_files_altered_since', -1, $job_type)); - - $undone_files = array(); - - if ('no' == $backup_files) { - $this->log('This backup run is not intended for files - skipping'); - $our_files = array(); - } else { - try { - // This should be always called; if there were no files in this run, it returns us an empty array - $backup_array = $updraftplus_backup->resumable_backup_of_files($resumption_no); - // This save, if there was something, is then immediately picked up again - if (is_array($backup_array)) { - $this->log('Saving backup status to database (elements: '.count($backup_array).")"); - $this->save_backup_to_history($backup_array); - } - - // Switch of variable name is purely vestigial - $our_files = $backup_array; - if (!is_array($our_files)) $our_files = array(); - } catch (Exception $e) { - $log_message = 'Exception ('.get_class($e).') occurred during files backup: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - // @codingStandardsIgnoreLine - if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary(); - $this->log($log_message); - $this->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - error_log($log_message); - // @codingStandardsIgnoreLine - if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary(); - $this->log($log_message); - $this->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - } - - } - - do_action('pre_database_backup_setup'); - - $backup_databases = $this->jobdata_get('backup_database'); - - if (!is_array($backup_databases)) $backup_databases = array('wp' => $backup_databases); - - foreach ($backup_databases as $whichdb => $backup_database) { - - if (is_array($backup_database)) { - $dbinfo = $backup_database['dbinfo']; - $backup_database = $backup_database['status']; - } else { - $dbinfo = array(); - } - - $tindex = ('wp' == $whichdb) ? 'db' : 'db'.$whichdb; - - if ('begun' == $backup_database || 'finished' == $backup_database || 'encrypted' == $backup_database) { - - if ('wp' == $whichdb) { - $db_descrip = 'WordPress DB'; - } else { - if (!empty($dbinfo) && is_array($dbinfo) && !empty($dbinfo['host'])) { - $db_descrip = "External DB $whichdb - ".$dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name']; - } else { - $db_descrip = "External DB $whichdb - details appear to be missing"; - } - } - - if ('begun' == $backup_database) { - if ($resumption_no > 0) { - $this->log("Resuming creation of database dump ($db_descrip)"); - } else { - $this->log("Beginning creation of database dump ($db_descrip)"); - } - } elseif ('encrypted' == $backup_database) { - $this->log("Database dump ($db_descrip): Creation and encryption were completed already"); - } else { - $this->log("Database dump ($db_descrip): Creation was completed already"); - } - - if ('wp' != $whichdb && (empty($dbinfo) || !is_array($dbinfo) || empty($dbinfo['host']))) { - unset($backup_databases[$whichdb]); - $this->jobdata_set('backup_database', $backup_databases); - continue; - } - - // Catch fatal errors through try/catch blocks around the database backup - try { - $db_backup = $updraftplus_backup->backup_db($backup_database, $whichdb, $dbinfo); - } catch (Exception $e) { - $log_message = 'Exception ('.get_class($e).') occurred during files backup: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - $this->log($log_message); - error_log($log_message); - $this->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - // @codingStandardsIgnoreLine - } catch (Error $e) { - $log_message = 'PHP Fatal error ('.get_class($e).') has occurred. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')'; - $this->log($log_message); - error_log($log_message); - $this->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error'); - die(); - } - - if (is_array($our_files) && is_string($db_backup)) $our_files[$tindex] = $db_backup; - - if ('encrypted' != $backup_database) { - $backup_databases[$whichdb] = array('status' => 'finished', 'dbinfo' => $dbinfo); - $this->jobdata_set('backup_database', $backup_databases); - } - } elseif ('no' == $backup_database) { - $this->log("No database backup ($whichdb) - not part of this run"); - } else { - $this->log("Unrecognised data when trying to ascertain if the database ($whichdb) was backed up (".serialize($backup_database).")"); - } - - // This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known. - $this->save_backup_to_history($our_files); - - // Potentially encrypt the database if it is not already - if ('no' != $backup_database && isset($our_files[$tindex]) && !preg_match("/\.crypt$/", $our_files[$tindex]) && 'incremental' != $job_type) { - $our_files[$tindex] = $updraftplus_backup->encrypt_file($our_files[$tindex]); - // No need to save backup history now, as it will happen in a few lines time - if (preg_match("/\.crypt$/", $our_files[$tindex])) { - $backup_databases[$whichdb] = array('status' => 'encrypted', 'dbinfo' => $dbinfo); - $this->jobdata_set('backup_database', $backup_databases); - } - } - - if ('no' != $backup_database && isset($our_files[$tindex]) && file_exists($updraft_dir.'/'.$our_files[$tindex])) { - $our_files[$tindex.'-size'] = filesize($updraft_dir.'/'.$our_files[$tindex]); - $this->save_backup_to_history($our_files); - } - - } - - $backupable_entities = $this->get_backupable_file_entities(true); - - $checksum_list = $this->which_checksums(); - - $checksums = array(); - - foreach ($checksum_list as $checksum) { - $checksums[$checksum] = array(); - } - - $total_size = 0; - - // Queue files for upload - foreach ($our_files as $key => $files) { - // Only continue if the stored info was about a dump - if (!isset($backupable_entities[$key]) && ('db' != substr($key, 0, 2) || '-size' == substr($key, -5, 5))) continue; - if (is_string($files)) $files = array($files); - foreach ($files as $findex => $file) { - - $size_key = (0 == $findex) ? $key.'-size' : $key.$findex.'-size'; - $total_size = (false === $total_size || !isset($our_files[$size_key]) || !is_numeric($our_files[$size_key])) ? false : $total_size + $our_files[$size_key]; - - foreach ($checksum_list as $checksum) { - - $cksum = $this->jobdata_get($checksum.'-'.$key.$findex); - if ($cksum) $checksums[$checksum][$key.$findex] = $cksum; - $cksum = $this->jobdata_get($checksum.'-'.$key.$findex.'.crypt'); - if ($cksum) $checksums[$checksum][$key.$findex.".crypt"] = $cksum; - - } -