| 1 |
<?php |
| 2 |
/* |
| 3 |
Plugin Name: UpdraftPlus - Backup/Restore |
| 4 |
Plugin URI: http://updraftplus.com |
| 5 |
Description: Backup and restore: take backups locally, or backup to Amazon S3, Dropbox, Google Drive, Rackspace, (S)FTP, WebDAV & email, on automatic schedules. |
| 6 |
Author: UpdraftPlus.Com, DavidAnderson |
| 7 |
Version: 1.5.20 |
| 8 |
Donate link: http://david.dw-perspective.org.uk/donate |
| 9 |
License: GPLv3 or later |
| 10 |
Text Domain: updraftplus |
| 11 |
Author URI: http://updraftplus.com |
| 12 |
*/ |
| 13 |
|
| 14 |
/* |
| 15 |
TODO - some of these are out of date/done, needs pruning |
| 16 |
// Add an appeal for translators to email me. If it a fails, use Google Translate Tools and appeal for native users to correct it. |
| 17 |
// Automatically detect LiteSpeed and scan .htaccess and send them to the relevant FAQ |
| 18 |
// Search for other TODO-s in the code |
| 19 |
// Test in PHP 5.4 |
| 20 |
// Better Dropbox errors (see item in To-Do box) |
| 21 |
// Count available time before doing a database restore |
| 22 |
// Add in downloading in the 'Restore' modal, and remove the advice to do so manually. |
| 23 |
// Provide an expert option to disable sslverify on WP HTTP requests. Mention in FAQs etc. |
| 24 |
// Save database encryption key inside backup history on per-db basis, so that if it changes we can still decrypt |
| 25 |
// Switch to Google Drive SDK. Google folders. https://developers.google.com/drive/folder |
| 26 |
// GlotPress |
| 27 |
// Convert S3.php to use WP's native HTTP functions |
| 28 |
// Ability to re-scan existing cloud storage |
| 29 |
// Migrator - search+replace the database |
| 30 |
// Make mcrypt warning on dropbox more prominent - one customer missed it |
| 31 |
// Store meta-data on which version of UD the backup was made with (will help if we ever introduce quirks that need ironing) |
| 32 |
// Test restoration when uploads dir is /assets/ (e.g. with Shoestrap theme) |
| 33 |
// Send the user an email upon their first backup with tips on what to do (e.g. support/improve) (include legacy check to not bug existing users) |
| 34 |
//Allow use of /usr/bin/zip - since this can escape from PHP's memory limit. Can still batch as we do so, in order to monitor/measure progress |
| 35 |
// Database decrypter |
| 36 |
// Easier download of 'in progress' backup logs (not just last completed) |
| 37 |
//Do an automated test periodically for the success of loop-back connections |
| 38 |
//When a manual backup is run, use a timer to update the 'Download backups and logs' section, just like 'Last finished backup run'. Beware of over-writing anything that's in there from a resumable downloader. |
| 39 |
//Change DB encryption to not require whole gzip in memory (twice) |
| 40 |
//Add Rackspace, Box.Net, SugarSync and Microsoft Skydrive support?? |
| 41 |
//Make it easier to find add-ons |
| 42 |
//The restorer has a hard-coded wp-content - fix |
| 43 |
//?? On 'backup now', open up a Lightbox, count down 5 seconds, then start examining the log file (if it can be found) |
| 44 |
//Should make clear in dashboard what is a non-fatal error (i.e. can be retried) - leads to unnecessary bug reports |
| 45 |
// Move the inclusion, cloud and retention data into the backup job (i.e. don't read current config, make it an attribute of each job). In fact, everything should be. So audit all code for where get_option is called inside a backup run: it shouldn't happen. |
| 46 |
// Should we resume if the only errors were upon deletion (i.e. the backup itself was fine?) Presently we do, but it displays errors for the user to confuse them. Perhaps better to make pruning a separate scheuled task?? |
| 47 |
// Warn the user if their zip-file creation is slooowww... |
| 48 |
// Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests... |
| 49 |
// Chunking + resuming on SFTP |
| 50 |
// Add-on to manage all your backups from a single dashboard |
| 51 |
// Make disk space check more intelligent (currently hard-coded at 35Mb) |
| 52 |
// Provide backup/restoration for UpdraftPlus's settings, to allow 'bootstrap' on a fresh WP install - some kind of single-use code which a remote UpdraftPlus can use to authenticate |
| 53 |
// Multiple jobs |
| 54 |
// Allow connecting to remote storage, scanning + populating backup history from it |
| 55 |
// GoogleDrive in-dashboard download resumption loads the whole archive into memory - should instead either chunk or directly stream fo the file handle |
| 56 |
// Multisite add-on should allow restoring of each blog individually |
| 57 |
// When looking for files to delete, is the current encryption setting used? Should not be. |
| 58 |
// Create single zip, containing even WordPress itself |
| 59 |
// When a new backup starts, AJAX-update the 'Last backup' display in the admin page. |
| 60 |
// Remove the recurrence of admin notices when settings are saved due to _wp_referer |
| 61 |
|
| 62 |
Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed |
| 63 |
// New sub-module to verify that the backups are there, independently of backup thread |
| 64 |
*/ |
| 65 |
|
| 66 |
/* Portions copyright 2010 Paul Kehrer |
| 67 |
Portions copyright 2011-13 David Anderson |
| 68 |
Other portions copyright as indicated authors in the relevant files |
| 69 |
|
| 70 |
This program is free software; you can redistribute it and/or modify |
| 71 |
it under the terms of the GNU General Public License as published by |
| 72 |
the Free Software Foundation; either version 3 of the License, or |
| 73 |
(at your option) any later version. |
| 74 |
|
| 75 |
This program is distributed in the hope that it will be useful, |
| 76 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 77 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 78 |
GNU General Public License for more details. |
| 79 |
|
| 80 |
You should have received a copy of the GNU General Public License |
| 81 |
along with this program; if not, write to the Free Software |
| 82 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 83 |
*/ |
| 84 |
|
| 85 |
define('UPDRAFTPLUS_DIR', dirname(__FILE__)); |
| 86 |
define('UPDRAFTPLUS_URL', plugins_url('', __FILE__)); |
| 87 |
define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php,backup,backups'); |
| 88 |
// This is used in various places, based on our assumption of the maximum time any job should take. May need lengthening in future if we get reports which show enormous sets hitting the limit. |
| 89 |
// Also one section requires at least 1% progress each run, so on a 5-minute schedule, that equals just under 9 hours - then an extra allowance takes it just over |
| 90 |
define('UPDRAFT_TRANSTIME', 3600*9+5); |
| 91 |
|
| 92 |
// Load add-ons |
| 93 |
if (is_file(UPDRAFTPLUS_DIR.'/premium.php')) require_once(UPDRAFTPLUS_DIR.'/premium.php'); |
| 94 |
if (is_file(UPDRAFTPLUS_DIR.'/autoload.php')) require_once(UPDRAFTPLUS_DIR.'/autoload.php'); |
| 95 |
|
| 96 |
if ($dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) { |
| 97 |
while ($e = readdir($dir_handle)) { |
| 98 |
if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e) && preg_match('/\.php$/', $e)) { |
| 99 |
include_once(UPDRAFTPLUS_DIR.'/addons/'.$e); |
| 100 |
} |
| 101 |
} |
| 102 |
@closedir($dir_handle); |
| 103 |
} |
| 104 |
|
| 105 |
if (!isset($updraftplus)) $updraftplus = new UpdraftPlus(); |
| 106 |
|
| 107 |
if (!$updraftplus->memory_check(192)) { |
| 108 |
// Experience appears to show that the memory limit is only likely to be hit (unless it is very low) by single files that are larger than available memory (when compressed) |
| 109 |
@ini_set('memory_limit', WP_MAX_MEMORY_LIMIT); //up the memory limit to the maximum WordPress is allowing for large backup files |
| 110 |
} |
| 111 |
|
| 112 |
if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options.php'); |
| 113 |
|
| 114 |
set_include_path(get_include_path().PATH_SEPARATOR.UPDRAFTPLUS_DIR.'/includes/phpseclib'); |
| 115 |
|
| 116 |
class UpdraftPlus { |
| 117 |
|
| 118 |
var $version; |
| 119 |
|
| 120 |
var $plugin_title = 'UpdraftPlus Backup/Restore'; |
| 121 |
|
| 122 |
// Choices will be shown in the admin menu in the order used here |
| 123 |
var $backup_methods = array ( |
| 124 |
"s3" => "Amazon S3", |
| 125 |
"dropbox" => "Dropbox", |
| 126 |
"googledrive" => "Google Drive", |
| 127 |
"ftp" => "FTP", |
| 128 |
'sftp' => 'SFTP', |
| 129 |
'webdav' => 'WebDAV', |
| 130 |
"email" => "Email" |
| 131 |
); |
| 132 |
|
| 133 |
var $dbhandle; |
| 134 |
var $dbhandle_isgz; |
| 135 |
var $errors = array(); |
| 136 |
var $nonce; |
| 137 |
var $logfile_name = ""; |
| 138 |
var $logfile_handle = false; |
| 139 |
var $backup_time; |
| 140 |
|
| 141 |
var $opened_log_time; |
| 142 |
var $backup_dir; |
| 143 |
|
| 144 |
var $jobdata; |
| 145 |
|
| 146 |
// Used to schedule resumption attempts beyond the tenth, if needed |
| 147 |
var $current_resumption; |
| 148 |
var $newresumption_scheduled = false; |
| 149 |
|
| 150 |
function __construct() { |
| 151 |
|
| 152 |
// Initialisation actions - takes place on plugin load |
| 153 |
|
| 154 |
if ($fp = fopen( __FILE__, 'r')) { |
| 155 |
$file_data = fread( $fp, 1024 ); |
| 156 |
if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) { |
| 157 |
$this->version = $matches[1]; |
| 158 |
} |
| 159 |
fclose($fp); |
| 160 |
} |
| 161 |
|
| 162 |
# Create admin page |
| 163 |
add_action('init', array($this, 'handle_url_actions')); |
| 164 |
// Run earlier than default - hence earlier than other components |
| 165 |
add_action('admin_init', array($this,'admin_init'), 9); |
| 166 |
// admin_menu runs earlier, and we need it because options.php wants to use $updraftplus_admin before admin_init happens |
| 167 |
add_action('admin_menu', array($this,'admin_init'), 9); |
| 168 |
add_action('updraft_backup', array($this,'backup_files')); |
| 169 |
add_action('updraft_backup_database', array($this,'backup_database')); |
| 170 |
# backup_all is used by the manual "Backup Now" button |
| 171 |
add_action('updraft_backup_all', array($this,'backup_all')); |
| 172 |
# this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc. |
| 173 |
add_action('updraft_backup_resume', array($this,'backup_resume'), 10, 3); |
| 174 |
# http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules |
| 175 |
add_filter('cron_schedules', array($this,'modify_cron_schedules')); |
| 176 |
add_action('plugins_loaded', array($this, 'load_translations')); |
| 177 |
|
| 178 |
register_deactivation_hook(__FILE__, array($this, 'deactivation')); |
| 179 |
|
| 180 |
} |
| 181 |
|
| 182 |
function admin_init() { |
| 183 |
// We are in the admin area: now load all that code |
| 184 |
global $updraftplus_admin; |
| 185 |
if (empty($updraftplus_admin)) require_once(UPDRAFTPLUS_DIR.'/admin.php'); |
| 186 |
|
| 187 |
if (isset($_GET['wpnonce']) && isset($_GET['page']) && isset($_GET['action']) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadlatestmodlog' && wp_verify_nonce($_GET['wpnonce'], 'updraftplus_download')) { |
| 188 |
|
| 189 |
$updraft_dir = $this->backups_dir_location(); |
| 190 |
|
| 191 |
$log_file = ''; |
| 192 |
$mod_time = 0; |
| 193 |
|
| 194 |
if ($handle = opendir($updraft_dir)) { |
| 195 |
while (false !== ($entry = readdir($handle))) { |
| 196 |
// The latter match is for files created internally by zipArchive::addFile |
| 197 |
if (preg_match('/^log\.[a-z0-9]+\.txt$/i', $entry)) { |
| 198 |
$mtime = filemtime($updraft_dir.'/'.$entry); |
| 199 |
if ($mtime > $mod_time) { |
| 200 |
$mod_time = $mtime; |
| 201 |
$log_file = $updraft_dir.'/'.$entry; |
| 202 |
} |
| 203 |
} |
| 204 |
} |
| 205 |
@closedir($handle); |
| 206 |
} |
| 207 |
|
| 208 |
if ($mod_time >0) { |
| 209 |
if (is_readable($log_file)) { |
| 210 |
header('Content-type: text/plain'); |
| 211 |
readfile($log_file); |
| 212 |
exit; |
| 213 |
} else { |
| 214 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') ); |
| 215 |
} |
| 216 |
} else { |
| 217 |
add_action('admin_notices', array($this,'show_admin_warning_nolog') ); |
| 218 |
} |
| 219 |
} |
| 220 |
|
| 221 |
} |
| 222 |
|
| 223 |
function add_curl_capath($handle) { |
| 224 |
if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) curl_setopt($handle, CURLOPT_CAINFO, UPDRAFTPLUS_DIR.'/includes/cacert.pem' ); |
| 225 |
} |
| 226 |
|
| 227 |
// Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?page=updraftplus&action=updraftmethod-googledrive-auth |
| 228 |
// Also handle action=downloadlog |
| 229 |
function handle_url_actions() { |
| 230 |
|
| 231 |
// First, basic security check: must be an admin page, with ability to manage options, with the right parameters |
| 232 |
if ( UpdraftPlus_Options::user_can_manage() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) { |
| 233 |
if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php')) { |
| 234 |
$method = $matches[1]; |
| 235 |
require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php'); |
| 236 |
$call_class = "UpdraftPlus_BackupModule_".$method; |
| 237 |
$call_method = "action_".$matches[2]; |
| 238 |
|
| 239 |
add_action('http_api_curl', array($this, 'add_curl_capath')); |
| 240 |
if (method_exists($call_class, $call_method)) call_user_func(array($call_class,$call_method)); |
| 241 |
remove_action('http_api_curl', array($this, 'add_curl_capath')); |
| 242 |
|
| 243 |
} elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) { |
| 244 |
// No WordPress nonce is needed here or for the next, since the backup is already nonce-based |
| 245 |
$updraft_dir = $this->backups_dir_location(); |
| 246 |
$log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt'; |
| 247 |
if (is_readable($log_file)) { |
| 248 |
header('Content-type: text/plain'); |
| 249 |
readfile($log_file); |
| 250 |
exit; |
| 251 |
} else { |
| 252 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') ); |
| 253 |
} |
| 254 |
} elseif ($_GET['action'] == 'downloadfile' && isset($_GET['updraftplus_file']) && preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-[\-a-z]+\.(gz\.crypt)$/i', $_GET['updraftplus_file'])) { |
| 255 |
$updraft_dir = $this->backups_dir_location(); |
| 256 |
$spool_file = $updraft_dir.'/'.$_GET['updraftplus_file']; |
| 257 |
if (is_readable($spool_file)) { |
| 258 |
$dkey = (isset($_GET['decrypt_key'])) ? $_GET['decrypt_key'] : ""; |
| 259 |
$this->spool_file('db', $spool_file, $dkey); |
| 260 |
exit; |
| 261 |
} else { |
| 262 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablefile') ); |
| 263 |
} |
| 264 |
} |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
function show_admin_warning_unreadablelog() { |
| 269 |
global $updraftplus_admin; |
| 270 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The log file could not be read.','updraftplus')); |
| 271 |
} |
| 272 |
|
| 273 |
function show_admin_warning_nolog() { |
| 274 |
global $updraftplus_admin; |
| 275 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('No log files were found.','updraftplus')); |
| 276 |
} |
| 277 |
|
| 278 |
function show_admin_warning_unreadablefile() { |
| 279 |
global $updraftplus_admin; |
| 280 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The given file could not be read.','updraftplus')); |
| 281 |
} |
| 282 |
|
| 283 |
function load_translations() { |
| 284 |
// Tell WordPress where to find the translations |
| 285 |
load_plugin_textdomain('updraftplus', false, basename(dirname(__FILE__)).'/languages'); |
| 286 |
} |
| 287 |
|
| 288 |
// Cleans up temporary files found in the updraft directory |
| 289 |
function clean_temporary_files() { |
| 290 |
$updraft_dir = $this->backups_dir_location(); |
| 291 |
if ($handle = opendir($updraft_dir)) { |
| 292 |
$now_time=time(); |
| 293 |
while (false !== ($entry = readdir($handle))) { |
| 294 |
// The latter match is for files created internally by zipArchive::addFile |
| 295 |
if ((preg_match('/\.tmp(\.gz)?$/i', $entry) || preg_match('/\.zip\.tmp\.([A-Za-z0-9]){6}?$/i', $entry)) && is_file($updraft_dir.'/'.$entry) && $now_time-filemtime($updraft_dir.'/'.$entry)>86400) { |
| 296 |
$this->log("Deleting old temporary file: $entry"); |
| 297 |
@unlink($updraft_dir.'/'.$entry); |
| 298 |
} |
| 299 |
} |
| 300 |
@closedir($handle); |
| 301 |
} |
| 302 |
} |
| 303 |
|
| 304 |
function backup_time_nonce() { |
| 305 |
$this->backup_time = time(); |
| 306 |
$nonce = substr(md5(time().rand()), 20); |
| 307 |
$this->nonce = $nonce; |
| 308 |
} |
| 309 |
|
| 310 |
function logfile_open($nonce) { |
| 311 |
|
| 312 |
//set log file name and open log file |
| 313 |
$updraft_dir = $this->backups_dir_location(); |
| 314 |
$this->logfile_name = $updraft_dir. "/log.$nonce.txt"; |
| 315 |
|
| 316 |
// Use append mode in case it already exists |
| 317 |
$this->logfile_handle = fopen($this->logfile_name, 'a'); |
| 318 |
$this->opened_log_time = microtime(true); |
| 319 |
$this->log('Opened log file at time: '.date('r')); |
| 320 |
global $wp_version; |
| 321 |
$logline = "UpdraftPlus: ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".php_uname().") Server: ".$_SERVER["SERVER_SOFTWARE"]." max_execution_time: ".@ini_get("max_execution_time")." memory_limit: ".ini_get('memory_limit')." ZipArchive::addFile : "; |
| 322 |
|
| 323 |
// method_exists causes some faulty PHP installations to segfault, leading to support requests |
| 324 |
if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) { |
| 325 |
$logline .= 'Y'; |
| 326 |
} else { |
| 327 |
$logline .= (method_exists('ZipArchive', 'addFile')) ? "Y" : "N"; |
| 328 |
} |
| 329 |
$this->log($logline); |
| 330 |
$disk_free_space = @disk_free_space($updraft_dir); |
| 331 |
$this->log("Free space on disk containing Updraft's temporary directory: ".round($disk_free_space/1048576,1)." Mb"); |
| 332 |
} |
| 333 |
|
| 334 |
# Logs the given line, adding (relative) time stamp and newline |
| 335 |
function log($line) { |
| 336 |
if ($this->logfile_handle) fwrite($this->logfile_handle, sprintf("%08.03f", round(microtime(true)-$this->opened_log_time, 3))." (".$this->current_resumption.") $line\n"); |
| 337 |
if ('download' == $this->jobdata_get('job_type')) { |
| 338 |
// Download messages are keyed on the job (since they could be running several), and transient |
| 339 |
// The values of the POST array were checked before |
| 340 |
set_transient('ud_dlmess_'.$_POST['timestamp'].'_'.$_POST['type'], $line." (".date('M d H:i:s').")", 3600); |
| 341 |
} else { |
| 342 |
UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date('M d H:i:s').")"); |
| 343 |
} |
| 344 |
if (defined('UPDRAFTPLUS_CONSOLELOG')) print $line."\n"; |
| 345 |
} |
| 346 |
|
| 347 |
// This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile |
| 348 |
function record_uploaded_chunk($percent, $extra, $file_path = false) { |
| 349 |
|
| 350 |
// Touch the original file, which helps prevent overlapping runs |
| 351 |
if ($file_path) touch($file_path); |
| 352 |
|
| 353 |
// Log it |
| 354 |
$service = $this->jobdata_get('service'); |
| 355 |
$log = ucfirst($service)." chunked upload: $percent % uploaded"; |
| 356 |
if ($extra) $log .= " ($extra)"; |
| 357 |
$this->log($log); |
| 358 |
// If we are on an 'overtime' resumption run, and we are still meainingfully uploading, then schedule a new resumption |
| 359 |
// Our definition of meaningful is that we must maintain an overall average of at least 1% per run, after allowing 9 runs for everything else to get going |
| 360 |
// i.e. Max 109 runs = 545 minutes = 9 hrs 05 |
| 361 |
// If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 87Kb/s upload speed required |
| 362 |
|
| 363 |
if ($percent > ( $this->current_resumption - 9)) { |
| 364 |
$this->something_useful_happened(); |
| 365 |
} |
| 366 |
} |
| 367 |
|
| 368 |
function something_useful_happened() { |
| 369 |
|
| 370 |
// First, update the record of maximum detected runtime on each run |
| 371 |
$time_passed = $this->jobdata_get('run_times'); |
| 372 |
if (!is_array($time_passed)) $time_passed = array(); |
| 373 |
$time_passed[$this->current_resumption] = microtime(true)-$this->opened_log_time; |
| 374 |
$this->jobdata_set('run_times', $time_passed); |
| 375 |
|
| 376 |
if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false) { |
| 377 |
$this->log("This is resumption ".$this->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled"); |
| 378 |
$resume_interval = $this->jobdata_get('resume_interval'); |
| 379 |
if (!is_numeric($resume_interval) || $resume_interval<$this->minimum_resume_interval()) { $resume_interval = $this->minimum_resume_interval(); } |
| 380 |
$schedule_for = time()+$resume_interval; |
| 381 |
$this->newresumption_scheduled = $schedule_for; |
| 382 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce)); |
| 383 |
} else { |
| 384 |
$this->reschedule_if_needed(); |
| 385 |
} |
| 386 |
} |
| 387 |
|
| 388 |
function minimum_resume_interval() { |
| 389 |
// Bringing this down brings in more risk of undetectable overlaps than is worth it |
| 390 |
return 300; |
| 391 |
// $inter = (int)ini_get('max_execution_time'); |
| 392 |
// if (!$inter || $inter>300) $inter = 300; |
| 393 |
// if ($inter<35) $inter=35; |
| 394 |
// return $inter; |
| 395 |
} |
| 396 |
|
| 397 |
// This important function returns a list of file entities that can potentially be backed up (subject to users settings), and optionally further meta-data about them |
| 398 |
function get_backupable_file_entities($include_others = true, $full_info = false) { |
| 399 |
|
| 400 |
$wp_upload_dir = wp_upload_dir(); |
| 401 |
|
| 402 |
if ($full_info) { |
| 403 |
$arr = array( |
| 404 |
'plugins' => array('path' => WP_PLUGIN_DIR, 'description' => __('Plugins','updraftplus')), |
| 405 |
'themes' => array('path' => WP_CONTENT_DIR.'/themes', 'description' => __('Themes','updraftplus')), |
| 406 |
'uploads' => array('path' => $wp_upload_dir['basedir'], 'description' => __('Uploads','updraftplus')) |
| 407 |
); |
| 408 |
} else { |
| 409 |
$arr = array( |
| 410 |
'plugins' => WP_PLUGIN_DIR, |
| 411 |
'themes' => WP_CONTENT_DIR.'/themes', |
| 412 |
'uploads' => $wp_upload_dir['basedir'] |
| 413 |
); |
| 414 |
} |
| 415 |
|
| 416 |
$arr = apply_filters('updraft_backupable_file_entities', $arr, $full_info); |
| 417 |
|
| 418 |
// We always then add 'others' on to the end |
| 419 |
if ($include_others) { |
| 420 |
if ($full_info) { |
| 421 |
$arr['others'] = array('path' => WP_CONTENT_DIR, 'description' => __('Others','updraftplus')); |
| 422 |
} else { |
| 423 |
$arr['others'] = WP_CONTENT_DIR; |
| 424 |
} |
| 425 |
} |
| 426 |
|
| 427 |
return $arr; |
| 428 |
|
| 429 |
} |
| 430 |
|
| 431 |
function backup_resume($resumption_no, $bnonce) { |
| 432 |
|
| 433 |
// 15 minutes |
| 434 |
@set_time_limit(900); |
| 435 |
|
| 436 |
@ignore_user_abort(true); |
| 437 |
// This is scheduled for 5 minutes after a backup job starts |
| 438 |
|
| 439 |
// Restore state |
| 440 |
$resumption_extralog = ''; |
| 441 |
if ($resumption_no > 0) { |
| 442 |
$this->nonce = $bnonce; |
| 443 |
$this->backup_time = $this->jobdata_get('backup_time'); |
| 444 |
$this->logfile_open($bnonce); |
| 445 |
|
| 446 |
$time_passed = $this->jobdata_get('run_times'); |
| 447 |
if (!is_array($time_passed)) $time_passed = array(); |
| 448 |
|
| 449 |
$prev_resumption = $resumption_no - 1; |
| 450 |
if (isset($time_passed[$prev_resumption])) $resumption_extralog = ", previous check-in=".round($time_passed[$prev_resumption], 1)."s"; |
| 451 |
|
| 452 |
} |
| 453 |
|
| 454 |
$btime = $this->backup_time; |
| 455 |
|
| 456 |
$job_type = $this->jobdata_get('job_type'); |
| 457 |
|
| 458 |
$updraft_dir = $this->backups_dir_location(); |
| 459 |
|
| 460 |
$time_ago = time()-$btime; |
| 461 |
|
| 462 |
$this->current_resumption = $resumption_no; |
| 463 |
$this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime (${time_ago}s ago), job type=$job_type".$resumption_extralog); |
| 464 |
|
| 465 |
// Schedule again, to run in 5 minutes again, in case we again fail |
| 466 |
// The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping |
| 467 |
$resume_interval = $this->jobdata_get('resume_interval'); |
| 468 |
if (!is_numeric($resume_interval) || $resume_interval<$this->minimum_resume_interval()) $resume_interval = $this->minimum_resume_interval(); |
| 469 |
|
| 470 |
// We just do this once, as we don't want to be in permanent conflict with the overlap detector |
| 471 |
if ($resumption_no == 8) { |
| 472 |
$max_time = 0; |
| 473 |
// $time_passed is set earlier |
| 474 |
$timings_string = ""; |
| 475 |
$run_times_known=0; |
| 476 |
for ($i=0; $i<=7; $i++) { |
| 477 |
$timings_string .= "$i:"; |
| 478 |
if (isset($time_passed[$i])) { |
| 479 |
$timings_string .= round($time_passed[$i], 1).' '; |
| 480 |
$run_times_known++; |
| 481 |
if ($time_passed[$i] > $max_time) $max_time = round($time_passed[$i]); |
| 482 |
} else { |
| 483 |
$timings_string .= '? '; |
| 484 |
} |
| 485 |
} |
| 486 |
$this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time)"); |
| 487 |
if ($run_times_known >= 6 && ($max_time + 35 < $resume_interval)) { |
| 488 |
$resume_interval = round($max_time + 35); |
| 489 |
$this->log("Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds"); |
| 490 |
$this->jobdata_set('resume_interval', $resume_interval); |
| 491 |
} |
| 492 |
} |
| 493 |
|
| 494 |
// A different argument than before is needed otherwise the event is ignored |
| 495 |
$next_resumption = $resumption_no+1; |
| 496 |
if ($next_resumption < 10) { |
| 497 |
$schedule_for = time()+$resume_interval; |
| 498 |
$this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted"); |
| 499 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce)); |
| 500 |
$this->newresumption_scheduled = $schedule_for; |
| 501 |
} else { |
| 502 |
$this->log(sprintf('The current run is resumption number %d - will not schedule a further attempt until we see something useful happening', $resumption_no)); |
| 503 |
} |
| 504 |
|
| 505 |
// Sanity check |
| 506 |
if (empty($this->backup_time)) { |
| 507 |
$this->log('Abort this run: the backup_time parameter appears to be empty (this is usually caused by resuming an already-complete backup, or by your site having a faulty object cache active (e.g. W3 Total Cache\'s object cache))'); |
| 508 |
return false; |
| 509 |
} |
| 510 |
|
| 511 |
// This should be always called; if there were no files in this run, it returns us an empty array |
| 512 |
$backup_array = $this->resumable_backup_of_files($resumption_no); |
| 513 |
|
| 514 |
// This save, if there was something, is then immediately picked up again |
| 515 |
if (is_array($backup_array)) { |
| 516 |
$this->log('Saving backup status to database (elements: '.count($backup_array).")"); |
| 517 |
$this->save_backup_history($backup_array); |
| 518 |
} |
| 519 |
|
| 520 |
// Switch of variable name is purely vestigial |
| 521 |
$our_files = $backup_array; |
| 522 |
if (!is_array($our_files)) $our_files = array(); |
| 523 |
|
| 524 |
$undone_files = array(); |
| 525 |
|
| 526 |
$backup_database = $this->jobdata_get('backup_database'); |
| 527 |
|
| 528 |
// The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed. |
| 529 |
if ($backup_database == "begun" || $backup_database == 'finished' || $backup_database == 'encrypted') { |
| 530 |
if ($backup_database == "begun") { |
| 531 |
if ($resumption_no > 0) { |
| 532 |
$this->log("Resuming creation of database dump"); |
| 533 |
} else { |
| 534 |
$this->log("Beginning creation of database dump"); |
| 535 |
} |
| 536 |
} elseif ($backup_database == 'encrypted') { |
| 537 |
$this->log("Database dump: Creation and encryption were completed already"); |
| 538 |
} else { |
| 539 |
$this->log("Database dump: Creation was completed already"); |
| 540 |
} |
| 541 |
|
| 542 |
$db_backup = $this->backup_db($backup_database); |
| 543 |
|
| 544 |
if(is_array($our_files) && is_string($db_backup)) { |
| 545 |
$our_files['db'] = $db_backup; |
| 546 |
} |
| 547 |
|
| 548 |
if ($backup_database != 'encrypted') $this->jobdata_set("backup_database", 'finished'); |
| 549 |
} else { |
| 550 |
$this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)"); |
| 551 |
} |
| 552 |
|
| 553 |
// Save this to our history so we can track backups for the retain feature |
| 554 |
$this->log("Saving backup history"); |
| 555 |
// This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known. |
| 556 |
$this->save_backup_history($our_files); |
| 557 |
|
| 558 |
// Potentially encrypt the database if it is not already |
| 559 |
if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) { |
| 560 |
$our_files['db'] = $this->encrypt_file($our_files['db']); |
| 561 |
// No need to save backup history now, as it will happen in a few lines time |
| 562 |
if (preg_match("/\.crypt$/", $our_files['db'])) $this->jobdata_set("backup_database", 'encrypted'); |
| 563 |
} |
| 564 |
|
| 565 |
if (isset($our_files['db']) && file_exists($updraft_dir.'/'.$our_files['db'])) { |
| 566 |
$our_files['db-size'] = filesize($updraft_dir.'/'.$our_files['db']); |
| 567 |
$this->save_backup_history($our_files); |
| 568 |
} |
| 569 |
|
| 570 |
$backupable_entities = $this->get_backupable_file_entities(true); |
| 571 |
|
| 572 |
foreach ($our_files as $key => $file) { |
| 573 |
|
| 574 |
// Only continue if the stored info was about a dump |
| 575 |
if (!isset($backupable_entities[$key]) && $key != 'db') continue; |
| 576 |
|
| 577 |
$hash = md5($file); |
| 578 |
$fullpath = $this->backups_dir_location().'/'.$file; |
| 579 |
if ($this->jobdata_get("uploaded_$hash") === "yes") { |
| 580 |
$this->log("$file: $key: This file has already been successfully uploaded"); |
| 581 |
} elseif (is_file($fullpath)) { |
| 582 |
$this->log("$file: $key: This file has not yet been successfully uploaded: will queue"); |
| 583 |
$undone_files[$key] = $file; |
| 584 |
} else { |
| 585 |
$this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem"); |
| 586 |
$this->uploaded_file($file); |
| 587 |
} |
| 588 |
} |
| 589 |
|
| 590 |
if (count($undone_files) == 0) { |
| 591 |
$this->log("There were no more files that needed uploading; backup job is complete"); |
| 592 |
// No email, as the user probably already got one if something else completed the run |
| 593 |
$this->backup_finish($next_resumption, true, false, $resumption_no); |
| 594 |
return; |
| 595 |
} |
| 596 |
|
| 597 |
$this->log("Requesting backup of the files that were not successfully uploaded"); |
| 598 |
$this->cloud_backup($undone_files); |
| 599 |
|
| 600 |
$this->log("Resume backup ($bnonce, $resumption_no): finish run"); |
| 601 |
if (is_array($our_files)) $this->save_last_backup($our_files); |
| 602 |
$this->backup_finish($next_resumption, true, true, $resumption_no); |
| 603 |
|
| 604 |
} |
| 605 |
|
| 606 |
function backup_all() { |
| 607 |
$this->boot_backup(true,true); |
| 608 |
} |
| 609 |
|
| 610 |
function backup_files() { |
| 611 |
# Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules |
| 612 |
$this->boot_backup(true,false); |
| 613 |
} |
| 614 |
|
| 615 |
function backup_database() { |
| 616 |
# Note that nothing will happen if the file backup had the same schedule |
| 617 |
$this->boot_backup(false,true); |
| 618 |
} |
| 619 |
|
| 620 |
// This works with any amount of settings, but we provide also a jobdata_set for efficiency as normally there's only one setting |
| 621 |
function jobdata_set_multi() { |
| 622 |
if (!is_array($this->jobdata)) $this->jobdata = array(); |
| 623 |
|
| 624 |
$args = func_num_args(); |
| 625 |
|
| 626 |
for ($i=1; $i<=$args/2; $i++) { |
| 627 |
$key = func_get_arg($i*2-2); |
| 628 |
$value = func_get_arg($i*2-1); |
| 629 |
$this->jobdata[$key] = $value; |
| 630 |
} |
| 631 |
if (!empty($this->nonce)) set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, UPDRAFT_TRANSTIME); |
| 632 |
} |
| 633 |
|
| 634 |
function jobdata_set($key, $value) { |
| 635 |
if (is_array($this->jobdata)) { |
| 636 |
$this->jobdata[$key] = $value; |
| 637 |
} else { |
| 638 |
$this->jobdata = get_transient("updraft_jobdata_".$this->nonce); |
| 639 |
if (!is_array($this->jobdata)) $this->jobdata = array($key => $value); |
| 640 |
} |
| 641 |
set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400); |
| 642 |
} |
| 643 |
|
| 644 |
function jobdata_get($key) { |
| 645 |
if (!is_array($this->jobdata)) { |
| 646 |
$this->jobdata = get_transient("updraft_jobdata_".$this->nonce); |
| 647 |
if (!is_array($this->jobdata)) return false; |
| 648 |
} |
| 649 |
return (isset($this->jobdata[$key])) ? $this->jobdata[$key] : false; |
| 650 |
} |
| 651 |
|
| 652 |
// This uses a transient; 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 transient just helps save resources. |
| 653 |
function resumable_backup_of_files($resumption_no) { |
| 654 |
//backup directories and return a numerically indexed array of file paths to the backup files |
| 655 |
$transient_status = $this->jobdata_get('backup_files'); |
| 656 |
if ($transient_status == 'finished') { |
| 657 |
$this->log("Creation of backups of directories: already finished"); |
| 658 |
} elseif ($transient_status == "begun") { |
| 659 |
if ($resumption_no>0) { |
| 660 |
$this->log("Creation of backups of directories: had begun; will resume"); |
| 661 |
} else { |
| 662 |
$this->log("Creation of backups of directories: beginning"); |
| 663 |
} |
| 664 |
} else { |
| 665 |
# This is not necessarily a backup run which is meant to contain files at all |
| 666 |
$this->log("This backup run is not intended for files - skipping"); |
| 667 |
return array(); |
| 668 |
} |
| 669 |
// We want this array, even if already finished |
| 670 |
$backup_array = $this->backup_dirs($transient_status); |
| 671 |
// This can get over-written later |
| 672 |
$this->jobdata_set('backup_files', 'finished'); |
| 673 |
return $backup_array; |
| 674 |
} |
| 675 |
|
| 676 |
// This procedure initiates a backup run |
| 677 |
function boot_backup($backup_files, $backup_database) { |
| 678 |
|
| 679 |
@ignore_user_abort(true); |
| 680 |
// 15 minutes |
| 681 |
@set_time_limit(900); |
| 682 |
|
| 683 |
//generate backup information |
| 684 |
$this->backup_time_nonce(); |
| 685 |
$this->logfile_open($this->nonce); |
| 686 |
|
| 687 |
if (!is_file($this->logfile_name)) { |
| 688 |
$this->log('Failed to open log file ('.$this->logfile_name.') - you need to check your UpdraftPlus settings (your chosen directory for creating files in is not writable, or you ran out of disk space). Backup aborted.'); |
| 689 |
$this->error(__('Could not create files in the backup directory. Backup aborted - check your UpdraftPlus settings.','updraftplus')); |
| 690 |
return false; |
| 691 |
} |
| 692 |
|
| 693 |
// Some house-cleaning |
| 694 |
$this->clean_temporary_files(); |
| 695 |
do_action('updraftplus_boot_backup'); |
| 696 |
|
| 697 |
// Log some information that may be helpful |
| 698 |
$this->log("Tasks: Backup files: $backup_files (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval', 'unset').") Backup DB: $backup_database (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval_database', 'unset').")"); |
| 699 |
|
| 700 |
# If the files and database schedules are the same, and if this the file one, then we rope in database too. |
| 701 |
# On the other hand, if the schedules were the same and this was the database run, then there is nothing to do. |
| 702 |
if (UpdraftPlus_Options::get_updraft_option('updraft_interval') == UpdraftPlus_Options::get_updraft_option('updraft_interval_database') || UpdraftPlus_Options::get_updraft_option('updraft_interval_database', 'xyz') == 'xyz' ) { |
| 703 |
$backup_database = ($backup_files == true) ? true : false; |
| 704 |
} |
| 705 |
|
| 706 |
$this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database"); |
| 707 |
|
| 708 |
# If nothing to be done, then just finish |
| 709 |
if (!$backup_files && !$backup_database) { |
| 710 |
$this->backup_finish(1, false, false, 0); |
| 711 |
return; |
| 712 |
} |
| 713 |
|
| 714 |
$resume_interval = $this->minimum_resume_interval(); |
| 715 |
// $max_execution_time = ini_get('max_execution_time'); |
| 716 |
// if ($max_execution_time >0 && $max_execution_time<300 && $resume_interval< $max_execution_time + 30) $resume_interval = $max_execution_time + 30; |
| 717 |
|
| 718 |
$initial_jobdata = array( |
| 719 |
'resume_interval', $resume_interval, |
| 720 |
'job_type', 'backup', |
| 721 |
'backup_time', $this->backup_time, |
| 722 |
'service', UpdraftPlus_Options::get_updraft_option('updraft_service') |
| 723 |
); |
| 724 |
|
| 725 |
// Save what *should* be done, to make it resumable from this point on |
| 726 |
if ($backup_database) array_push($initial_jobdata, 'backup_database', 'begun'); |
| 727 |
if ($backup_files) array_push($initial_jobdata, 'backup_files', 'begun'); |
| 728 |
|
| 729 |
// Use of jobdata_set_multi saves around 200ms |
| 730 |
call_user_func_array(array($this, 'jobdata_set_multi'), $initial_jobdata); |
| 731 |
|
| 732 |
// Everything is now set up; now go |
| 733 |
$this->backup_resume(0, $this->nonce); |
| 734 |
|
| 735 |
} |
| 736 |
|
| 737 |
// Encrypts the file if the option is set; returns the basename of the file (according to whether it was encrypted or nto) |
| 738 |
function encrypt_file($file) { |
| 739 |
$encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase'); |
| 740 |
if (strlen($encryption) > 0) { |
| 741 |
$this->log("$file: applying encryption"); |
| 742 |
$encryption_error = 0; |
| 743 |
$microstart = microtime(true); |
| 744 |
require_once(UPDRAFTPLUS_DIR.'/includes/phpseclib/Crypt/Rijndael.php'); |
| 745 |
$rijndael = new Crypt_Rijndael(); |
| 746 |
$rijndael->setKey($encryption); |
| 747 |
$updraft_dir = $this->backups_dir_location(); |
| 748 |
$file_size = @filesize($updraft_dir.'/'.$file)/1024; |
| 749 |
if (false === file_put_contents($updraft_dir.'/'.$file.'.crypt' , $rijndael->encrypt(file_get_contents($updraft_dir.'/'.$file)))) {$encryption_error = 1;} |
| 750 |
if (0 == $encryption_error) { |
| 751 |
$time_taken = max(0.000001, microtime(true)-$microstart); |
| 752 |
$this->log("$file: encryption successful: ".round($file_size,1)."Kb in ".round($time_taken,1)."s (".round($file_size/$time_taken, 1)."Kb/s)"); |
| 753 |
# Delete unencrypted file |
| 754 |
@unlink($updraft_dir.'/'.$file); |
| 755 |
return basename($file.'.crypt'); |
| 756 |
} else { |
| 757 |
$this->log("Encryption error occurred when encrypting database. Encryption aborted."); |
| 758 |
$this->error(__("Encryption error occurred when encrypting database. Encryption aborted.",'updraftplus')); |
| 759 |
return basename($file); |
| 760 |
} |
| 761 |
} else { |
| 762 |
return basename($file); |
| 763 |
} |
| 764 |
} |
| 765 |
|
| 766 |
function backup_finish($cancel_event, $clear_nonce_transient, $allow_email, $resumption_no) { |
| 767 |
|
| 768 |
// In fact, leaving the hook to run (if debug is set) is harmless, as the resume job should only do tasks that were left unfinished, which at this stage is none. |
| 769 |
if (empty($this->errors)) { |
| 770 |
if ($clear_nonce_transient) { |
| 771 |
$this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled"); |
| 772 |
wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event, $this->nonce)); |
| 773 |
// TODO: Delete the job transient (is presently useful for debugging, and only lasts 4 hours) |
| 774 |
} |
| 775 |
} else { |
| 776 |
$this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled"); |
| 777 |
} |
| 778 |
|
| 779 |
// Send the results email if appropriate, which means: |
| 780 |
// - The caller allowed it (which is not the case in an 'empty' run) |
| 781 |
// - And: An email address was set (which must be so in email mode) |
| 782 |
// And one of: |
| 783 |
// - Debug mode |
| 784 |
// - There were no errors (which means we completed and so this is the final run - time for the final report) |
| 785 |
// - It was the tenth resumption; everything failed |
| 786 |
|
| 787 |
$send_an_email = false; |
| 788 |
|
| 789 |
// Make sure that the final status is shown |
| 790 |
if (empty($this->errors)) { |
| 791 |
$send_an_email = true; |
| 792 |
$final_message = __("The backup apparently succeeded and is now complete",'updraftplus'); |
| 793 |
} elseif ($this->newresumption_scheduled == false) { |
| 794 |
$send_an_email = true; |
| 795 |
$final_message = __("The backup attempt has finished, apparently unsuccessfully",'updraftplus'); |
| 796 |
} else { |
| 797 |
// There are errors, but a resumption will be attempted |
| 798 |
$final_message = __("The backup has not finished; a resumption is scheduled within 5 minutes",'updraftplus'); |
| 799 |
} |
| 800 |
|
| 801 |
// Now over-ride the decision to send an email, if needed |
| 802 |
if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { |
| 803 |
$send_an_email = true; |
| 804 |
$this->log("An email has been scheduled for this job, because we are in debug mode"); |
| 805 |
} |
| 806 |
// If there's no email address, or the set was empty, that is the final over-ride: don't send |
| 807 |
if (!$allow_email) { |
| 808 |
$send_an_email = false; |
| 809 |
$this->log("No email will be sent - this backup set was empty."); |
| 810 |
} elseif (UpdraftPlus_Options::get_updraft_option('updraft_email') == '') { |
| 811 |
$send_an_email = false; |
| 812 |
$this->log("No email will/can be sent - the user has not configured an email address."); |
| 813 |
} |
| 814 |
|
| 815 |
if ($send_an_email) $this->send_results_email($final_message); |
| 816 |
|
| 817 |
$this->log($final_message); |
| 818 |
|
| 819 |
@fclose($this->logfile_handle); |
| 820 |
|
| 821 |
// Don't delete the log file now; delete it upon rotation |
| 822 |
//if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) @unlink($this->logfile_name); |
| 823 |
|
| 824 |
} |
| 825 |
|
| 826 |
function send_results_email($final_message) { |
| 827 |
|
| 828 |
$debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode'); |
| 829 |
|
| 830 |
$sendmail_to = UpdraftPlus_Options::get_updraft_option('updraft_email'); |
| 831 |
|
| 832 |
$backup_files = $this->jobdata_get('backup_files'); |
| 833 |
$backup_db = $this->jobdata_get("backup_database"); |
| 834 |
|
| 835 |
if ($backup_files == 'finished' && ( $backup_db == 'finished' || $backup_db == 'encrypted' ) ) { |
| 836 |
$backup_contains = "Files and database"; |
| 837 |
} elseif ($backup_files == 'finished') { |
| 838 |
$backup_contains = ($backup_db == "begun") ? "Files (database backup has not completed)" : "Files only (database was not part of this particular schedule)"; |
| 839 |
} elseif ($backup_db == 'finished' || $backup_db == 'encrypted') { |
| 840 |
$backup_contains = ($backup_files == "begun") ? "Database (files backup has not completed)" : "Database only (files were not part of this particular schedule)"; |
| 841 |
} else { |
| 842 |
$backup_contains = "Unknown/unexpected error - please raise a support request"; |
| 843 |
} |
| 844 |
|
| 845 |
$this->log("Sending email ('$backup_contains') report to: ".substr($sendmail_to, 0, 5)."..."); |
| 846 |
|
| 847 |
$append_log = ($debug_mode && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ; |
| 848 |
|
| 849 |
wp_mail($sendmail_to,__('Backed up', 'updraftplus').': '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus: ".__('WordPress backup is complete','updraftplus').".\r\n".__('Backup contains','updraftplus').': '.$backup_contains."\r\n".__('Latest status', 'updraftplus').": $final_message\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log); |
| 850 |
|
| 851 |
} |
| 852 |
|
| 853 |
function save_last_backup($backup_array) { |
| 854 |
$success = (empty($this->errors)) ? 1 : 0; |
| 855 |
|
| 856 |
$last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors, 'backup_nonce' => $this->nonce); |
| 857 |
|
| 858 |
UpdraftPlus_Options::update_updraft_option('updraft_last_backup', $last_backup); |
| 859 |
} |
| 860 |
|
| 861 |
// This should be called whenever a file is successfully uploaded |
| 862 |
function uploaded_file($file, $id = false) { |
| 863 |
$hash = md5($file); |
| 864 |
$this->log("Recording as successfully uploaded: $file ($hash)"); |
| 865 |
$this->jobdata_set("uploaded_$hash", "yes"); |
| 866 |
if ($id) { |
| 867 |
$ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() ); |
| 868 |
$ids[$file] = $id; |
| 869 |
UpdraftPlus_Options::update_updraft_option('updraft_file_ids',$ids); |
| 870 |
$this->log("Stored file<->id correlation in database ($file <-> $id)"); |
| 871 |
} |
| 872 |
// Delete local files immediately if the option is set |
| 873 |
// Where we are only backing up locally, only the "prune" function should do deleting |
| 874 |
if ($this->jobdata_get('service') != '' && $this->jobdata_get('service') != 'none') $this->delete_local($file); |
| 875 |
} |
| 876 |
|
| 877 |
// Dispatch to the relevant function |
| 878 |
function cloud_backup($backup_array) { |
| 879 |
|
| 880 |
$service = $this->jobdata_get('service'); |
| 881 |
$this->log("Cloud backup selection: ".$service); |
| 882 |
@set_time_limit(900); |
| 883 |
|
| 884 |
$method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php'; |
| 885 |
add_action('http_api_curl', array($this, 'add_curl_capath')); |
| 886 |
if (file_exists($method_include)) require_once($method_include); |
| 887 |
remove_action('http_api_curl', array($this, 'add_curl_capath')); |
| 888 |
|
| 889 |
if ($service == "none") { |
| 890 |
$this->log("No remote despatch: user chose no remote backup service"); |
| 891 |
} else { |
| 892 |
$this->log("Beginning dispatch of backup to remote"); |
| 893 |
} |
| 894 |
|
| 895 |
$objname = "UpdraftPlus_BackupModule_${service}"; |
| 896 |
if (method_exists($objname, "backup")) { |
| 897 |
// New style - external, allowing more plugability |
| 898 |
$remote_obj = new $objname; |
| 899 |
$remote_obj->backup($backup_array); |
| 900 |
} elseif ($service == "none") { |
| 901 |
$this->prune_retained_backups("none", null, null); |
| 902 |
} |
| 903 |
} |
| 904 |
|
| 905 |
function prune_file($service, $dofile, $method_object = null, $object_passback = null ) { |
| 906 |
$this->log("Delete this file: $dofile, service=$service"); |
| 907 |
$fullpath = $this->backups_dir_location().'/'.$dofile; |
| 908 |
// delete it if it's locally available |
| 909 |
if (file_exists($fullpath)) { |
| 910 |
$this->log("Deleting local copy ($fullpath)"); |
| 911 |
@unlink($fullpath); |
| 912 |
} |
| 913 |
|
| 914 |
// Despatch to the particular method's deletion routine |
| 915 |
if (!is_null($method_object)) $method_object->delete($dofile, $object_passback); |
| 916 |
} |
| 917 |
|
| 918 |
// Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant. |
| 919 |
function prune_retained_backups($service, $backup_method_object = null, $backup_passback = null) { |
| 920 |
|
| 921 |
// If they turned off deletion on local backups, then there is nothing to do |
| 922 |
if (UpdraftPlus_Options::get_updraft_option('updraft_delete_local') == 0 && $service == 'none') { |
| 923 |
$this->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups"); |
| 924 |
return; |
| 925 |
} |
| 926 |
|
| 927 |
$this->log("Retain: beginning examination of existing backup sets"); |
| 928 |
|
| 929 |
// Number of backups to retain - files |
| 930 |
$updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 1); |
| 931 |
$updraft_retain = (is_numeric($updraft_retain)) ? $updraft_retain : 1; |
| 932 |
$this->log("Retain files: user setting: number to retain = $updraft_retain"); |
| 933 |
|
| 934 |
// Number of backups to retain - db |
| 935 |
$updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain); |
| 936 |
$updraft_retain_db = (is_numeric($updraft_retain_db)) ? $updraft_retain_db : 1; |
| 937 |
$this->log("Retain db: user setting: number to retain = $updraft_retain_db"); |
| 938 |
|
| 939 |
// Returns an array, most recent first, of backup sets |
| 940 |
$backup_history = $this->get_backup_history(); |
| 941 |
$db_backups_found = 0; |
| 942 |
$file_backups_found = 0; |
| 943 |
$this->log("Number of backup sets in history: ".count($backup_history)); |
| 944 |
|
| 945 |
$backupable_entities = $this->get_backupable_file_entities(true); |
| 946 |
|
| 947 |
foreach ($backup_history as $backup_datestamp => $backup_to_examine) { |
| 948 |
// $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads |
| 949 |
// The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted |
| 950 |
$this->log("Examining backup set with datestamp: $backup_datestamp"); |
| 951 |
|
| 952 |
if (isset($backup_to_examine['db'])) { |
| 953 |
$db_backups_found++; |
| 954 |
$this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found"); |
| 955 |
if ($db_backups_found > $updraft_retain_db) { |
| 956 |
$this->log("$backup_datestamp: over retain limit ($updraft_retain_db); will delete this database"); |
| 957 |
$dofile = $backup_to_examine['db']; |
| 958 |
if (!empty($dofile)) $this->prune_file($service, $dofile, $backup_method_object, $backup_passback); |
| 959 |
unset($backup_to_examine['db']); |
| 960 |
} |
| 961 |
} |
| 962 |
|
| 963 |
$contains_files = false; |
| 964 |
foreach ($backupable_entities as $entity => $info) { |
| 965 |
if (isset($backup_to_examine[$entity])) { |
| 966 |
$contains_files = true; |
| 967 |
break; |
| 968 |
} |
| 969 |
} |
| 970 |
|
| 971 |
if ($contains_files) { |
| 972 |
$file_backups_found++; |
| 973 |
$this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found"); |
| 974 |
if ($file_backups_found > $updraft_retain) { |
| 975 |
$this->log("$backup_datestamp: over retain limit ($updraft_retain); will delete this file set"); |
| 976 |
|
| 977 |
foreach ($backupable_entities as $entity => $info) { |
| 978 |
if (!empty($backup_to_examine[$entity])) { |
| 979 |
$this->prune_file($service, $backup_to_examine[$entity], $backup_method_object, $backup_passback); |
| 980 |
} |
| 981 |
unset($backup_to_examine[$entity]); |
| 982 |
} |
| 983 |
|
| 984 |
} |
| 985 |
} |
| 986 |
|
| 987 |
// Get new result, post-deletion |
| 988 |
$contains_files = false; |
| 989 |
foreach ($backupable_entities as $entity => $info) { |
| 990 |
if (isset($backup_to_examine[$entity])) { |
| 991 |
$contains_files = true; |
| 992 |
break; |
| 993 |
} |
| 994 |
} |
| 995 |
|
| 996 |
// Delete backup set completely if empty, o/w just remove DB |
| 997 |
// We search on the four keys which represent data, allowing other keys to be used to track other things |
| 998 |
if (!$contains_files && !isset($backup_to_examine['db']) ) { |
| 999 |
$this->log("$backup_datestamp: this backup set is now empty; will remove from history"); |
| 1000 |
unset($backup_history[$backup_datestamp]); |
| 1001 |
if (isset($backup_to_examine['nonce'])) { |
| 1002 |
$fullpath = $this->backups_dir_location().'/log.'.$backup_to_examine['nonce'].'.txt'; |
| 1003 |
if (is_file($fullpath)) { |
| 1004 |
$this->log("$backup_datestamp: deleting log file (log.".$backup_to_examine['nonce'].".txt)"); |
| 1005 |
@unlink($fullpath); |
| 1006 |
} else { |
| 1007 |
$this->log("$backup_datestamp: corresponding log file not found - must have already been deleted"); |
| 1008 |
} |
| 1009 |
} else { |
| 1010 |
$this->log("$backup_datestamp: no nonce record found in the backup set, so cannot delete any remaining log file"); |
| 1011 |
} |
| 1012 |
} else { |
| 1013 |
$this->log("$backup_datestamp: this backup set remains non-empty; will retain in history"); |
| 1014 |
$backup_history[$backup_datestamp] = $backup_to_examine; |
| 1015 |
} |
| 1016 |
} |
| 1017 |
$this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation"); |
| 1018 |
UpdraftPlus_Options::update_updraft_option('updraft_backup_history',$backup_history); |
| 1019 |
} |
| 1020 |
|
| 1021 |
function delete_local($file) { |
| 1022 |
if(UpdraftPlus_Options::get_updraft_option('updraft_delete_local')) { |
| 1023 |
$this->log("Deleting local file: $file"); |
| 1024 |
//need error checking so we don't delete what isn't successfully uploaded? |
| 1025 |
$fullpath = $this->backups_dir_location().'/'.$file; |
| 1026 |
return unlink($fullpath); |
| 1027 |
} |
| 1028 |
return true; |
| 1029 |
} |
| 1030 |
|
| 1031 |
// This function is not needed for backup success, according to the design, but it helps with efficient scheduling |
| 1032 |
function reschedule_if_needed() { |
| 1033 |
// If nothing is scheduled, then return |
| 1034 |
if (empty($this->newresumption_scheduled)) return; |
| 1035 |
$time_now = time(); |
| 1036 |
$time_away = $this->newresumption_scheduled - $time_now; |
| 1037 |
// 30 is chosen because it is also used to detect recent activity on files (file mod times) |
| 1038 |
if ($time_away >1 && $time_away <= 30) { |
| 1039 |
$this->log('The scheduled resumption is within 30 seconds - will reschedule'); |
| 1040 |
// Push 30 seconds into the future |
| 1041 |
// $this->reschedule(60); |
| 1042 |
// Increase interval generally by 30 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 30 seconds *this* time) |
| 1043 |
$this->increase_resume_and_reschedule(30); |
| 1044 |
} |
| 1045 |
} |
| 1046 |
|
| 1047 |
function reschedule($how_far_ahead) { |
| 1048 |
// Reschedule - remove presently scheduled event |
| 1049 |
$next_resumption = $this->current_resumption + 1; |
| 1050 |
wp_clear_scheduled_hook('updraft_backup_resume', array($next_resumption, $this->nonce)); |
| 1051 |
// Add new event |
| 1052 |
if ($how_far_ahead < $this->minimum_resume_interval()) $how_far_ahead=$this->minimum_resume_interval(); |
| 1053 |
$schedule_for = time() + $how_far_ahead; |
| 1054 |
$this->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)"); |
| 1055 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $this->nonce)); |
| 1056 |
$this->newresumption_scheduled = $schedule_for; |
| 1057 |
} |
| 1058 |
|
| 1059 |
function increase_resume_and_reschedule($howmuch = 120, $force_schedule = false) { |
| 1060 |
|
| 1061 |
$resume_interval = $this->jobdata_get('resume_interval'); |
| 1062 |
if (!is_numeric($resume_interval) || $resume_interval < $this->minimum_resume_interval()) $resume_interval = $this->minimum_resume_interval(); |
| 1063 |
|
| 1064 |
if (empty($this->newresumption_scheduled) && $force_schedule) { |
| 1065 |
$this->log("A new resumption will be scheduled to prevent the job ending"); |
| 1066 |
} |
| 1067 |
|
| 1068 |
if (!empty($this->newresumption_scheduled) || $force_schedule) $this->reschedule($resume_interval+$howmuch); |
| 1069 |
$this->jobdata_set('resume_interval', $resume_interval+$howmuch); |
| 1070 |
|
| 1071 |
$this->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = ".($resume_interval+$howmuch)); |
| 1072 |
} |
| 1073 |
|
| 1074 |
// For detecting another run, and aborting if one was found |
| 1075 |
function check_recent_modification($file) { |
| 1076 |
if (file_exists($file)) { |
| 1077 |
$time_mod = (int)@filemtime($file); |
| 1078 |
$time_now = time(); |
| 1079 |
if ($time_mod>100 && ($time_now-$time_mod)<30) { |
| 1080 |
$this->terminate_due_to_activity($file, $time_now, $time_mod); |
| 1081 |
} |
| 1082 |
} |
| 1083 |
} |
| 1084 |
|
| 1085 |
// This function is resumable |
| 1086 |
function backup_dirs($transient_status) { |
| 1087 |
|
| 1088 |
if(!$this->backup_time) $this->backup_time_nonce(); |
| 1089 |
|
| 1090 |
$updraft_dir = $this->backups_dir_location(); |
| 1091 |
if(!is_writable($updraft_dir)) { |
| 1092 |
$this->log("Backup directory ($updraft_dir) is not writable, or does not exist"); |
| 1093 |
$this->error(sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $updraft_dir)); |
| 1094 |
return array(); |
| 1095 |
} |
| 1096 |
|
| 1097 |
// Bring in all the zip routines |
| 1098 |
require_once(UPDRAFTPLUS_DIR.'/backup.php'); |
| 1099 |
global $updraftplus_backup; |
| 1100 |
|
| 1101 |
//get the blog name and rip out all non-alphanumeric chars other than _ |
| 1102 |
$blog_name = str_replace(' ','_',substr(get_bloginfo(), 0, 96)); |
| 1103 |
$blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name); |
| 1104 |
if(!$blog_name) $blog_name = 'non_alpha_name'; |
| 1105 |
|
| 1106 |
$backup_file_basename = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$this->nonce; |
| 1107 |
|
| 1108 |
$backup_array = array(); |
| 1109 |
|
| 1110 |
$possible_backups = $this->get_backupable_file_entities(false); |
| 1111 |
|
| 1112 |
# Plugins, themes, uploads |
| 1113 |
foreach ($possible_backups as $youwhat => $whichdir) { |
| 1114 |
|
| 1115 |
// TODO: Should store what is wanted in the job description |
| 1116 |
if (UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", apply_filters("updraftplus_defaultoption_include_$youwhat", true))) { |
| 1117 |
|
| 1118 |
$zip_file = $updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.'.zip'; |
| 1119 |
|
| 1120 |
$this->check_recent_modification($zip_file); |
| 1121 |
|
| 1122 |
if ($transient_status == 'finished') { |
| 1123 |
$backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip'; |
| 1124 |
if (file_exists($zip_file)) $backup_array[$youwhat.'-size'] = filesize($zip_file); |
| 1125 |
} else { |
| 1126 |
# Apply a filter to allow add-ons to provide their own method for creating a zip of the entity |
| 1127 |
$created = apply_filters('updraftplus_backup_makezip_'.$youwhat, $whichdir, $backup_file_basename); |
| 1128 |
# If the filter did not lead to something being created, then use the default method |
| 1129 |
if ($created == $whichdir) { |
| 1130 |
$created = $updraftplus_backup->create_zip($whichdir, $youwhat, $updraft_dir, $backup_file_basename); |
| 1131 |
} |
| 1132 |
if ($created) { |
| 1133 |
$backup_array[$youwhat] = $created; |
| 1134 |
$backup_array[$youwhat.'-size'] = filesize($updraft_dir.'/'.$created); |
| 1135 |
} |
| 1136 |
} |
| 1137 |
} else { |
| 1138 |
$this->log("No backup of $youwhat: excluded by user's options"); |
| 1139 |
} |
| 1140 |
} |
| 1141 |
|
| 1142 |
# Others - needs special/separate handling, since its purpose is to mop up everything else |
| 1143 |
if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) { |
| 1144 |
|
| 1145 |
$zip_file = $updraft_dir.'/'.$backup_file_basename.'-others.zip'; |
| 1146 |
$this->check_recent_modification($zip_file); |
| 1147 |
|
| 1148 |
if ($transient_status == 'finished') { |
| 1149 |
$backup_array['others'] = $backup_file_basename.'-others.zip'; |
| 1150 |
if (file_exists($zip_file)) $backup_array['others-size'] = filesize($zip_file); |
| 1151 |
} else { |
| 1152 |
$this->log("Beginning backup of other directories found in the content directory"); |
| 1153 |
|
| 1154 |
// http://www.phpconcept.net/pclzip/user-guide/53 |
| 1155 |
/* First parameter to create is: |
| 1156 |
An array of filenames or dirnames, |
| 1157 |
or |
| 1158 |
A string containing the filename or a dirname, |
| 1159 |
or |
| 1160 |
A string containing a list of filename or dirname separated by a comma. |
| 1161 |
*/ |
| 1162 |
|
| 1163 |
# Create an array of directories to be skipped |
| 1164 |
$others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE)); |
| 1165 |
# Make the values into the keys |
| 1166 |
$others_skip = array_flip($others_skip); |
| 1167 |
|
| 1168 |
$possible_backups_dirs = array_flip($possible_backups); |
| 1169 |
|
| 1170 |
$other_dirlist = $this->compile_folder_list_for_backup(WP_CONTENT_DIR, $possible_backups_dirs, $others_skip); |
| 1171 |
|
| 1172 |
if (count($other_dirlist)>0) { |
| 1173 |
$created = $updraftplus_backup->create_zip($other_dirlist, 'others', $updraft_dir, $backup_file_basename); |
| 1174 |
if ($created) { |
| 1175 |
$backup_array['others'] = $created; |
| 1176 |
$backup_array['others-size'] = filesize($updraft_dir.'/'.$created); |
| 1177 |
} |
| 1178 |
} else { |
| 1179 |
$this->log("No backup of other directories: there was nothing found to back up"); |
| 1180 |
} |
| 1181 |
|
| 1182 |
# If we are not already finished |
| 1183 |
} |
| 1184 |
} else { |
| 1185 |
$this->log("No backup of other directories: excluded by user's options"); |
| 1186 |
} |
| 1187 |
return $backup_array; |
| 1188 |
} |
| 1189 |
|
| 1190 |
// 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 (avoid is potentially dangerous; skip is just a preference). They are allowed to overlap. |
| 1191 |
function compile_folder_list_for_backup($backup_from_inside_dir, $avoid_these_dirs, $skip_these_dirs) { |
| 1192 |
|
| 1193 |
$dirlist = array(); |
| 1194 |
|
| 1195 |
$this->log('Looking for candidates to back up in: '.$backup_from_inside_dir); |
| 1196 |
|
| 1197 |
$updraft_dir = $this->backups_dir_location(); |
| 1198 |
if ($handle = opendir($backup_from_inside_dir)) { |
| 1199 |
|
| 1200 |
while (false !== ($entry = readdir($handle))) { |
| 1201 |
$candidate = $backup_from_inside_dir.'/'.$entry; |
| 1202 |
if ($entry != "." && $entry != "..") { |
| 1203 |
if (isset($avoid_these_dirs[$candidate])) { |
| 1204 |
$this->log("finding files: $entry: skipping: this is the ".$avoid_these_dirs[$candidate]." directory"); |
| 1205 |
} elseif ($candidate == $updraft_dir) { |
| 1206 |
$this->log("finding files: $entry: skipping: this is the updraft directory"); |
| 1207 |
} elseif (isset($skip_these_dirs[$entry])) { |
| 1208 |
$this->log("finding files: $entry: skipping: excluded by options"); |
| 1209 |
} else { |
| 1210 |
$this->log("finding files: $entry: adding to list"); |
| 1211 |
array_push($dirlist, $candidate); |
| 1212 |
} |
| 1213 |
} |
| 1214 |
} |
| 1215 |
@closedir($handle); |
| 1216 |
} else { |
| 1217 |
$this->log('ERROR: Could not read the directory: '.$backup_from_inside_dir); |
| 1218 |
$this->error(__('Could not read the directory', 'updraftplus').': '.$backup_from_inside_dir); |
| 1219 |
} |
| 1220 |
|
| 1221 |
return $dirlist; |
| 1222 |
|
| 1223 |
} |
| 1224 |
|
| 1225 |
function save_backup_history($backup_array) { |
| 1226 |
if(is_array($backup_array)) { |
| 1227 |
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); |
| 1228 |
$backup_history = (is_array($backup_history)) ? $backup_history : array(); |
| 1229 |
$backup_array['nonce'] = $this->nonce; |
| 1230 |
$backup_array['service'] = $this->jobdata_get('service'); |
| 1231 |
$backup_history[$this->backup_time] = $backup_array; |
| 1232 |
UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history); |
| 1233 |
} else { |
| 1234 |
$this->log('Could not save backup history because we have no backup array. Backup probably failed.'); |
| 1235 |
$this->error(__('Could not save backup history because we have no backup array. Backup probably failed.','updraftplus')); |
| 1236 |
} |
| 1237 |
} |
| 1238 |
|
| 1239 |
function get_backup_history() { |
| 1240 |
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); |
| 1241 |
// In fact, it looks like the line below actually *introduces* a race condition |
| 1242 |
//by doing a raw DB query to get the most up-to-date data from this option we slightly narrow the window for the multiple-cron race condition |
| 1243 |
// global $wpdb; |
| 1244 |
// $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'"))); |
| 1245 |
if(is_array($backup_history)) { |
| 1246 |
krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop. |
| 1247 |
} else { |
| 1248 |
$backup_history = array(); |
| 1249 |
} |
| 1250 |
return $backup_history; |
| 1251 |
} |
| 1252 |
|
| 1253 |
// Open a file, store its filehandle |
| 1254 |
function backup_db_open($file, $allow_gz = true) { |
| 1255 |
if (function_exists('gzopen') && $allow_gz == true) { |
| 1256 |
$this->dbhandle = @gzopen($file, 'w'); |
| 1257 |
$this->dbhandle_isgz = true; |
| 1258 |
} else { |
| 1259 |
$this->dbhandle = @fopen($file, 'w'); |
| 1260 |
$this->dbhandle_isgz = false; |
| 1261 |
} |
| 1262 |
if(!$this->dbhandle) { |
| 1263 |
$this->log("ERROR: $file: Could not open the backup file for writing"); |
| 1264 |
$this->error("$file: ".__("Could not open the backup file for writing",'updraftplus')); |
| 1265 |
} |
| 1266 |
} |
| 1267 |
|
| 1268 |
function backup_db_header() { |
| 1269 |
|
| 1270 |
//Begin new backup of MySql |
| 1271 |
$this->stow("# " . 'WordPress MySQL database backup' . "\n"); |
| 1272 |
$this->stow("# " . 'Created by UpdraftPlus (http://updraftplus.com)' . "\n"); |
| 1273 |
$this->stow("#\n"); |
| 1274 |
$this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n"); |
| 1275 |
$this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n"); |
| 1276 |
$this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n"); |
| 1277 |
$this->stow("# --------------------------------------------------------\n"); |
| 1278 |
|
| 1279 |
if (defined("DB_CHARSET")) { |
| 1280 |
$this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"); |
| 1281 |
$this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"); |
| 1282 |
$this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"); |
| 1283 |
$this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n"); |
| 1284 |
} |
| 1285 |
$this->stow("/*!40101 SET foreign_key_checks = 0 */;\n"); |
| 1286 |
} |
| 1287 |
|
| 1288 |
/* This function is resumable, using the following method: |
| 1289 |
- Each table is written out to ($final_filename).table.tmp |
| 1290 |
- When the writing finishes, it is renamed to ($final_filename).table |
| 1291 |
- When all tables are finished, they are concatenated into the final file |
| 1292 |
*/ |
| 1293 |
function backup_db($already_done = "begun") { |
| 1294 |
|
| 1295 |
// Get the file prefix |
| 1296 |
$updraft_dir = $this->backups_dir_location(); |
| 1297 |
|
| 1298 |
if(!$this->backup_time) $this->backup_time_nonce(); |
| 1299 |
if (!$this->opened_log_time) $this->logfile_open($this->nonce); |
| 1300 |
|
| 1301 |
// Get the blog name and rip out all non-alphanumeric chars other than _ |
| 1302 |
$blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 96))); |
| 1303 |
if (!$blog_name) $blog_name = 'non_alpha_name'; |
| 1304 |
|
| 1305 |
$file_base = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$this->nonce; |
| 1306 |
$backup_file_base = $updraft_dir.'/'.$file_base; |
| 1307 |
|
| 1308 |
if ('finished' == $already_done) return basename($backup_file_base.'-db.gz'); |
| 1309 |
if ('encrypted' == $already_done) return basename($backup_file_base.'-db.gz.crypt'); |
| 1310 |
|
| 1311 |
$total_tables = 0; |
| 1312 |
|
| 1313 |
global $table_prefix, $wpdb; |
| 1314 |
|
| 1315 |
$all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N); |
| 1316 |
$all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables); |
| 1317 |
|
| 1318 |
if (!is_writable($updraft_dir)) { |
| 1319 |
$this->log("The backup directory ($updraft_dir) is not writable."); |
| 1320 |
$this->error("$updraft_dir: ".__('The backup directory is not writable.','updraftplus')); |
| 1321 |
return false; |
| 1322 |
} |
| 1323 |
|
| 1324 |
$stitch_files = array(); |
| 1325 |
|
| 1326 |
foreach ($all_tables as $table) { |
| 1327 |
$total_tables++; |
| 1328 |
// Increase script execution time-limit to 15 min for every table. |
| 1329 |
if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60); |
| 1330 |
// The table file may already exist if we have produced it on a previous run |
| 1331 |
$table_file_prefix = $file_base.'-db-table-'.$table.'.table'; |
| 1332 |
if (file_exists($updraft_dir.'/'.$table_file_prefix.'.gz')) { |
| 1333 |
$this->log("Table $table: corresponding file already exists; moving on"); |
| 1334 |
} else { |
| 1335 |
// Open file, store the handle |
| 1336 |
$this->backup_db_open($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true); |
| 1337 |
# === is needed, otherwise 'false' matches (i.e. prefix does not match) |
| 1338 |
if ( strpos($table, $table_prefix) === 0 ) { |
| 1339 |
// Create the SQL statements |
| 1340 |
$this->stow("# --------------------------------------------------------\n"); |
| 1341 |
$this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n"); |
| 1342 |
$this->stow("# --------------------------------------------------------\n"); |
| 1343 |
$this->backup_table($table); |
| 1344 |
} else { |
| 1345 |
$this->stow("# --------------------------------------------------------\n"); |
| 1346 |
$this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n"); |
| 1347 |
$this->stow("# --------------------------------------------------------\n"); |
| 1348 |
} |
| 1349 |
// Close file |
| 1350 |
$this->close($this->dbhandle); |
| 1351 |
$this->log("Table $table: finishing file (${table_file_prefix}.gz)"); |
| 1352 |
rename($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', $updraft_dir.'/'.$table_file_prefix.'.gz'); |
| 1353 |
} |
| 1354 |
$stitch_files[] = $table_file_prefix; |
| 1355 |
} |
| 1356 |
|
| 1357 |
// Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side |
| 1358 |
$backup_final_file_name = $backup_file_base.'-db.gz'; |
| 1359 |
$time_now = time(); |
| 1360 |
$time_mod = (int)@filemtime($backup_final_file_name); |
| 1361 |
if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<20) { |
| 1362 |
$this->terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod); |
| 1363 |
} elseif (file_exists($backup_final_file_name)) { |
| 1364 |
$this->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 20 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."); |
| 1365 |
} |
| 1366 |
|
| 1367 |
// Finally, stitch the files together |
| 1368 |
$this->backup_db_open($backup_final_file_name, true); |
| 1369 |
$this->backup_db_header(); |
| 1370 |
|
| 1371 |
// 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 leads to files missing from the db dump |
| 1372 |
$unlink_files = array(); |
| 1373 |
|
| 1374 |
foreach ($stitch_files as $table_file) { |
| 1375 |
$this->log("{$table_file}.gz: adding to final database dump"); |
| 1376 |
if (!$handle = gzopen($updraft_dir.'/'.$table_file.'.gz', "r")) { |
| 1377 |
$this->log("Error: Failed to open database file for reading: ${table_file}.gz"); |
| 1378 |
$this->error(" Failed to open database file for reading: ${table_file}.gz"); |
| 1379 |
} else { |
| 1380 |
while ($line = gzgets($handle, 2048)) { $this->stow($line); } |
| 1381 |
gzclose($handle); |
| 1382 |
$unlink_files[] = $updraft_dir.'/'.$table_file.'.gz'; |
| 1383 |
} |
| 1384 |
} |
| 1385 |
|
| 1386 |
if (defined("DB_CHARSET")) { |
| 1387 |
$this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"); |
| 1388 |
$this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"); |
| 1389 |
$this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"); |
| 1390 |
} |
| 1391 |
|
| 1392 |
$this->log($file_base.'-db.gz: finished writing out complete database file ('.round(filesize($backup_final_file_name)/1024,1).' Kb)'); |
| 1393 |
$this->close($this->dbhandle); |
| 1394 |
|
| 1395 |
foreach ($unlink_files as $unlink_file) @unlink($unlink_file); |
| 1396 |
|
| 1397 |
if (count($this->errors)) { |
| 1398 |
return false; |
| 1399 |
} else { |
| 1400 |
# We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop |
| 1401 |
$this->log("Total database tables backed up: $total_tables"); |
| 1402 |
return basename($backup_file_base.'-db.gz'); |
| 1403 |
} |
| 1404 |
|
| 1405 |
} //wp_db_backup |
| 1406 |
|
| 1407 |
function terminate_due_to_activity($file, $time_now, $time_mod) { |
| 1408 |
$file_size = filesize($file); |
| 1409 |
$this->log("Terminate: the final database file ($file) exists, and was modified within the last 20 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=$file_size). This likely means that another UpdraftPlus run is at work; so we will exit."); |
| 1410 |
$this->increase_resume_and_reschedule(120, true); |
| 1411 |
die; |
| 1412 |
} |
| 1413 |
|
| 1414 |
/** |
| 1415 |
* Taken partially from phpMyAdmin and partially from |
| 1416 |
* Alain Wolf, Zurich - Switzerland |
| 1417 |
* Website: http://restkultur.ch/personal/wolf/scripts/db_backup/ |
| 1418 |
* Modified by Scott Merrill (http://www.skippy.net/) |
| 1419 |
* to use the WordPress $wpdb object |
| 1420 |
* @param string $table |
| 1421 |
* @param string $segment |
| 1422 |
* @return void |
| 1423 |
*/ |
| 1424 |
function backup_table($table, $segment = 'none') { |
| 1425 |
global $wpdb; |
| 1426 |
|
| 1427 |
$microtime = microtime(true); |
| 1428 |
|
| 1429 |
$total_rows = 0; |
| 1430 |
|
| 1431 |
$table_structure = $wpdb->get_results("DESCRIBE $table"); |
| 1432 |
if (! $table_structure) { |
| 1433 |
//$this->error(__('Error getting table details','wp-db-backup') . ": $table"); |
| 1434 |
return false; |
| 1435 |
} |
| 1436 |
|
| 1437 |
if(($segment == 'none') || ($segment == 0)) { |
| 1438 |
// Add SQL statement to drop existing table |
| 1439 |
$this->stow("\n\n"); |
| 1440 |
$this->stow("#\n"); |
| 1441 |
$this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n"); |
| 1442 |
$this->stow("#\n"); |
| 1443 |
$this->stow("\n"); |
| 1444 |
$this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n"); |
| 1445 |
|
| 1446 |
// Table structure |
| 1447 |
// Comment in SQL-file |
| 1448 |
$this->stow("\n\n"); |
| 1449 |
$this->stow("#\n"); |
| 1450 |
$this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n"); |
| 1451 |
$this->stow("#\n"); |
| 1452 |
$this->stow("\n"); |
| 1453 |
|
| 1454 |
$create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N); |
| 1455 |
if (false === $create_table) { |
| 1456 |
$err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table); |
| 1457 |
//$this->error($err_msg); |
| 1458 |
$this->stow("#\n# $err_msg\n#\n"); |
| 1459 |
} |
| 1460 |
$this->stow($create_table[0][1] . ' ;'); |
| 1461 |
|
| 1462 |
if (false === $table_structure) { |
| 1463 |
$err_msg = sprintf('Error getting table structure of %s', $table); |
| 1464 |
$this->stow("#\n# $err_msg\n#\n"); |
| 1465 |
} |
| 1466 |
|
| 1467 |
// Comment in SQL-file |
| 1468 |
$this->stow("\n\n#\n# " . sprintf('Data contents of table %s',$this->backquote($table)) . "\n#\n"); |
| 1469 |
} |
| 1470 |
|
| 1471 |
// In UpdraftPlus, segment is always 'none' |
| 1472 |
if(($segment == 'none') || ($segment >= 0)) { |
| 1473 |
$defs = array(); |
| 1474 |
$integer_fields = array(); |
| 1475 |
// $table_structure was from "DESCRIBE $table" |
| 1476 |
foreach ($table_structure as $struct) { |
| 1477 |
if ( (0 === strpos($struct->Type, 'tinyint')) || (0 === strpos(strtolower($struct->Type), 'smallint')) || |
| 1478 |
(0 === strpos(strtolower($struct->Type), 'mediumint')) || (0 === strpos(strtolower($struct->Type), 'int')) || (0 === strpos(strtolower($struct->Type), 'bigint')) ) { |
| 1479 |
$defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default; |
| 1480 |
$integer_fields[strtolower($struct->Field)] = "1"; |
| 1481 |
} |
| 1482 |
} |
| 1483 |
|
| 1484 |
if($segment == 'none') { |
| 1485 |
$row_start = 0; |
| 1486 |
$row_inc = 100; |
| 1487 |
} else { |
| 1488 |
$row_start = $segment * 100; |
| 1489 |
$row_inc = 100; |
| 1490 |
} |
| 1491 |
|
| 1492 |
do { |
| 1493 |
if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60); |
| 1494 |
|
| 1495 |
$table_data = $wpdb->get_results("SELECT * FROM $table LIMIT {$row_start}, {$row_inc}", ARRAY_A); |
| 1496 |
$entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES '; |
| 1497 |
// \x08\\x09, not required |
| 1498 |
$search = array("\x00", "\x0a", "\x0d", "\x1a"); |
| 1499 |
$replace = array('\0', '\n', '\r', '\Z'); |
| 1500 |
if($table_data) { |
| 1501 |
$thisentry = ""; |
| 1502 |
foreach ($table_data as $row) { |
| 1503 |
$total_rows++; |
| 1504 |
$values = array(); |
| 1505 |
foreach ($row as $key => $value) { |
| 1506 |
if (isset($integer_fields[strtolower($key)])) { |
| 1507 |
// make sure there are no blank spots in the insert syntax, |
| 1508 |
// yet try to avoid quotation marks around integers |
| 1509 |
$value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value; |
| 1510 |
$values[] = ( '' === $value ) ? "''" : $value; |
| 1511 |
} else { |
| 1512 |
$values[] = "'" . str_replace($search, $replace, str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value))) . "'"; |
| 1513 |
} |
| 1514 |
} |
| 1515 |
if ($thisentry) $thisentry .= ",\n "; |
| 1516 |
$thisentry .= '('.implode(', ', $values).')'; |
| 1517 |
// Flush every 512Kb |
| 1518 |
if (strlen($thisentry) > 524288) { |
| 1519 |
$this->stow(" \n".$entries.$thisentry.';'); |
| 1520 |
$thisentry = ""; |
| 1521 |
} |
| 1522 |
|
| 1523 |
} |
| 1524 |
if ($thisentry) $this->stow(" \n".$entries.$thisentry.';'); |
| 1525 |
$row_start += $row_inc; |
| 1526 |
} |
| 1527 |
} while((count($table_data) > 0) and ($segment=='none')); |
| 1528 |
} |
| 1529 |
|
| 1530 |
if(($segment == 'none') || ($segment < 0)) { |
| 1531 |
// Create footer/closing comment in SQL-file |
| 1532 |
$this->stow("\n"); |
| 1533 |
$this->stow("#\n"); |
| 1534 |
$this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n"); |
| 1535 |
$this->stow("# --------------------------------------------------------\n"); |
| 1536 |
$this->stow("\n"); |
| 1537 |
} |
| 1538 |
$this->log("Table $table: Total rows added: $total_rows in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds"); |
| 1539 |
|
| 1540 |
} // end backup_table() |
| 1541 |
|
| 1542 |
function stow($query_line) { |
| 1543 |
if ($this->dbhandle_isgz) { |
| 1544 |
if(! @gzwrite($this->dbhandle, $query_line)) { |
| 1545 |
//$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg); |
| 1546 |
} |
| 1547 |
} else { |
| 1548 |
if(false === @fwrite($this->dbhandle, $query_line)) { |
| 1549 |
//$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg); |
| 1550 |
} |
| 1551 |
} |
| 1552 |
} |
| 1553 |
|
| 1554 |
function close($handle) { |
| 1555 |
if ($this->dbhandle_isgz) { |
| 1556 |
gzclose($handle); |
| 1557 |
} else { |
| 1558 |
fclose($handle); |
| 1559 |
} |
| 1560 |
} |
| 1561 |
|
| 1562 |
function error($error) { |
| 1563 |
if (count($this->errors) == 0) $this->log("An error condition has occurred for the first time on this run"); |
| 1564 |
$this->errors[] = $error; |
| 1565 |
return true; |
| 1566 |
} |
| 1567 |
|
| 1568 |
/** |
| 1569 |
* Add backquotes to tables and db-names in |
| 1570 |
* SQL queries. Taken from phpMyAdmin. |
| 1571 |
*/ |
| 1572 |
function backquote($a_name) { |
| 1573 |
if (!empty($a_name) && $a_name != '*') { |
| 1574 |
if (is_array($a_name)) { |
| 1575 |
$result = array(); |
| 1576 |
reset($a_name); |
| 1577 |
while(list($key, $val) = each($a_name)) |
| 1578 |
$result[$key] = '`' . $val . '`'; |
| 1579 |
return $result; |
| 1580 |
} else { |
| 1581 |
return '`' . $a_name . '`'; |
| 1582 |
} |
| 1583 |
} else { |
| 1584 |
return $a_name; |
| 1585 |
} |
| 1586 |
} |
| 1587 |
|
| 1588 |
/*END OF WP-DB-BACKUP BLOCK */ |
| 1589 |
|
| 1590 |
/* |
| 1591 |
this function is both the backup scheduler and ostensibly a filter callback for saving the option. |
| 1592 |
it is called in the register_setting for the updraft_interval, which means when the admin settings |
| 1593 |
are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter) |
| 1594 |
so the option can be properly saved. |
| 1595 |
*/ |
| 1596 |
function schedule_backup($interval) { |
| 1597 |
//clear schedule and add new so we don't stack up scheduled backups |
| 1598 |
wp_clear_scheduled_hook('updraft_backup'); |
| 1599 |
switch($interval) { |
| 1600 |
case 'every4hours': |
| 1601 |
case 'every8hours': |
| 1602 |
case 'twicedaily': |
| 1603 |
case 'daily': |
| 1604 |
case 'weekly': |
| 1605 |
case 'fortnightly': |
| 1606 |
case 'monthly': |
| 1607 |
$first_time = apply_filters('updraftplus_schedule_start_files', time()+30); |
| 1608 |
wp_schedule_event($first_time, $interval, 'updraft_backup'); |
| 1609 |
break; |
| 1610 |
} |
| 1611 |
return wp_filter_nohtml_kses($interval); |
| 1612 |
} |
| 1613 |
|
| 1614 |
function deactivation () { |
| 1615 |
wp_clear_scheduled_hook('updraftplus_weekly_ping'); |
| 1616 |
} |
| 1617 |
|
| 1618 |
// Acts as a WordPress options filter |
| 1619 |
function googledrive_clientid_checkchange($client_id) { |
| 1620 |
if (UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid') != $client_id) { |
| 1621 |
require_once(UPDRAFTPLUS_DIR.'/methods/googledrive.php'); |
| 1622 |
add_action('http_api_curl', array($this, 'add_curl_capath')); |
| 1623 |
UpdraftPlus_BackupModule_googledrive::gdrive_auth_revoke(true); |
| 1624 |
remove_action('http_api_curl', array($this, 'add_curl_capath')); |
| 1625 |
|
| 1626 |
} |
| 1627 |
return $client_id; |
| 1628 |
} |
| 1629 |
|
| 1630 |
function schedule_backup_database($interval) { |
| 1631 |
//clear schedule and add new so we don't stack up scheduled backups |
| 1632 |
wp_clear_scheduled_hook('updraft_backup_database'); |
| 1633 |
switch($interval) { |
| 1634 |
case 'every4hours': |
| 1635 |
case 'every8hours': |
| 1636 |
case 'twicedaily': |
| 1637 |
case 'daily': |
| 1638 |
case 'weekly': |
| 1639 |
case 'fortnightly': |
| 1640 |
case 'monthly': |
| 1641 |
$first_time = apply_filters('updraftplus_schedule_start_db', time()+30); |
| 1642 |
wp_schedule_event($first_time, $interval, 'updraft_backup_database'); |
| 1643 |
break; |
| 1644 |
} |
| 1645 |
return wp_filter_nohtml_kses($interval); |
| 1646 |
} |
| 1647 |
|
| 1648 |
//wp-cron only has hourly, daily and twicedaily, so we need to add some of our own |
| 1649 |
function modify_cron_schedules($schedules) { |
| 1650 |
$schedules['weekly'] = array( 'interval' => 604800, 'display' => 'Once Weekly' ); |
| 1651 |
$schedules['fortnightly'] = array( 'interval' => 1209600, 'display' => 'Once Each Fortnight' ); |
| 1652 |
$schedules['monthly'] = array( 'interval' => 2592000, 'display' => 'Once Monthly' ); |
| 1653 |
$schedules['every4hours'] = array( 'interval' => 14400, 'display' => 'Every 4 hours' ); |
| 1654 |
$schedules['every8hours'] = array( 'interval' => 28800, 'display' => 'Every 8 hours' ); |
| 1655 |
return $schedules; |
| 1656 |
} |
| 1657 |
|
| 1658 |
function backups_dir_location() { |
| 1659 |
|
| 1660 |
if (!empty($this->backup_dir)) return $this->backup_dir; |
| 1661 |
|
| 1662 |
$updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir')); |
| 1663 |
$default_backup_dir = WP_CONTENT_DIR.'/updraft'; |
| 1664 |
$updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir; |
| 1665 |
|
| 1666 |
// Do a test for a relative path |
| 1667 |
if ('/' != substr($updraft_dir, 0, 1) && "\\" != substr($updraft_dir, 0, 1) && !preg_match('/^[a-zA-Z]:/', $updraft_dir)) { |
| 1668 |
$updraft_dir = ABSPATH.$updraft_dir; |
| 1669 |
} |
| 1670 |
|
| 1671 |
//if the option isn't set, default it to /backups inside the upload dir |
| 1672 |
//check for the existence of the dir and an enumeration preventer. |
| 1673 |
// index.php is for a sanity check - make sure that we're not somewhere unexpected |
| 1674 |
if((!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) && !is_file($updraft_dir.'/index.php')) { |
| 1675 |
@mkdir($updraft_dir, 0775, true); |
| 1676 |
@file_put_contents($updraft_dir.'/index.html',"<html><body><a href=\"http://updraftplus.com\">WordPress backups by UpdraftPlus</a></body></html>"); |
| 1677 |
if (!is_file($updraft_dir.'/.htaccess')) @file_put_contents($updraft_dir.'/.htaccess','deny from all'); |
| 1678 |
} |
| 1679 |
|
| 1680 |
$this->backup_dir = $updraft_dir; |
| 1681 |
|
| 1682 |
return $updraft_dir; |
| 1683 |
} |
| 1684 |
|
| 1685 |
function spool_file($type, $fullpath, $encryption = "") { |
| 1686 |
|
| 1687 |
@set_time_limit(900); |
| 1688 |
|
| 1689 |
if (file_exists($fullpath)) { |
| 1690 |
|
| 1691 |
$file = basename($fullpath); |
| 1692 |
|
| 1693 |
$len = filesize($fullpath); |
| 1694 |
|
| 1695 |
$filearr = explode('.',$file); |
| 1696 |
// //we've only got zip and gz...for now |
| 1697 |
$file_ext = array_pop($filearr); |
| 1698 |
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 |
| 1699 |
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past |
| 1700 |
header("Content-Length: $len;"); |
| 1701 |
|
| 1702 |
if ($file_ext == 'crypt') { |
| 1703 |
if ($encryption == "") $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase'); |
| 1704 |
if ($encryption == "") { |
| 1705 |
header('Content-type: text/plain'); |
| 1706 |
_e("Decryption failed. The database file is encrypted, but you have no encryption key entered.",'updraftplus'); |
| 1707 |
$this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.'); |
| 1708 |
} else { |
| 1709 |
require_once(UPDRAFTPLUS_DIR.'/includes/phpseclib/Crypt/Rijndael.php'); |
| 1710 |
$rijndael = new Crypt_Rijndael(); |
| 1711 |
$rijndael->setKey($encryption); |
| 1712 |
$ciphertext = $rijndael->decrypt(file_get_contents($fullpath)); |
| 1713 |
if ($ciphertext) { |
| 1714 |
header('Content-type: application/x-gzip'); |
| 1715 |
header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";"); |
| 1716 |
print $ciphertext; |
| 1717 |
} else { |
| 1718 |
header('Content-type: text/plain'); |
| 1719 |
echo __("Decryption failed. The most likely cause is that you used the wrong key.",'updraftplus')." ".__('The decryption key used:','updraftplus').' '.$encryption; |
| 1720 |
|
| 1721 |
} |
| 1722 |
} |
| 1723 |
} else { |
| 1724 |
if ($file_ext == 'zip') { |
| 1725 |
header('Content-type: application/zip'); |
| 1726 |
} else { |
| 1727 |
header('Content-type: application/x-gzip'); |
| 1728 |
} |
| 1729 |
header("Content-Disposition: attachment; filename=\"$file\";"); |
| 1730 |
readfile($fullpath); |
| 1731 |
} |
| 1732 |
// $this->delete_local($file); |
| 1733 |
} else { |
| 1734 |
echo __('File not found', 'updraftplus'); |
| 1735 |
} |
| 1736 |
} |
| 1737 |
|
| 1738 |
function retain_range($input) { |
| 1739 |
$input = (int)$input; |
| 1740 |
return ($input > 0 && $input < 3650) ? $input : 1; |
| 1741 |
} |
| 1742 |
|
| 1743 |
function memory_check_current() { |
| 1744 |
# Returns in megabytes |
| 1745 |
$memory_limit = ini_get('memory_limit'); |
| 1746 |
$memory_unit = $memory_limit[strlen($memory_limit)-1]; |
| 1747 |
$memory_limit = substr($memory_limit,0,strlen($memory_limit)-1); |
| 1748 |
switch($memory_unit) { |
| 1749 |
case 'K': |
| 1750 |
$memory_limit = $memory_limit/1024; |
| 1751 |
break; |
| 1752 |
case 'G': |
| 1753 |
$memory_limit = $memory_limit*1024; |
| 1754 |
break; |
| 1755 |
case 'M': |
| 1756 |
//assumed size, no change needed |
| 1757 |
break; |
| 1758 |
} |
| 1759 |
return $memory_limit; |
| 1760 |
} |
| 1761 |
|
| 1762 |
function memory_check($memory) { |
| 1763 |
$memory_limit = $this->memory_check_current(); |
| 1764 |
return ($memory_limit >= $memory)?true:false; |
| 1765 |
} |
| 1766 |
|
| 1767 |
function url_start($urls,$url) { |
| 1768 |
return ($urls) ? '<a href="http://'.$url.'">' : ""; |
| 1769 |
} |
| 1770 |
|
| 1771 |
function url_end($urls,$url) { |
| 1772 |
return ($urls) ? '</a>' : " (http://$url)"; |
| 1773 |
} |
| 1774 |
|
| 1775 |
function wordshell_random_advert($urls) { |
| 1776 |
if (defined('UPDRAFTPLUS_NOADS')) return ""; |
| 1777 |
$rad = rand(0,8); |
| 1778 |
switch ($rad) { |
| 1779 |
case 0: |
| 1780 |
return $this->url_start($urls,'updraftplus.com')."Want more features or paid, guaranteed support? Check out UpdraftPlus.Com".$this->url_end($urls,'updraftplus.com'); |
| 1781 |
break; |
| 1782 |
case 1: |
| 1783 |
if (defined('WPLANG') && strlen(WPLANG)>0 && !is_file(UPDRAFTPLUS_DIR.'/languages/updraftplus-'.WPLANG. |
| 1784 |
'.mo')) return __('Can you translate? Want to improve UpdraftPlus for speakers of your language?','updraftplus').$this->url_start($urls,'updraftplus.com/translate/')."Please go here for instructions - it is easy.".$this->url_end($urls,'updraftplus.com/translate/'); |
| 1785 |
|
| 1786 |
return __('Find UpdraftPlus useful?','updraftplus').' '.$this->url_start($urls,'david.dw-perspective.org.uk/donate').__("Please make a donation", 'updraftplus').$this->url_end($urls,'david.dw-perspective.org.uk/donate'); |
| 1787 |
case 2: |
| 1788 |
return $this->url_start($urls,'wordshell.net')."Check out WordShell".$this->url_end($urls,'www.wordshell.net')." - manage WordPress from the command line - huge time-saver"; |
| 1789 |
break; |
| 1790 |
case 3: |
| 1791 |
return __('Like UpdraftPlus and can spare one minute?','updraftplus').$this->url_start($urls,'wordpress.org/support/view/plugin-reviews/updraftplus#postform').' '.__('Please help UpdraftPlus by giving a positive review at wordpress.org','updraftplus').$this->url_end($urls,'wordpress.org/support/view/plugin-reviews/updraftplus#postform'); |
| 1792 |
break; |
| 1793 |
case 4: |
| 1794 |
return $this->url_start($urls,'www.simbahosting.co.uk')."Need high-quality WordPress hosting from WordPress specialists? (Including automatic backups and 1-click installer). Get it from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk'); |
| 1795 |
break; |
| 1796 |
case 5: |
| 1797 |
if (!defined('UPDRAFTPLUS_NOADS')) { |
| 1798 |
return $this->url_start($urls,'updraftplus.com').__("Need even more features and support? Check out UpdraftPlus Premium",'updraftplus').$this->url_end($urls,'updraftplus.com'); |
| 1799 |
} else { |
| 1800 |
return "Thanks for being an UpdraftPlus premium user. Keep visiting ".$this->url_start($urls,'updraftplus.com')."updraftplus.com".$this->url_end($urls,'updraftplus.com')." to see what's going on."; |
| 1801 |
} |
| 1802 |
break; |
| 1803 |
case 6: |
| 1804 |
return "Need custom WordPress services from experts (including bespoke development)?".$this->url_start($urls,'www.simbahosting.co.uk/s3/products-and-services/wordpress-experts/')." Get them from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk/s3/products-and-services/wordpress-experts/'); |
| 1805 |
break; |
| 1806 |
case 7: |
| 1807 |
return $this->url_start($urls,'updraftplus.com').__("Check out UpdraftPlus.Com for help, add-ons and support",'updraftplus').$this->url_end($urls,'updraftplus.com'); |
| 1808 |
break; |
| 1809 |
case 8: |
| 1810 |
return __("Want to say thank-you for UpdraftPlus?",'updraftplus').$this->url_start($urls,'updraftplus.com/shop/')." ".__("Please buy our very cheap 'no adverts' add-on.",'updraftplus').$this->url_end($urls,'updraftplus.com/shop/'); |
| 1811 |
break; |
| 1812 |
} |
| 1813 |
} |
| 1814 |
|
| 1815 |
} |
| 1816 |
|
| 1817 |
|
| 1818 |
?> |
| 1819 |
|