public function mod_rewrite_unavailable($check_if_in_use_first = true) { if (function_exists('apache_get_modules')) { global $wp_rewrite; $mods = apache_get_modules(); if ((!$check_if_in_use_first || $wp_rewrite->using_mod_rewrite_permalinks()) && ((in_array('core', $mods) || in_array('http_core', $mods)) && !in_array('mod_rewrite', $mods))) { return true; } } return false; } /** * Count the number of alerts that have occurred at the specified level * * @param String $level - the level to count at * * @return Integer */ public function error_count($level = 'error') { $count = 0; foreach ($this->errors as $err) { if (('error' == $level && (is_string($err) || is_wp_error($err))) || (is_array($err) && $level == $err['level'])) { $count++; } } return $count; } public function list_errors() { echo ''; } private function save_last_backup($backup_array) { $success = ($this->error_count() == 0) ? 1 : 0; $last_backup = apply_filters('updraftplus_save_last_backup', array( 'backup_time' => $this->backup_time, 'backup_array' => $backup_array, 'success' => $success, 'errors' => $this->errors, 'backup_nonce' => $this->nonce )); UpdraftPlus_Options::update_updraft_option('updraft_last_backup', $last_backup, false); } /** * $handle must be either false or a WPDB class (or extension thereof). Other options are not yet fully supported. * * @param Resource|Boolean|Object $handle * @param Boolean $logit - whether to log information about the check * @param Boolean $reschedule - whether to schedule a resumption if checking fails * @return Boolean|Integer - whether the check succeeded, or -1 for an unknown result */ public function check_db_connection($handle = false, $logit = false, $reschedule = false) { $type = false; if (false === $handle || is_a($handle, 'wpdb')) { $type = 'wpdb'; } elseif (is_resource($handle)) { // Expected: string(10) "mysql link" $type = get_resource_type($handle); } elseif (is_object($handle) && is_a($handle, 'mysqli')) { $type = 'mysqli'; } if (false === $type) return -1; $db_connected = -1; if ('mysql link' == $type || 'mysqli' == $type) { // @codingStandardsIgnoreLine if ('mysql link' == $type && @mysql_ping($handle)) return true; if ('mysqli' == $type && @mysqli_ping($handle)) return true; // @codingStandardsIgnoreLine for ($tries = 1; $tries <= 5; $tries++) { // to do, if ever needed // if ($this->db_connect(false )) return true; // sleep(1); } } elseif ('wpdb' == $type) { if (false === $handle || (is_object($handle) && 'wpdb' == get_class($handle))) { global $wpdb; $handle = $wpdb; } if (method_exists($handle, 'check_connection') && (!defined('UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS') || !UPDRAFTPLUS_SUPPRESS_CONNECTION_CHECKS)) { if (!$handle->check_connection(false)) { if ($logit) $this->log("The database went away, and could not be reconnected to"); // Almost certainly a no-op if ($reschedule) UpdraftPlus_Job_Scheduler::reschedule(60); $db_connected = false; } else { $db_connected = true; } } } return $db_connected; } /** * This should be called whenever a file is successfully uploaded * * @param String $file - full filepath * @param Boolean $force - mark as successfully uploaded even if not on the last service * @return Void */ public function uploaded_file($file, $force = false) { global $updraftplus_backup; $db_connected = $this->check_db_connection(false, true, true); $service = empty($updraftplus_backup->current_service) ? '' : $updraftplus_backup->current_service; $instance_id = empty($updraftplus_backup->current_instance) ? '' : $updraftplus_backup->current_instance; $shash = $service.(('' == $service) ? '' : '-').$instance_id.(('' == $instance_id) ? '' : '-').md5($file); if ($force || !empty($updraftplus_backup->last_storage_instance)) { $this->log("Recording as successfully uploaded: $file"); $new_jobdata = $this->get_uploaded_jobdata_items($file, $service, $instance_id); } else { $new_jobdata = array('uploaded_'.$shash => 'yes'); $this->log("Recording as successfully uploaded: $file (".$updraftplus_backup->current_service.", more services to follow)"); } $upload_status = $this->jobdata_get('uploading_substatus'); if (is_array($upload_status) && isset($upload_status['i'])) { $upload_status['i']++; $upload_status['p'] = 0; $new_jobdata['uploading_substatus'] = $upload_status; } $this->jobdata_set_multi($new_jobdata); // Really, we could do this immediately when we realise the DB has gone away. This is just for the probably-impossible case that a DB write really can still succeed. But, we must abort before calling delete_local(), as the removal of the local file can cause it to be recreated if the DB is out of sync with the fact that it really is already uploaded if (false === $db_connected) { UpdraftPlus_Job_Scheduler::record_still_alive(); die; } // Delete local files immediately if the option is set // Where we are only backing up locally, only the "prune" function should do deleting $service = $this->jobdata_get('service'); if (!empty($updraftplus_backup->last_storage_instance) && ('' !== $service && ((is_array($service) && count($service)>0 && (count($service) > 1 || (array('') !== $service && array('none') !== $service))) || (is_string($service) && 'none' !== $service)))) { $this->delete_local($file); } } /** * Gets the jobdata items to be added to mark a file as uploaded * * @param String $file - the file (basename) * @param String $service - service identifier * @param String $instance_id - instance identifier * * @return Array - jobdata items */ public function get_uploaded_jobdata_items($file, $service = '', $instance_id = '') { $hash = md5($file); $shash = $service.(('' == $service) ? '' : '-').$instance_id.(('' == $instance_id) ? '' : '-').md5($file); return array( 'uploaded_lastreset' => $this->current_resumption, 'uploaded_'.$hash => 'yes', 'uploaded_'.$shash =>'yes' ); } /** * Return whether a particular file has been uploaded to a particular remote service * * @param String $file - the filename (basename) * @param String $service - the service identifier; or none, to indicate all services * @param String $instance_id - the instance identifier * * @return Boolean - the result */ public function is_uploaded($file, $service = '', $instance_id = '') { $hash = $service.(('' == $service) ? '' : '-').$instance_id.(('' == $instance_id) ? '' : '-').md5($file); return ('yes' === $this->jobdata_get("uploaded_$hash")) ? true : false; } private function delete_local($file) { $log = "Deleting local file: $file: "; if (UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1)) { $fullpath = $this->backups_dir_location().'/'.$file; // check to make sure it exists before removing if (realpath($fullpath)) { $deleted = unlink($fullpath); $this->log($log.(($deleted) ? 'OK' : 'failed')); return $deleted; } } else { $this->log($log."skipped: user has unchecked updraft_delete_local option"); } return true; } /** * For detecting another run, and aborting if one was found * * @param String $file - full file path of the file to check */ public function check_recent_modification($file) { if (file_exists($file)) { $time_mod = (int) @filemtime($file); $time_now = time(); if ($time_mod > 100 && ($time_now - $time_mod) < 30) { UpdraftPlus_Job_Scheduler::terminate_due_to_activity($file, $time_now, $time_mod); } } } public function get_exclude($whichone) { if ('uploads' == $whichone) { $exclude = explode(',', UpdraftPlus_Options::get_updraft_option('updraft_include_uploads_exclude', UPDRAFT_DEFAULT_UPLOADS_EXCLUDE)); } elseif ('others' == $whichone) { $exclude = explode(',', UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE)); } else { $exclude = apply_filters('updraftplus_include_'.$whichone.'_exclude', array()); } return (empty($exclude) || !is_array($exclude)) ? array() : $exclude; } public function wp_upload_dir() { if (is_multisite()) { global $current_site; switch_to_blog($current_site->blog_id); } $wp_upload_dir = wp_upload_dir(); if (is_multisite()) restore_current_blog(); return $wp_upload_dir; } public function backup_uploads_dirlist($logit = false) { // Create an array of directories to be skipped // Make the values into the keys $exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_uploads_exclude', UPDRAFT_DEFAULT_UPLOADS_EXCLUDE); if ($logit) $this->log("Exclusion option setting (uploads): ".$exclude); $skip = array_flip(preg_split("/,/", $exclude)); $wp_upload_dir = $this->wp_upload_dir(); $uploads_dir = $wp_upload_dir['basedir']; return $this->compile_folder_list_for_backup($uploads_dir, array(), $skip); } public function backup_others_dirlist($logit = false) { // Create an array of directories to be skipped // Make the values into the keys $exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE); if ($logit) $this->log("Exclusion option setting (others): ".$exclude); $skip = array_flip(preg_split("/,/", $exclude)); $file_entities = $this->get_backupable_file_entities(false); // Keys = directory names to avoid; values = the label for that directory (used only in log files) // $avoid_these_dirs = array_flip($file_entities); $avoid_these_dirs = array(); foreach ($file_entities as $type => $dirs) { if (is_string($dirs)) { $avoid_these_dirs[$dirs] = $type; } elseif (is_array($dirs)) { foreach ($dirs as $dir) { $avoid_these_dirs[$dir] = $type; } } } return $this->compile_folder_list_for_backup(WP_CONTENT_DIR, $avoid_these_dirs, $skip); } /** * avoid_these_dirs and skip_these_dirs ultimately do the same thing; but avoid_these_dirs takes full paths whereas skip_these_dirs takes basenames; and they are logged differently (dirs in avoid are potentially dangerous to include; skip is just a user-level preference). They are allowed to overlap. * * @param string $backup_from_inside_dir * @param string $avoid_these_dirs * @param string $skip_these_dirs * @return array */ public function compile_folder_list_for_backup($backup_from_inside_dir, $avoid_these_dirs, $skip_these_dirs) { // Entries in $skip_these_dirs are allowed to end in *, which means "and anything else as a suffix". It's not a full shell glob, but it covers what is needed to-date. $dirlist = array(); $added = 0; $this->log('Looking for candidates to backup in: '.$backup_from_inside_dir); $updraft_dir = $this->backups_dir_location(); if (is_file($backup_from_inside_dir)) { array_push($dirlist, $backup_from_inside_dir); $added++; $this->log("finding files: $backup_from_inside_dir: adding to list ($added)"); } elseif ($handle = opendir($backup_from_inside_dir)) { while (false !== ($entry = readdir($handle))) { if ('.' == $entry || '..' == $entry) continue; // $candidate: full path; $entry = one-level $candidate = $backup_from_inside_dir.'/'.$entry; if (isset($avoid_these_dirs[$candidate])) { $this->log("finding files: $entry: skipping: this is the ".$avoid_these_dirs[$candidate]." directory"); } elseif ($candidate == $updraft_dir) { $this->log("finding files: $entry: skipping: this is the updraft directory"); } elseif (isset($skip_these_dirs[$entry])) { $this->log("finding files: $entry: skipping: excluded by options"); } else { $add_to_list = true; // Now deal with entries in $skip_these_dirs ending in * or starting with * foreach ($skip_these_dirs as $skip => $sind) { if ('*' == substr($skip, -1, 1) && '*' == substr($skip, 0, 1) && strlen($skip) > 2) { if (strpos($entry, substr($skip, 1, strlen($skip)-2)) !== false) { $this->log("finding files: $entry: skipping: excluded by options (glob)"); $add_to_list = false; } } elseif ('*' == substr($skip, -1, 1) && strlen($skip) > 1) { if (substr($entry, 0, strlen($skip)-1) == substr($skip, 0, strlen($skip)-1)) { $this->log("finding files: $entry: skipping: excluded by options (glob)"); $add_to_list = false; } } elseif ('*' == substr($skip, 0, 1) && strlen($skip) > 1) { if (strlen($entry) >= strlen($skip)-1 && substr($entry, (strlen($skip)-1)*-1) == substr($skip, 1)) { $this->log("finding files: $entry: skipping: excluded by options (glob)"); $add_to_list = false; } } } if ($add_to_list) { array_push($dirlist, $candidate); $added++; $skip_dblog = (($added > 50 && 0 != $added % 100) || ($added > 2000 && 0 != $added % 500)); $this->log("finding files: $entry: adding to list ($added)", 'notice', false, $skip_dblog); } } } @closedir($handle); } else { $this->log('ERROR: Could not read the directory: '.$backup_from_inside_dir); $this->log(__('Could not read the directory', 'updraftplus').': '.$backup_from_inside_dir, 'error'); } return $dirlist; } /** * Save the backup information to the backup history during a running backup (adding information to the currently-running job) * * @param Array $backup_array - the backup history */ private function save_backup_to_history($backup_array) { if (!is_array($backup_array)) { $this->log('Could not save backup history because we have no backup array. Backup probably failed.'); $this->log(__('Could not save backup history because we have no backup array. Backup probably failed.', 'updraftplus'), 'error'); return; } $job_type = $this->jobdata_get('job_type'); $backup_array['nonce'] = $this->file_nonce; $backup_array['service'] = $this->jobdata_get('service'); $backup_array['service_instance_ids'] = array(); if ('incremental' != $job_type) $backup_array['always_keep'] = $this->jobdata_get('always_keep', false); $backup_array['files_enumerated_at'] = $this->jobdata_get('files_enumerated_at'); // N.B. Though the saved 'service' option can have various forms (especially if upgrading from (very) old versions), in the jobdata, it is always an array. $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids($backup_array['service']); // N.B. On PHP 5.5+, we'd use array_column() foreach ($storage_objects_and_ids as $method => $method_information) { if ('none' == $method || !$method || !$method_information['object']->supports_feature('multi_options')) continue; $backup_array['service_instance_ids'][$method] = array_keys($method_information['instance_settings']); } if ('incremental' != $job_type && '' != ($label = $this->jobdata_get('label', ''))) $backup_array['label'] = $label; if (!isset($backup_array['created_by_version'])) $backup_array['created_by_version'] = $this->version; $backup_array['last_saved_by_version'] = $this->version; $backup_array['is_multisite'] = is_multisite() ? true : false; $remotesend_info = $this->jobdata_get('remotesend_info'); if (is_array($remotesend_info) && !empty($remotesend_info['url'])) $backup_array['remotesend_url'] = $remotesend_info['url']; if (false != $this->jobdata_get('is_autobackup', false)) $backup_array['autobackup'] = true; if (false != ($morefiles_linked_indexes = $this->jobdata_get('morefiles_linked_indexes', false))) $backup_array['morefiles_linked_indexes'] = $morefiles_linked_indexes; if (false != ($morefiles_more_locations = $this->jobdata_get('morefiles_more_locations', false))) $backup_array['morefiles_more_locations'] = $morefiles_more_locations; UpdraftPlus_Backup_History::save_backup(apply_filters('updraftplus_save_backup_history_timestamp', $this->backup_time), $backup_array); } /** * If files + db are on different schedules but are scheduled for the same time, * then combine them $event = (object) array('hook' => $hook, 'timestamp' => $timestamp, 'schedule' => $recurrence, 'args' => $args, 'interval' => $schedules[$recurrence]['interval']); * See wp_schedule_single_event() and wp_schedule_event() in wp-includes/cron.php * * @param Object|Boolean $event - the event being scheduled * @return Object|Boolean - the filtered value */ public function schedule_event($event) { static $scheduled = array(); if (is_object($event) && ('updraft_backup' == $event->hook || 'updraft_backup_database' == $event->hook)) { // Reset the option - but make sure it is saved first so that we can used it (since this hook may be called just before our actual cron task) $this->combine_jobs_around = UpdraftPlus_Options::get_updraft_option('updraft_combine_jobs_around'); UpdraftPlus_Options::delete_updraft_option('updraft_combine_jobs_around'); $scheduled[$event->hook] = true; // This next fragment is wrong: there's only a 'second call' when saving all settings; otherwise, the WP scheduler might just be updating one event. So, there's some inefficieny as the option is wiped and set uselessly at least once when saving settings. // We only want to take action on the second call (otherwise, our information is out-of-date already) // If there is no second call, then that's fine - nothing to do // if (count($scheduled) < 2) { // return $event; // } $backup_scheduled_for = ('updraft_backup' == $event->hook) ? $event->timestamp : wp_next_scheduled('updraft_backup'); $db_scheduled_for = ('updraft_backup_database' == $event->hook) ? $event->timestamp : wp_next_scheduled('updraft_backup_database'); $diff = absint($backup_scheduled_for - $db_scheduled_for); $margin = (defined('UPDRAFTPLUS_COMBINE_MARGIN') && is_numeric(UPDRAFTPLUS_COMBINE_MARGIN)) ? UPDRAFTPLUS_COMBINE_MARGIN : 600; if ($backup_scheduled_for && $db_scheduled_for && $diff < $margin) { // We could change the event parameters; however, this would complicate other code paths (because the WP cron system uses a hash of the parameters as a key, and you must supply the exact parameters to look up events). So, we just set a marker that boot_backup() can pick up on. UpdraftPlus_Options::update_updraft_option('updraft_combine_jobs_around', min($backup_scheduled_for, $db_scheduled_for)); } } return $event; } /** * This function is both the backup scheduler and a filter callback for saving the option. It is called in the register_setting for the updraft_interval, which means when the admin settings are saved it is called. * * @param String $interval * @return String - filtered value */ public function schedule_backup($interval) { $previous_time = wp_next_scheduled('updraft_backup'); // Clear schedule so that we don't stack up scheduled backups wp_clear_scheduled_hook('updraft_backup'); if ('manual' == $interval) { // Clear increments schedule as the file schedule is manual wp_clear_scheduled_hook('updraft_backup_increments'); return 'manual'; } $previous_interval = UpdraftPlus_Options::get_updraft_option('updraft_interval'); $valid_schedules = wp_get_schedules(); if (empty($valid_schedules[$interval])) $interval = 'daily'; // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today. $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120; $first_time = apply_filters('updraftplus_schedule_firsttime_files', $default_time); wp_schedule_event($first_time, $interval, 'updraft_backup'); return $interval; } /** * This function is both the database backup scheduler and a filter callback for saving the option. It is called in the register_setting for the updraft_interval_database, which means when the admin settings are saved it is called. * * @param String $interval * @return String - filtered value */ public function schedule_backup_database($interval) { $previous_time = wp_next_scheduled('updraft_backup_database'); // Clear schedule so that we don't stack up scheduled backups wp_clear_scheduled_hook('updraft_backup_database'); if ('manual' == $interval) return 'manual'; $previous_interval = UpdraftPlus_Options::get_updraft_option('updraft_interval_database'); $valid_schedules = wp_get_schedules(); if (empty($valid_schedules[$interval])) $interval = 'daily'; // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today. $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120; $first_time = apply_filters('updraftplus_schedule_firsttime_db', $default_time); wp_schedule_event($first_time, $interval, 'updraft_backup_database'); return $interval; } /** * This function is both the increments backup scheduler and a filter callback for saving the option. It is called in the register_setting for the updraft_interval_increments, which means when the admin settings are saved it is called. * * @param String $interval * @return String - filtered value */ public function schedule_backup_increments($interval) { $previous_time = wp_next_scheduled('updraft_backup_increments'); // Clear schedule so that we don't stack up scheduled backups wp_clear_scheduled_hook('updraft_backup_increments'); if ('none' == $interval || empty($interval)) return 'none'; $previous_interval = UpdraftPlus_Options::get_updraft_option('updraft_interval_increments'); $valid_schedules = wp_get_schedules(); if (empty($valid_schedules[$interval])) $interval = 'daily'; // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today. $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120; $first_time = apply_filters('updraftplus_schedule_firsttime_increments', $default_time); wp_schedule_event($first_time, $interval, 'updraft_backup_increments'); return $interval; } /** * Acts as a WordPress options filter * * @param Array $options - An array of options * @param String $option_name - The option name * * @return Array - the returned array can either be the set of updated options or a WordPress error array */ public function storage_options_filter($options, $option_name) { if ('updraft_' !== substr($option_name, 0, 8)) return $options; $method = substr($option_name, 8); $storage = UpdraftPlus_Storage_Methods_Interface::get_storage_object($method); if (!is_a($storage, 'UpdraftPlus_BackupModule') || !is_callable(array($storage, 'options_filter'))) return $options; return call_user_func(array($storage, 'options_filter'), $options); } /** * Get the location of UD's internal directory * * @param Boolean $allow_cache * @return String - the directory path. Returns without any trailing slash. */ public function backups_dir_location($allow_cache = true) { if ($allow_cache && !empty($this->backup_dir)) return $this->backup_dir; $updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')); // When newly installing, if someone had (e.g.) wp-content/updraft in their database from a previous, deleted pre-1.7.18 install but had removed the updraft directory before re-installing, without this fix they'd end up with wp-content/wp-content/updraft. if (preg_match('/^wp-content\/(.*)$/', $updraft_dir, $matches) && ABSPATH.'wp-content' === WP_CONTENT_DIR) { UpdraftPlus_Options::update_updraft_option('updraft_dir', $matches[1]); $updraft_dir = WP_CONTENT_DIR.'/'.$matches[1]; } $default_backup_dir = WP_CONTENT_DIR.'/updraft'; $updraft_dir = ($updraft_dir) ? $updraft_dir : $default_backup_dir; // Do a test for a relative path if ('/' != substr($updraft_dir, 0, 1) && "\\" != substr($updraft_dir, 0, 1) && !preg_match('/^[a-zA-Z]:/', $updraft_dir)) { // Legacy - file paths stored related to ABSPATH if (is_dir(ABSPATH.$updraft_dir) && is_file(ABSPATH.$updraft_dir.'/index.html') && is_file(ABSPATH.$updraft_dir.'/.htaccess') && !is_file(ABSPATH.$updraft_dir.'/index.php') && false !== strpos(file_get_contents(ABSPATH.$updraft_dir.'/.htaccess', false, null, 0, 20), 'deny from all')) { $updraft_dir = ABSPATH.$updraft_dir; } else { // File paths stored relative to WP_CONTENT_DIR $updraft_dir = trailingslashit(WP_CONTENT_DIR).$updraft_dir; } } // Check for the existence of the dir and prevent enumeration // index.php is for a sanity check - make sure that we're not somewhere unexpected if ((!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) && !is_file($updraft_dir.'/index.php') || !is_file($updraft_dir.'/web.config')) { @mkdir($updraft_dir, 0775, true); @file_put_contents($updraft_dir.'/index.html', "WordPress backups by UpdraftPlus"); if (!is_file($updraft_dir.'/.htaccess')) @file_put_contents($updraft_dir.'/.htaccess', 'deny from all'); if (!is_file($updraft_dir.'/web.config')) @file_put_contents($updraft_dir.'/web.config', "\n\n\n\n\n\n\n"); } $this->backup_dir = $updraft_dir; return $updraft_dir; } public function spool_file($fullpath, $encryption = '') { @set_time_limit(900); if (!file_exists($fullpath) || filesize($fullpath) < 1) { _e('File not found', 'updraftplus'); return; } // Prevent any debug output // Don't enable this line - it causes 500 HTTP errors in some cases/hosts on some large files, for unknown reason // @ini_set('display_errors', '0'); $spooled = false; if (UpdraftPlus_Encryption::is_file_encrypted($fullpath)) { if (ob_get_level()) { $flush_max = min(5, (int) ob_get_level()); for ($i=1; $i<=$flush_max; $i++) { @ob_end_clean(); } } header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past UpdraftPlus_Encryption::spool_crypted_file($fullpath, (string) $encryption); return; } $content_type = UpdraftPlus_Manipulation_Functions::get_mime_type_from_filename($fullpath, false); include_once(UPDRAFTPLUS_DIR.'/includes/class-partialfileservlet.php'); // Prevent the file being read into memory if (ob_get_level()) { $flush_max = min(5, (int) ob_get_level()); for ($i=1; $i<=$flush_max; $i++) { @ob_end_clean(); } } if (ob_get_level()) @ob_end_clean(); // Twice - see HS#6673 - someone at least needed it if (isset($_SERVER['HTTP_RANGE'])) { $range_header = trim($_SERVER['HTTP_RANGE']); } elseif (function_exists('apache_request_headers')) { foreach (apache_request_headers() as $name => $value) { if (strtoupper($name) === 'RANGE') { $range_header = trim($value); } } } if (empty($range_header)) { header("Content-Length: ".filesize($fullpath)); header("Content-type: $content_type"); header("Content-Disposition: attachment; filename=\"".basename($fullpath)."\";"); readfile($fullpath); return; } try { $range_header = UpdraftPlus_RangeHeader::createFromHeaderString($range_header); $servlet = new UpdraftPlus_PartialFileServlet($range_header); $servlet->send_file($fullpath, $content_type); } catch (UpdraftPlus_InvalidRangeHeaderException $e) { header("HTTP/1.1 400 Bad Request"); error_log("UpdraftPlus: UpdraftPlus_InvalidRangeHeaderException: ".$e->getMessage()); } catch (UpdraftPlus_UnsatisfiableRangeException $e) { header("HTTP/1.1 416 Range Not Satisfiable"); } catch (UpdraftPlus_NonExistentFileException $e) { header("HTTP/1.1 404 Not Found"); } catch (UpdraftPlus_UnreadableFileException $e) { header("HTTP/1.1 500 Internal Server Error"); } } public function just_one_email($input, $required = false) { $x = $this->just_one($input, 'saveemails', (empty($input) && false === $required) ? '' : get_bloginfo('admin_email')); if (is_array($x)) { foreach ($x as $ind => $val) { if (empty($val)) unset($x[$ind]); } if (empty($x)) $x = ''; } return $x; } /** * Filter the values down to just one (subject to being filtered) * * @param Array|String $input - input * @param String $filter - filter suffix to use * @param Boolean|String $rinput - a 'preferred' value (unless false) if no filtering is done * * @return Array|String - output, after filtering */ public function just_one($input, $filter = 'savestorage', $rinput = false) { $oinput = $input; if (false === $rinput) $rinput = is_array($input) ? array_pop($input) : $input; if (is_string($rinput) && false !== strpos($rinput, ',')) $rinput = substr($rinput, 0, strpos($rinput, ',')); return apply_filters('updraftplus_'.$filter, $rinput, $oinput); } /** * Enqueue the JavaScript and CSS for the select2 library */ public function enqueue_select2() { // De-register to defeat any plugins that may have registered incompatible versions (e.g. WooCommerce 2.5 beta1 still has the Select 2 3.5 series) wp_deregister_script('select2'); wp_deregister_style('select2'); $select2_version = $this->use_unminified_scripts() ? '4.0.3'.'.'.time() : '4.0.3'; $min_or_not = $this->use_unminified_scripts() ? '' : '.min'; wp_enqueue_script('select2', UPDRAFTPLUS_URL."/includes/select2/select2".$min_or_not.".js", array('jquery'), $select2_version); wp_enqueue_style('select2', UPDRAFTPLUS_URL."/includes/select2/select2".$min_or_not.".css", array(), $select2_version); } public function memory_check_current($memory_limit = false) { // Returns in megabytes if (false == $memory_limit) $memory_limit = ini_get('memory_limit'); $memory_limit = rtrim($memory_limit); $memory_unit = $memory_limit[strlen($memory_limit)-1]; if (0 == (int) $memory_unit && '0' !== $memory_unit) { $memory_limit = substr($memory_limit, 0, strlen($memory_limit)-1); } else { $memory_unit = ''; } switch ($memory_unit) { case '': $memory_limit = floor($memory_limit/1048576); break; case 'K': case 'k': $memory_limit = floor($memory_limit/1024); break; case 'G': $memory_limit = $memory_limit*1024; break; case 'M': // assumed size, no change needed break; } return $memory_limit; } public function memory_check($memory, $check_using = false) { $memory_limit = $this->memory_check_current($check_using); return ($memory_limit >= $memory) ? true : false; } /** * Get the UpdraftPlus RSS feed * * @uses fetch_feed() * * @return WP_Error|SimplePie WP_Error object on failure or SimplePie object on success */ public function get_updraftplus_rssfeed() { if (!function_exists('fetch_feed')) include(ABSPATH.WPINC.'/feed.php'); return fetch_feed('http://feeds.feedburner.com/updraftplus/'); } /** * Sets up the nonce, basic job data, opens a log file for a new restore job, and makes sure that the Updraft_Restorer class is available * * @param boolean|string $nonce - the job nonce we want to use or false for a new one * * @return void */ public function initiate_restore_job($nonce = false) { $this->backup_time_nonce($nonce); // we reset here so that we ensure the correct jobdata gets loaded while we resume $this->jobdata_reset(); $this->jobdata_set('job_type', 'restore'); $this->jobdata_set('job_time_ms', $this->job_time_ms); $this->logfile_open($this->nonce); if (!class_exists('Updraft_Restorer')) include_once(UPDRAFTPLUS_DIR.'/restorer.php'); } /** * Analyse a database file and return information about it * * @param Integer $timestamp - the database time in the backup history * @param Array $res - accompanying data. The key 'updraft_encryptionphrase' will be used for decryption if relevant. * @param Boolean|String $db_file - the path to the file to analyse; if not specified (false), then it will be obtained from the backup history * @param Boolean $header_only - whether or not to stop analysis once the header ends * * @return Array - containing arrays for the resulting messages, warnings, errors and meta information */ public function analyse_db_file($timestamp, $res, $db_file = false, $header_only = false) { $mess = array(); $warn = array(); $err = array(); $info = array(); $wp_version = $this->get_wordpress_version(); global $wpdb; $updraft_dir = $this->backups_dir_location(); if (false === $db_file) { // This attempts to raise the maximum packet size. This can't be done within the session, only globally. Therefore, it has to be done before the session starts; in our case, during the pre-analysis. $this->max_packet_size(); $backup = UpdraftPlus_Backup_History::get_history($timestamp); if (!isset($backup['nonce']) || !isset($backup['db'])) return array($mess, $warn, $err, $info); $db_file = is_string($backup['db']) ? $updraft_dir.'/'.$backup['db'] : $updraft_dir.'/'.$backup['db'][0]; } if (!is_readable($db_file)) return array($mess, $warn, $err, $info); // Encrypted - decrypt it if (UpdraftPlus_Encryption::is_file_encrypted($db_file)) { $encryption = empty($res['updraft_encryptionphrase']) ? UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase') : $res['updraft_encryptionphrase']; if (!$encryption) { if (class_exists('UpdraftPlus_Addon_MoreDatabase')) { $err[] = sprintf(__('Error: %s', 'updraftplus'), __('Decryption failed. The database file is encrypted, but you have no encryption key entered.', 'updraftplus')); } else { $err[] = sprintf(__('Error: %s', 'updraftplus'), __('Decryption failed. The database file is encrypted.', 'updraftplus')); } return array($mess, $warn, $err, $info); } $decrypted_file = UpdraftPlus_Encryption::decrypt($db_file, $encryption); if (is_array($decrypted_file)) { $db_file = $decrypted_file['fullpath']; } else { $err[] = __('Decryption failed. The most likely cause is that you used the wrong key.', 'updraftplus'); return array($mess, $warn, $err, $info); } } // Even the empty schema when gzipped comes to 1565 bytes; a blank WP 3.6 install at 5158. But we go low, in case someone wants to share single tables. if (filesize($db_file) < 1000) { $err[] = sprintf(__('The database is too small to be a valid WordPress database (size: %s Kb).', 'updraftplus'), round(filesize($db_file)/1024, 1)); return array($mess, $warn, $err, $info); } $is_plain = ('.gz' == substr($db_file, -3, 3)) ? false : true; $dbhandle = $is_plain ? fopen($db_file, 'r') : UpdraftPlus_Filesystem_Functions::gzopen_for_read($db_file, $warn, $err); if (!is_resource($dbhandle)) { $err[] = __('Failed to open database file.', 'updraftplus'); return array($mess, $warn, $err, $info); } $info['timestamp'] = $timestamp; // Analyse the file, print the results. $line = 0; $old_siteurl = ''; $old_home = ''; $old_table_prefix = ''; $old_siteinfo = array(); $gathering_siteinfo = true; $old_wp_version = ''; $old_php_version = ''; $tables_found = array(); $db_charsets_found = array(); // TODO: If the backup is the right size/checksum, then we could restore the $line <= 100 in the 'while' condition and not bother scanning the whole thing? Or better: sort the core tables to be first so that this usually terminates early $wanted_tables = array('terms', 'term_taxonomy', 'term_relationships', 'commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'users', 'usermeta'); $migration_warning = false; $processing_create = false; $db_version = $wpdb->db_version(); // Don't set too high - we want a timely response returned to the browser // Until April 2015, this was always 90. But we've seen a few people with ~1GB databases (uncompressed), and 90s is not enough. Note that we don't bother checking here if it's compressed - having a too-large timeout when unexpected is harmless, as it won't be hit. On very large dbs, they're expecting it to take a while. // "120 or 240" is a first attempt at something more useful than just fixed at 90 - but should be sufficient (as 90 was for everyone without ~1GB databases) $default_dbscan_timeout = (filesize($db_file) < 31457280) ? 120 : 240; $dbscan_timeout = (defined('UPDRAFTPLUS_DBSCAN_TIMEOUT') && is_numeric(UPDRAFTPLUS_DBSCAN_TIMEOUT)) ? UPDRAFTPLUS_DBSCAN_TIMEOUT : $default_dbscan_timeout; @set_time_limit($dbscan_timeout); // We limit the time that we spend scanning the file for character sets $db_charset_collate_scan_timeout = (defined('UPDRAFTPLUS_DB_CHARSET_COLLATE_SCAN_TIMEOUT') && is_numeric(UPDRAFTPLUS_DB_CHARSET_COLLATE_SCAN_TIMEOUT)) ? UPDRAFTPLUS_DB_CHARSET_COLLATE_SCAN_TIMEOUT : 10; $charset_scan_start_time = microtime(true); $db_supported_character_sets_res = $GLOBALS['wpdb']->get_results('SHOW CHARACTER SET', OBJECT_K); $db_supported_character_sets = (null !== $db_supported_character_sets_res) ? $db_supported_character_sets_res : array(); $db_charsets_found = array(); $db_supported_collations_res = $GLOBALS['wpdb']->get_results('SHOW COLLATION', OBJECT_K); $db_supported_collations = (null !== $db_supported_collations_res) ? $db_supported_collations_res : array(); $db_charsets_found = array(); $db_collates_found = array(); $db_supported_charset_related_to_unsupported_collation = false; $db_supported_charsets_related_to_unsupported_collations = array(); while ((($is_plain && !feof($dbhandle)) || (!$is_plain && !gzeof($dbhandle))) && ($line<100 || (!$header_only && count($wanted_tables)>0) || ((microtime(true) - $charset_scan_start_time) < $db_charset_collate_scan_timeout && !empty($db_supported_character_sets)))) { $line++; // Up to 1MB $buffer = ($is_plain) ? rtrim(fgets($dbhandle, 1048576)) : rtrim(gzgets($dbhandle, 1048576)); // Comments are what we are interested in if (substr($buffer, 0, 1) == '#') { $processing_create = false; if ('' == $old_siteurl && preg_match('/^\# Backup of: (http(.*))$/', $buffer, $matches)) { $old_siteurl = untrailingslashit($matches[1]); $mess[] = __('Backup of:', 'updraftplus').' '.htmlspecialchars($old_siteurl).((!empty($old_wp_version)) ? ' '.sprintf(__('(version: %s)', 'updraftplus'), $old_wp_version) : ''); // Check for should-be migration if (untrailingslashit(site_url()) != $old_siteurl) { if (!$migration_warning) { $migration_warning = true; $info['migration'] = true; // && !class_exists('UpdraftPlus_Addons_Migrator') if (UpdraftPlus_Manipulation_Functions::normalise_url($old_siteurl) == UpdraftPlus_Manipulation_Functions::normalise_url(site_url())) { // Same site migration with only http/https difference $info['same_url'] = false; $old_siteurl_parsed = parse_url($old_siteurl); $actual_siteurl_parsed = parse_url(site_url()); if ((stripos($old_siteurl_parsed['host'], 'www.') === 0 && stripos($actual_siteurl_parsed['host'], 'www.') !== 0) || (stripos($old_siteurl_parsed['host'], 'www.') !== 0 && stripos($actual_siteurl_parsed['host'], 'www.') === 0)) { $powarn = sprintf(__('The website address in the backup set (%s) is slightly different from that of the site now (%s). This is not expected to be a problem for restoring the site, as long as visits to the former address still reach the site.', 'updraftplus'), $old_siteurl, site_url()).' '; } else { $powarn = ''; } if (('https' == $old_siteurl_parsed['scheme'] && 'http' == $actual_siteurl_parsed['scheme']) || ('http' == $old_siteurl_parsed['scheme'] && 'https' == $actual_siteurl_parsed['scheme'])) { $powarn .= sprintf(__('This backup set is of this site, but at the time of the backup you were using %s, whereas the site now uses %s.', 'updraftplus'), $old_siteurl_parsed['scheme'], $actual_siteurl_parsed['scheme']); if ('https' == $old_siteurl_parsed['scheme']) { $powarn .= ' '.apply_filters('updraftplus_https_to_http_additional_warning', sprintf(__('This restoration will work if you still have an SSL certificate (i.e. can use https) to access the site. Otherwise, you will want to use %s to search/replace the site address so that the site can be visited without https.', 'updraftplus'), ''.__('the migrator add-on', 'updraftplus').'')); } else { $powarn .= ' '.apply_filters('updraftplus_http_to_https_additional_warning', sprintf(__('As long as your web hosting allows http (i.e. non-SSL access) or will forward requests to https (which is almost always the case), this is no problem. If that is not yet set up, then you should set it up, or use %s so that the non-https links are automatically replaced.', 'updraftplus'), apply_filters('updraftplus_migrator_addon_link', ''.__('the migrator add-on', 'updraftplus').''))); } } else { $powarn .= apply_filters('updraftplus_dbscan_urlchange_www_append_warning', ''); } $warn[] = $powarn; } else { // For completely different site migration $info['same_url'] = false; $warn[] = apply_filters('updraftplus_dbscan_urlchange', ''.sprintf(__('This backup set is from a different site (%s) - this is not a restoration, but a migration. You need the Migrator add-on in order to make this work.', 'updraftplus'), htmlspecialchars($old_siteurl.' / '.untrailingslashit(site_url()))).'', $old_siteurl, $res); } if (!class_exists('UpdraftPlus_Addons_Migrator')) { $warn[] .= ''.__('You can search and replace your database (for migrating a website to a new location/URL) with the Migrator add-on - follow this link for more information', 'updraftplus').''; } } if ($this->mod_rewrite_unavailable(false)) { $warn[] = sprintf(__('You are using the %s webserver, but do not seem to have the %s module loaded.', 'updraftplus'), 'Apache', 'mod_rewrite').' '.sprintf(__('You should enable %s to make any pretty permalinks (e.g. %s) work', 'updraftplus'), 'mod_rewrite', 'http://example.com/my-page/'); } } else { // For exactly same URL site restoration $info['same_url'] = true; } } elseif ('' == $old_home && preg_match('/^\# Home URL: (http(.*))$/', $buffer, $matches)) { $old_home = untrailingslashit($matches[1]); // Check for should-be migration if (!$migration_warning && UpdraftPlus_Manipulation_Functions::normalise_url(home_url()) != UpdraftPlus_Manipulation_Functions::normalise_url($old_home)) { $migration_warning = true; $powarn = apply_filters('updraftplus_dbscan_urlchange', ''.sprintf(__('This backup set is from a different site (%s) - this is not a restoration, but a migration. You need the Migrator add-on in order to make this work.', 'updraftplus'), htmlspecialchars($old_home.' / '.home_url())).'', $old_home, $res); if (!empty($powarn)) $warn[] = $powarn; } } elseif (!isset($info['created_by_version']) && preg_match('/^\# Created by UpdraftPlus version ([\d\.]+)/', $buffer, $matches)) { $info['created_by_version'] = trim($matches[1]); } elseif ('' == $old_wp_version && preg_match('/^\# WordPress Version: ([0-9]+(\.[0-9]+)+)(-[-a-z0-9]+,)?(.*)$/', $buffer, $matches)) { $old_wp_version = $matches[1]; if (!empty($matches[3])) $old_wp_version .= substr($matches[3], 0, strlen($matches[3])-1); if (version_compare($old_wp_version, $wp_version, '>')) { // $mess[] = sprintf(__('%s version: %s', 'updraftplus'), 'WordPress', $old_wp_version); $warn[] = sprintf(__('You are importing from a newer version of WordPress (%s) into an older one (%s). There are no guarantees that WordPress can handle this.', 'updraftplus'), $old_wp_version, $wp_version); } if (preg_match('/running on PHP ([0-9]+\.[0-9]+)(\s|\.)/', $matches[4], $nmatches) && preg_match('/^([0-9]+\.[0-9]+)(\s|\.)/', PHP_VERSION, $cmatches)) { $old_php_version = $nmatches[1]; $current_php_version = $cmatches[1]; if (version_compare($old_php_version, $current_php_version, '>')) { // $mess[] = sprintf(__('%s version: %s', 'updraftplus'), 'WordPress', $old_wp_version); $warn[] = sprintf(__('The site in this backup was running on a webserver with version %s of %s. ', 'updraftplus'), $old_php_version, 'PHP').' '.sprintf(__('This is significantly newer than the server which you are now restoring onto (version %s).', 'updraftplus'), PHP_VERSION).' '.sprintf(__('You should only proceed if you cannot update the current server and are confident (or willing to risk) that your plugins/themes/etc. are compatible with the older %s version.', 'updraftplus'), 'PHP').' '.sprintf(__('Any support requests to do with %s should be raised with your web hosting company.', 'updraftplus'), 'PHP'); } } } elseif ('' == $old_table_prefix && (preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches) || preg_match('/^-- Table prefix: (\S+)$/i', $buffer, $matches))) { $old_table_prefix = $matches[1]; // echo ''.__('Old table prefix:', 'updraftplus').' '.htmlspecialchars($old_table_prefix).'
'; } elseif (empty($info['label']) && preg_match('/^\# Label: (.*)$/', $buffer, $matches)) { $info['label'] = $matches[1]; $mess[] = __('Backup label:', 'updraftplus').' '.htmlspecialchars($info['label']); } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) { if ('end' == $matches[1]) { $gathering_siteinfo = false; // Sanity checks if (isset($old_siteinfo['multisite']) && !$old_siteinfo['multisite'] && is_multisite()) { // Just need to check that you're crazy // if (!defined('UPDRAFTPLUS_EXPERIMENTAL_IMPORTINTOMULTISITE') || !UPDRAFTPLUS_EXPERIMENTAL_IMPORTINTOMULTISITE) { // $err[] = sprintf(__('Error: %s', 'updraftplus'), __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'updraftplus')); // return array($mess, $warn, $err, $info); // } else { $warn[] = __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'updraftplus').' '.__('It will be imported as a new site.', 'updraftplus').' '.__('Please read this link for important information on this process.', 'updraftplus').''; // } // Got the needed code? if (!class_exists('UpdraftPlusAddOn_MultiSite') || !class_exists('UpdraftPlus_Addons_Migrator')) { $err[] = sprintf(__('Error: %s', 'updraftplus'), sprintf(__('To import an ordinary WordPress site into a multisite installation requires %s.', 'updraftplus'), 'UpdraftPlus Premium')); return array($mess, $warn, $err, $info); } } elseif (isset($old_siteinfo['multisite']) && $old_siteinfo['multisite'] && !is_multisite()) { $warn[] = __('Warning:', 'updraftplus').' '.__('Your backup is of a WordPress multisite install; but this site is not. Only the first site of the network will be accessible.', 'updraftplus').' '.__('If you want to restore a multisite backup, you should first set up your WordPress installation as a multisite.', 'updraftplus').''; } } elseif (preg_match('/^([^=]+)=(.*)$/', $matches[1], $kvmatches)) { $key = $kvmatches[1]; $val = $kvmatches[2]; if ('multisite' == $key) { $info['multisite'] = $val ? true : false; if ($val) $mess[] = ''.__('Site information:', 'updraftplus').' '.'backup is of a WordPress Network'; } $old_siteinfo[$key] = $val; } } elseif (preg_match('/^\# Skipped tables: (.*)$/', $buffer, $matches)) { $skipped_tables = explode(',', $matches[1]); } } elseif (preg_match('#^\s*/\*\!40\d+ SET NAMES (.*)\*\/#i', $buffer, $smatches)) { $db_charsets_found[] = rtrim($smatches[1]); } elseif (preg_match('/^\s*create table \`?([^\`\(]*)\`?\s*\(/i', $buffer, $matches)) { $table = $matches[1]; $tables_found[] = $table; if ($old_table_prefix) { // Remove prefix $table = UpdraftPlus_Manipulation_Functions::str_replace_once($old_table_prefix, '', $table); if (in_array($table, $wanted_tables)) { $wanted_tables = array_diff($wanted_tables, array($table)); } } if (';' != substr($buffer, -1, 1)) { $processing_create = true; $db_supported_charset_related_to_unsupported_collation = true; } } elseif ($processing_create) { if (!empty($db_supported_collations)) { if (preg_match('/ COLLATE=([^\s;]+)/i', $buffer, $collate_match)) { $db_collates_found[] = $collate_match[1]; if (!isset($db_supported_collations[$collate_match[1]])) { $db_supported_charset_related_to_unsupported_collation = true; } } if (preg_match('/ COLLATE ([a-zA-Z0-9._-]+),/i', $buffer, $collate_match)) { $db_collates_found[] = $collate_match[1]; if (!isset($db_supported_collations[$collate_match[1]])) { $db_supported_charset_related_to_unsupported_collation = true; } } if (preg_match('/ COLLATE ([a-zA-Z0-9._-]+) /i', $buffer, $collate_match)) { $db_collates_found[] = $collate_match[1]; if (!isset($db_supported_collations[$collate_match[1]])) { $db_supported_charset_related_to_unsupported_collation = true; } } } if (!empty($db_supported_character_sets)) { if (preg_match('/ CHARSET=([^\s;]+)/i', $buffer, $charset_match)) { $db_charsets_found[] = $charset_match[1]; if ($db_supported_charset_related_to_unsupported_collation && !in_array($charset_match[1], $db_supported_charsets_related_to_unsupported_collations)) { $db_supported_charsets_related_to_unsupported_collations[] = $charset_match[1]; } } } if (';' == substr($buffer, -1, 1)) { $processing_create = false; $db_supported_charset_related_to_unsupported_collation = false; } static $mysql_version_warned = false; if (!$mysql_version_warned && version_compare($db_version, '5.2.0', '<') && preg_match('/(CHARSET|COLLATE)[= ]utf8mb4/', $buffer)) { $mysql_version_warned = true; $err[] = sprintf(__('Error: %s', 'updraftplus'), sprintf(__('The database backup uses MySQL features not available in the old MySQL version (%s) that this site is running on.', 'updraftplus'), $db_version).' '.__('You must upgrade MySQL to be able to use this database.', 'updraftplus')); } } } if ($is_plain) { @fclose($dbhandle); } else { @gzclose($dbhandle); } if (!empty($db_supported_character_sets)) { $db_charsets_found_unique = array_unique($db_charsets_found); $db_unsupported_charset = array(); $db_charset_forbidden = false; foreach ($db_charsets_found_unique as $db_charset) { if (!isset($db_supported_character_sets[$db_charset])) { $db_unsupported_charset[] = $db_charset; $db_charset_forbidden = true; } } if ($db_charset_forbidden) { $db_unsupported_charset_unique = array_unique($db_unsupported_charset); $warn[] = sprintf(_n("The database server that this WordPress site is running on doesn't support the character set (%s) which you are trying to import.", "The database server that this WordPress site is running on doesn't support the character sets (%s) which you are trying to import.", count($db_unsupported_charset_unique), 'updraftplus'), implode(', ', $db_unsupported_charset_unique)).' '.__('You can choose another suitable character set instead and continue with the restoration at your own risk.', 'updraftplus').' '.__('Go here for more information.', 'updraftplus').''.' '.__('Go here for more information.', 'updraftplus').''; $db_supported_character_sets = array_keys($db_supported_character_sets); $similar_type_charset = UpdraftPlus_Manipulation_Functions::get_matching_str_from_array_elems($db_unsupported_charset_unique, $db_supported_character_sets, true); if (empty($similar_type_charset)) { $row = $GLOBALS['wpdb']->get_row('show variables like "character_set_database"'); $similar_type_charset = (null !== $row) ? $row->Value : ''; } if (empty($similar_type_charset) && !empty($db_supported_character_sets[0])) { $similar_type_charset = $db_supported_character_sets[0]; } $charset_select_html = ' '; $charset_select_html .= ''; if (empty($info['addui'])) $info['addui'] = ''; $info['addui'] .= $charset_select_html; } } if (!empty($db_supported_collations)) { $db_collates_found_unique = array_unique($db_collates_found); $db_unsupported_collate = array(); $db_collate_forbidden = false; foreach ($db_collates_found_unique as $db_collate) { if (!isset($db_supported_collations[$db_collate])) { $db_unsupported_collate[] = $db_collate; $db_collate_forbidden = true; } } if ($db_collate_forbidden) { $db_unsupported_collate_unique = array_unique($db_unsupported_collate); $warn[] = sprintf(_n("The database server that this WordPress site is running on doesn't support the collation (%s) used in the database which you are trying to import.", "The database server that this WordPress site is running on doesn't support multiple collations (%s) used in the database which you are trying to import.", count($db_unsupported_collate_unique), 'updraftplus'), implode(', ', $db_unsupported_collate_unique)).' '.__('You can choose another suitable collation instead and continue with the restoration (at your own risk).', 'updraftplus'); $similar_type_collate = ''; if ($db_charset_forbidden && !empty($similar_type_charset)) { $similar_type_collate = $this->get_similar_collate_related_to_charset($db_supported_collations, $db_unsupported_collate_unique, $similar_type_charset); } if (empty($similar_type_collate) && !empty($db_supported_charsets_related_to_unsupported_collations)) { $db_supported_collations_related_to_charset = array(); foreach ($db_supported_collations as $db_supported_collation => $db_supported_collations_info_obj) { if (isset($db_supported_collations_info_obj->Charset) && in_array($db_supported_collations_info_obj->Charset, $db_supported_charsets_related_to_unsupported_collations)) { $db_supported_collations_related_to_charset[] = $db_supported_collation; } } if (!empty($db_supported_collations_related_to_charset)) { $similar_type_collate = UpdraftPlus_Manipulation_Functions::get_matching_str_from_array_elems($db_unsupported_collate_unique, $db_supported_collations_related_to_charset, false); } } if (empty($similar_type_collate)) { $similar_type_collate = $this->get_similar_collate_based_on_ocuurence_count($db_collates_found, $db_supported_collations, $db_supported_charsets_related_to_unsupported_collations); } if (empty($similar_type_collate)) { $similar_type_collate = UpdraftPlus_Manipulation_Functions::get_matching_str_from_array_elems($db_unsupported_collate_unique, array_keys($db_supported_collations), false); } $collate_select_html = ''; $collate_select_html .= ''; $info['addui'] = empty($info['addui']) ? $collate_select_html : $info['addui'].'
'.$collate_select_html; if ($db_charset_forbidden) { $collate_change_on_charset_selection_data = array( 'db_supported_collations' => $db_supported_collations, 'db_unsupported_collate_unique' => $db_unsupported_collate_unique, 'db_collates_found' => $db_collates_found, ); $info['addui'] .= ''; } } } /* $blog_tables = "CREATE TABLE $wpdb->terms ( CREATE TABLE $wpdb->term_taxonomy ( CREATE TABLE $wpdb->term_relationships ( CREATE TABLE $wpdb->commentmeta ( CREATE TABLE $wpdb->comments ( CREATE TABLE $wpdb->links ( CREATE TABLE $wpdb->options ( CREATE TABLE $wpdb->postmeta ( CREATE TABLE $wpdb->posts ( $users_single_table = "CREATE TABLE $wpdb->users ( $users_multi_table = "CREATE TABLE $wpdb->users ( $usermeta_table = "CREATE TABLE $wpdb->usermeta ( $ms_global_tables = "CREATE TABLE $wpdb->blogs ( CREATE TABLE $wpdb->blog_versions ( CREATE TABLE $wpdb->registration_log ( CREATE TABLE $wpdb->site ( CREATE TABLE $wpdb->sitemeta ( CREATE TABLE $wpdb->signups ( */ if (!isset($skipped_tables)) $skipped_tables = array(); $missing_tables = array(); if ($old_table_prefix) { if (!$header_only) { foreach ($wanted_tables as $table) { if (!in_array($old_table_prefix.$table, $tables_found)) { $missing_tables[] = $table; } } foreach ($missing_tables as $key => $value) { if (in_array($old_table_prefix.$value, $skipped_tables)) { unset($missing_tables[$key]); } } if (count($missing_tables)>0) { $warn[] = sprintf(__('This database backup is missing core WordPress tables: %s', 'updraftplus'), implode(', ', $missing_tables)); } if (count($skipped_tables)>0) { $warn[] = sprintf(__('This database backup has the following WordPress tables excluded: %s', 'updraftplus'), implode(', ', $skipped_tables)); } } } else { if (empty($backup['meta_foreign'])) { $warn[] = __('UpdraftPlus was unable to find the table prefix when scanning the database backup.', 'updraftplus'); } } // //need to make sure that we reset the file back to .crypt before clean temp files // $db_file = $decrypted_file['fullpath'].'.crypt'; // unlink($decrypted_file['fullpath']); return array($mess, $warn, $err, $info); } /** * Get the current outgoing IP address. Use this wisely; of course, it's not guaranteed to always be the same. * * @return String|Boolean - returns false upon failure */ public function get_outgoing_ip_address() { $ip_lookup = wp_remote_get('https://ipvigilante.com/json', array('timeout' => 6)); if (200 == wp_remote_retrieve_response_code($ip_lookup)) { $info = json_decode(wp_remote_retrieve_body($ip_lookup), true); if (!empty($info['status']) && !empty($info['data']) && 'success' === $info['status']); if (!empty($info['data']['ipv4'])) return $info['data']['ipv4']; if (!empty($info['data']['ipv6'])) return $info['data']['ipv6']; } return false; } /** * Get default substitute similar collate related to charset * * @param array $db_supported_collations Supported collations. It should contain result of 'SHOW COLLATION' query * @param array $db_unsupported_collate_unique Unsupported unique collates collection * @param string $similar_type_charset Charset for which need to get default collate substitution * @return string $similar_type_collate default substitute collate which is best suitable or blank string */ public function get_similar_collate_related_to_charset($db_supported_collations, $db_unsupported_collate_unique, $similar_type_charset) { $similar_type_collate = ''; $db_supported_collations_related_to_charset = array(); foreach ($db_supported_collations as $db_supported_collation => $db_supported_collations_info_obj) { if (isset($db_supported_collations_info_obj->Charset) && $db_supported_collations_info_obj->Charset == $similar_type_charset) { $db_supported_collations_related_to_charset[] = $db_supported_collation; } } if (!empty($db_supported_collations_related_to_charset)) { $similar_type_collate = UpdraftPlus_Manipulation_Functions::get_matching_str_from_array_elems($db_unsupported_collate_unique, $db_supported_collations_related_to_charset, false); } return $similar_type_collate; } /** * Get default substitute similar collate based on existing supported collates count in database backup file * * @param array $db_collates_found All collates which have found in database backup file regardless whether they are supported or unsupported * @param array $db_supported_collations Supported collations. It should contain result of 'SHOW COLLATION' query * @param array $db_supported_charsets_related_to_unsupported_collations All charset which are related to unsupported collation * * @return string $similar_type_collate default substitute collate which is best suitable or blank string */ public function get_similar_collate_based_on_ocuurence_count($db_collates_found, $db_supported_collations, $db_supported_charsets_related_to_unsupported_collations) { $similar_type_collate = ''; $db_supported_collates_found_with_occurrence = array(); foreach ($db_collates_found as $db_collate_found) { if (isset($db_supported_collations[$db_collate_found])) { if (isset($db_supported_collates_found_with_occurrence[$db_collate_found])) { $db_supported_collates_found_with_occurrence[$db_collate_found] = intval($db_supported_collates_found_with_occurrence[$db_collate_found]) + 1; } else { $db_supported_collates_found_with_occurrence[$db_collate_found] = 1; } } } if (!empty($db_supported_collates_found_with_occurrence)) { arsort($db_supported_collates_found_with_occurrence); if (!empty($db_supported_charsets_related_to_unsupported_collations)) { foreach ($db_supported_collates_found_with_occurrence as $db_supported_collate_with_occurrence => $occurrence_count) { if (isset($db_supported_collations[$db_supported_collate_with_occurrence]) && isset($db_supported_collations[$db_supported_collate_with_occurrence]->Charset) && in_array($db_supported_collations[$db_supported_collate_with_occurrence]->Charset, $db_supported_charsets_related_to_unsupported_collations)) { $similar_type_collate = $db_supported_collate_with_occurrence; break; } } } else { $similar_type_collate = array_search(max($db_supported_collates_found_with_occurrence), $db_supported_collates_found_with_occurrence); } } return $similar_type_collate; } /** * Retrieves current clean url for anchor link where href attribute value is not url (for ex. #div) or empty * * @return String - current clean url */ public static function get_current_clean_url() { // Within an UpdraftCentral context, there should be no prefix on the anchor link if (defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND) return ''; if (defined('DOING_AJAX') && DOING_AJAX) { $current_url = $_SERVER["HTTP_REFERER"]; } else { $url_prefix = is_ssl() ? 'https' : 'http'; $current_url = $url_prefix."://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; } $remove_query_args = array('state', 'action', 'oauth_verifier', 'nonce', 'updraftplus_instance', 'access_token', 'user_id', 'updraftplus_googledriveauth'); return UpdraftPlus_Manipulation_Functions::wp_unslash(remove_query_arg($remove_query_args, $current_url)); } /** * TODO: Remove legacy storage setting keys from here * These are used in 4 places (Feb 2016 - of course, you should re-scan the code to check if relying on this): showing current settings on the debug modal, wiping all current settings, getting a settings bundle to restore when migrating, and for relevant keys in POST-ed data when saving settings over AJAX * * @return Array - the list of keys */ public function get_settings_keys() { // N.B. updraft_backup_history is not included here, as we don't want that wiped return array( 'updraft_autobackup_default', 'updraft_dropbox', 'updraft_googledrive', 'updraftplus_tmp_googledrive_access_token', 'updraftplus_dismissedautobackup', 'dismissed_general_notices_until', 'dismissed_clone_php_notices_until', 'dismissed_clone_wc_notices_until', 'dismissed_season_notices_until', 'updraftplus_dismissedexpiry', 'updraftplus_dismisseddashnotice', 'updraft_interval', 'updraft_interval_increments', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp', 'updraft_backblaze', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_wpcore', 'updraft_include_wpcore_exclude', 'updraft_include_more', 'updraft_include_blogs', 'updraft_include_mu-plugins', 'updraft_auto_updates', 'updraft_include_others_exclude', 'updraft_include_uploads_exclude', 'updraft_lastmessage', 'updraft_googledrive_token', 'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_adminlocking', 'updraft_updraftvault', 'updraft_remotesites', 'updraft_migrator_localkeys', 'updraft_central_localkeys', 'updraft_retain_extrarules', 'updraft_googlecloud', 'updraft_include_more_path', 'updraft_split_every', 'updraft_ssl_nossl', 'updraft_backupdb_nonwp', 'updraft_extradbs', 'updraft_combine_jobs_around', 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_db', 'updraft_startday_files', 'updraft_sftp', 'updraft_s3', 'updraft_s3generic', 'updraft_dreamhost', 'updraft_s3generic_login', 'updraft_s3generic_pass', 'updraft_s3generic_remote_path', 'updraft_s3generic_endpoint', 'updraft_webdav', 'updraft_openstack', 'updraft_onedrive', 'updraft_azure', 'updraft_cloudfiles', 'updraft_cloudfiles_user', 'updraft_cloudfiles_apikey', 'updraft_cloudfiles_path', 'updraft_cloudfiles_authurl', 'updraft_ssl_useservercerts', 'updraft_ssl_disableverify', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dreamobjects_login', 'updraft_dreamobjects_pass', 'updraft_dreamobjects_remote_path', 'updraft_dreamobjects', 'updraft_report_warningsonly', 'updraft_report_wholebackup', 'updraft_report_dbbackup', 'updraft_log_syslog', 'updraft_extradatabases', 'updraftplus_tour_cancelled_on', 'updraftplus_version', ); } /** * A function that works through the array passed to it and gets a list of all the tables from that database and puts the information in an array ready to be parsed and output to html. * * @param Array $dbsinfo an array that contains information about each database, the default 'wp' array is just an empty array, but other entries can be added so that this method can get tables from other databases the array structure for this would be array('wp' => array(), 'TestDB' => array('host' => '', 'user' => '', 'pass' => '', 'name' => '', 'prefix' => '')) * note that the extra tables array key must match the database name in the array note that the extra tables array key must match the database name in the array * @return Array - databases and their table names */ public function get_database_tables($dbsinfo = array('wp' => array())) { global $wpdb; if (!class_exists('UpdraftPlus_Database_Utility')) include_once(UPDRAFTPLUS_DIR.'/includes/class-database-utility.php'); $dbhandle = ''; $db_tables_array = array(); foreach ($dbsinfo as $key => $value) { if ('wp' == $key) { // The table prefix after being filtered - i.e. what filters what we'll actually backup $table_prefix = $this->get_table_prefix(true); // The unfiltered table prefix - i.e. the real prefix that things are relative to $table_prefix_raw = $this->get_table_prefix(false); $dbinfo['host'] = DB_HOST; $dbinfo['name'] = DB_NAME; $dbinfo['user'] = DB_USER; $dbinfo['pass'] = DB_PASSWORD; $dbhandle = $wpdb; } else { $dbhandle = new UpdraftPlus_WPDB_OtherDB_Utility($dbsinfo[$key]['user'], $dbsinfo[$key]['pass'], $dbsinfo[$key]['name'], $dbsinfo[$key]['host']); if (!empty($dbhandle->error)) { return $this->log_wp_error($dbhandle->error); } $table_prefix = $dbsinfo[$key]['prefix']; $table_prefix_raw = $dbsinfo[$key]['prefix']; } // SHOW FULL - so that we get to know whether it's a BASE TABLE or a VIEW $all_tables = $dbhandle->get_results("SHOW FULL TABLES", ARRAY_N); if (empty($all_tables) && !empty($dbhandle->last_error)) { $all_tables = $dbhandle->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' == $key && 0 == count($all_tables)) { return $this->log_wp_error("No tables found in wp database."); die; } // Put the options table first $updraftplus_database_utility = new UpdraftPlus_Database_Utility($key, $table_prefix_raw, $dbhandle); usort($all_tables, array($updraftplus_database_utility, 'backup_db_sorttables')); $all_table_names = array_map(array($this, 'cb_get_name'), $all_tables); $db_tables_array[$key] = $all_table_names; } return $db_tables_array; } /** * 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']; } /** * Retrieves the appropriate URL for the given target page * * @internal * @param String $which_page The target page * @return String - The requested URL for a given page */ public function get_url($which_page = false) { switch ($which_page) { case 'my-account': return apply_filters('updraftplus_com_myaccount', 'https://updraftplus.com/my-account/'); break; case 'shop': return apply_filters('updraftplus_com_shop', 'https://updraftplus.com/shop/'); break; case 'premium': return apply_filters('updraftplus_com_premium', 'https://updraftplus.com/shop/updraftplus-premium/'); break; case 'buy-tokens': return apply_filters('updraftplus_com_updraftclone_tokens', 'https://updraftplus.com/shop/updraftclone-tokens/'); break; case 'lost-password': return apply_filters('updraftplus_com_myaccount_lostpassword', 'https://updraftplus.com/my-account/lost-password/'); break; case 'mothership': return apply_filters('updraftplus_com_mothership', 'https://updraftplus.com/plugin-info'); break; case 'shop_premium': return apply_filters('updraftplus_com_shop_premium', 'https://updraftplus.com/shop/updraftplus-premium/'); break; case 'shop_vault_5': return apply_filters('updraftplus_com_shop_vault_5', 'https://updraftplus.com/shop/updraftplus-vault-storage-5-gb/'); break; case 'shop_vault_15': return apply_filters('updraftplus_com_shop_vault_15', 'https://updraftplus.com/shop/updraftplus-vault-storage-15-gb/'); break; case 'shop_vault_50': return apply_filters('updraftplus_com_shop_vault_50', 'https://updraftplus.com/shop/updraftplus-vault-storage-50-gb/'); break; default: return 'URL not found ('.$which_page.')'; } } /** * Get log message for permission failure * * @param string $path full path of file or folder * @param string $log_message_prefix action which is performed to path * @param string $directory_prefix_in_log_message Directory Prefix. It should be either "Parent" or "Destination" * @return string|boolean log message (HTML). If posix function doesn't exist, It returns false */ public function log_permission_failure_message($path, $log_message_prefix, $directory_prefix_in_log_message = 'Parent') { if ($this->do_posix_functions_exist()) { $stat_data = stat($path); $log_message = $log_message_prefix.': Failed. '; $log_message .= $directory_prefix_in_log_message.' Directory UID='.$stat_data['uid'].', GID='.$stat_data['gid'].'. '; $log_message .= $this->get_log_message_for_current_uid_and_gid(); return $log_message; } else { return false; } } /** * Get log message for current uid and gid * * @return String log message of current process (HTML) */ private function get_log_message_for_current_uid_and_gid() { $log_message = 'Effective/real user IDs of the current process: '.posix_geteuid().'/'.posix_getuid().'. '; $log_message .= 'Effective/real group IDs of the current process: '.posix_getegid().'/'.posix_getgid().'. '; return $log_message; } /** * Checks whether POSIX functions exists or not * * @return boolean true if POSIX functions exists or not */ private function do_posix_functions_exist() { return function_exists('posix_geteuid') && function_exists('posix_getuid') && function_exists('posix_getegid') && function_exists('posix_getgid'); } /** * Checks whether debug mode is on or not. If it is on then unminified script will be used. * * @return boolean true indicate use the unminified script */ public function use_unminified_scripts() { return UpdraftPlus_Options::get_updraft_option('updraft_debug_mode') || (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG); } }