'.$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();
- ?>
-
-
- 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').'
'),
- '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' => '
' . __('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'). '
',
- '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 .= '
'.__('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) {
- ?>
-
- 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));
- ?>
-
-
- 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 = '
'.sprintf(__('You have an unfinished restoration operation, begun %s ago.', 'updraftplus'), $time_ago).'
';
- $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 = '
';
- }
-
- $interested = htmlspecialchars(__('Interested in knowing about your UpdraftPlus.Com password security? Read about it here.', 'updraftplus'));
-
- $connect = htmlspecialchars(__('Connect', 'updraftplus'));
-
- $enter_credentials_end = '
';
-
- 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 '
";
- }
-
- 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 = '
';
- // $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 .= '
',__('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).'
'.__('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).'
'.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 = "
-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 '
';
- 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 '
';
- 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') .'
'.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 '
'; // 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').'
'. __('The following remote storage options are configured.', 'updraftplus').' '.$active_remote_storage_list.'
';
- }
-
- /**
- * 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'] = '
';
-
- $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') . '
' . __('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') . '
';
- }
-
- 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 814aa61d..00000000
--- 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 3a2d5e45..00000000
--- 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
';
-
- 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 '