| 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.7.18 |
| 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 |
// Raise a warning for probably-too-large email attachments (make intelligent - i.e. record what's the largest succesfully despatched so far? Or is that not relevant, since that only tells us about the first link?) |
| 17 |
// After Oct 15 2013: Remove page(s) from websites discussing W3TC |
| 18 |
// Exempt UD itself from a plugins restore? (will options be out-of-sync? exempt options too?) |
| 19 |
// Post restore/migrate, check updraft_dir, and reset if non-existent |
| 20 |
// Auto-empty caches post-restore/post-migration (prevent support requests from people with state/wrong cacheing data) |
| 21 |
// Show 'Migrate' instead of 'Restore' on the button if relevant |
| 22 |
// Test with: http://wordpress.org/plugins/wp-db-driver/ |
| 23 |
// Backup notes |
| 24 |
// Detect scheduler not running. Could detect possible causes? (e.g. parse .htaccess) |
| 25 |
// The delete-em at the end needs to be made resumable |
| 26 |
// Show running jobs at top. If there are some, and are overdue, kick off a few visits to wp-cron.php to help along. |
| 27 |
// Incremental - can leverage some of the multi-zip work??? |
| 28 |
// Put in a help link to explain what WordPress core (including any additions to your WordPress root directory) does (was asked for support) |
| 29 |
// mysqldump, if available, for faster database dumps. Need then to test compatibility with max_packet_size detection in restoration |
| 30 |
// Check flow of activation on multisite |
| 31 |
// Find a faster encryption method |
| 32 |
// Multiple files in more-files |
| 33 |
// On restore, raise a warning for ginormous zips |
| 34 |
// Detect double-compressed files when they are uploaded (need a way to detect gz compression in general) |
| 35 |
// Log migrations/restores, and have an option for auto-emailing the log |
| 36 |
# Email backup method should be able to force split limit down to something manageable - or at least, should make the option display. (Put it in email class. Tweak the storage dropdown to not hide stuff also in expert class if expert is shown). |
| 37 |
// What happens if you restore with a database that then changes the setting for updraft_dir ? Should be safe, as the setting is cached during a run: double-check. |
| 38 |
// Import/slurp backups from other sites. See: http://www.skyverge.com/blog/extending-the-wordpress-xml-rpc-api/ |
| 39 |
// More sophisticated options for retaining/deleting (e.g. 4/day for X days, then 7/week for Z weeks, then 1/month for Y months) |
| 40 |
// Unpack zips via AJAX? Do bit-by-bit to allow enormous opens a better chance? (have a huge one in Dropbox) |
| 41 |
// Put in a maintenance-mode detector |
| 42 |
// Detect CloudFlare output in attempts to connect - detecting cloudflare.com should be sufficient |
| 43 |
// Bring multisite shop page up to date |
| 44 |
// Re-do pricing + support packages |
| 45 |
// Option to create S3 bucket with RRS |
| 46 |
// More files: back up multiple directories, not just one |
| 47 |
// Give a help page to go with the message: A zip error occurred - check your log for more details (reduce support requests) |
| 48 |
// Exclude .git and .svn by default from wpcore |
| 49 |
// Add option to add, not just replace entities on restore/migrate |
| 50 |
// Warnings of unreadable files upon PclZip fall-back may need to be more visible - test |
| 51 |
// Add warning to backup run at beginning if -old dirs exist |
| 52 |
// Auto-alert if disk usage passes user-defined threshold / or an automatically computed one. Auto-alert if more backups are known than should be (usually a sign of incompleteness). Actually should just delete unknown backups over a certain age. |
| 53 |
// Generic S3 provider: add page to site. S3-compatible storage providers: http://www.dragondisk.com/s3-storage-providers.html |
| 54 |
// Importer - import backup sets from another WP site directly via HTTP |
| 55 |
// Option to create new user for self post-restore |
| 56 |
// Auto-disable certain cacheing/minifying plugins post-restore |
| 57 |
// Enhance Google Drive support to not require registration, and to allow offline auth |
| 58 |
// Add note post-DB backup: you will need to log in using details from newly-imported DB |
| 59 |
// Make search+replace two-pass to deal with moving between exotic non-default moved-directory setups |
| 60 |
// Get link - http://www.rackspace.com/knowledge_center/article/how-to-use-updraftplus-to-back-up-cloud-sites-to-cloud-files |
| 61 |
// 'Delete from your webserver' should trigger a rescan if the backup was local-only |
| 62 |
// Notify user only if backup fails |
| 63 |
// Log file SHA1 after finishing creation |
| 64 |
// Add more info to email - e.g. names + sizes + checksums of uploads + locations |
| 65 |
// Encrypt archives - http://www.frostjedi.com/phpbb3/viewtopic.php?f=46&t=168508&p=391881&e=391881 |
| 66 |
// Option for additive restores - i.e. add content (themes, plugins,...) instead of replacing |
| 67 |
// Testing framework - automated testing of all file upload / download / deletion methods |
| 68 |
// Though Google Drive code users WP's native HTTP functions, it seems to not work if WP natively uses something other than curl |
| 69 |
// Ginormous tables - need to make sure we "touch" the being-written-out-file (and double-check that we check for that) every 15 seconds - https://friendpaste.com/697eKEcWib01o6zT1foFIn |
| 70 |
// With ginormous tables, log how many times they've been attempted: after 3rd attempt, log a warning and move on. But first, batch ginormous tables (resumable) |
| 71 |
// Import single site into a multisite: http://codex.wordpress.org/Migrating_Multiple_Blogs_into_WordPress_3.0_Multisite, http://wordpress.org/support/topic/single-sites-to-multisite?replies=5, http://wpmu.org/import-export-wordpress-sites-multisite/ |
| 72 |
// Selective restores - some resources |
| 73 |
// Automatically de-activate cacheing/minifying plugins upon restore (and inform user) to prevent unexpected clashes |
| 74 |
// After restoring the themes, should check to see if the currently-active one still exists or not |
| 75 |
// When you migrate/restore, if there is a .htaccess, warn/give option about it. |
| 76 |
// 'Show log' should be done in a nice pop-out, with a button to download the raw |
| 77 |
// delete_old_dirs() needs to use WP_Filesystem in a more user-friendly way when errors occur |
| 78 |
// Bulk download of entire set at once (not have to click 7 times). |
| 79 |
// Restoration should also clear all common cache locations (or just not back them up) |
| 80 |
// Deal with gigantic database tables - e.g. those over a million rows on cheap hosting. |
| 81 |
// When restoring core, need an option to retain database settings / exclude wp-config.php |
| 82 |
// If migrating, warn about consequences of over-writing wp-config.php |
| 83 |
// Produce a command-line version of the restorer (so that people with shell access are immune from server-enforced timeouts) |
| 84 |
// Restorations should be logged also |
| 85 |
// Migrator - list+download from remote, kick-off backup remotely |
| 86 |
// April 20, 2015: This is the date when the Google Documents API is likely to stop working (https://developers.google.com/google-apps/documents-list/terms) |
| 87 |
// Search for other TODO-s in the code |
| 88 |
// Opt-in non-personal stats + link to aggregated results |
| 89 |
// Stand-alone installer - take a look at this: http://wordpress.org/extend/plugins/duplicator/screenshots/ |
| 90 |
// More DB add-on (other non-WP tables; even other databases) |
| 91 |
// Unlimited customers should be auto-emailed each time they add a site (security) |
| 92 |
// Update all-features page at updraftplus.com (not updated after 1.5.5) |
| 93 |
// Add in downloading in the 'Restore' modal, and remove the advice to do so manually. |
| 94 |
// Save database encryption key inside backup history on per-db basis, so that if it changes we can still decrypt |
| 95 |
// Switch to Google Drive SDK. Google folders. https://developers.google.com/drive/folder |
| 96 |
// Convert S3.php to use WP's native HTTP functions |
| 97 |
// AJAX-ify restoration |
| 98 |
// Ability to re-scan existing cloud storage |
| 99 |
// Dropbox uses one mcrypt function - port to phpseclib for more portability |
| 100 |
// Store meta-data on which version of UD the backup was made with (will help if we ever introduce quirks that need ironing) |
| 101 |
// 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) |
| 102 |
// GoogleDrive +Rackspace folders |
| 103 |
//Do an automated test periodically for the success of loop-back connections |
| 104 |
//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. |
| 105 |
//Change DB encryption to not require whole gzip in memory (twice) |
| 106 |
//Add Box.Net, SugarSync, Me.Ga support?? |
| 107 |
//Make it easier to find add-ons |
| 108 |
// 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. |
| 109 |
// 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?? |
| 110 |
// Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests... |
| 111 |
// Chunking + resuming on SFTP |
| 112 |
// Add-on to check integrity of backups |
| 113 |
// Add-on to manage all your backups from a single dashboard |
| 114 |
// Make disk space check more intelligent (currently hard-coded at 35Mb) |
| 115 |
// 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 |
| 116 |
// Multiple jobs |
| 117 |
// Allow connecting to remote storage, scanning + populating backup history from it |
| 118 |
// GoogleDrive in-dashboard download resumption loads the whole archive into memory - should instead either chunk or directly stream fo the file handle |
| 119 |
// Multisite add-on should allow restoring of each blog individually |
| 120 |
// Create single zip, containing even WordPress itself |
| 121 |
// Remove the recurrence of admin notices when settings are saved due to _wp_referer |
| 122 |
|
| 123 |
Encrypt filesystem, if memory allows (and have option for abort if not) |
| 124 |
// New sub-module to verify that the backups are there, independently of backup thread |
| 125 |
*/ |
| 126 |
|
| 127 |
/* |
| 128 |
Portions copyright 2011-13 David Anderson |
| 129 |
Portions copyright 2010 Paul Kehrer |
| 130 |
Other portions copyright as indicated authors in the relevant files |
| 131 |
|
| 132 |
This program is free software; you can redistribute it and/or modify |
| 133 |
it under the terms of the GNU General Public License as published by |
| 134 |
the Free Software Foundation; either version 3 of the License, or |
| 135 |
(at your option) any later version. |
| 136 |
|
| 137 |
This program is distributed in the hope that it will be useful, |
| 138 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 139 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 140 |
GNU General Public License for more details. |
| 141 |
|
| 142 |
You should have received a copy of the GNU General Public License |
| 143 |
along with this program; if not, write to the Free Software |
| 144 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 145 |
*/ |
| 146 |
|
| 147 |
define('UPDRAFTPLUS_DIR', dirname(__FILE__)); |
| 148 |
define('UPDRAFTPLUS_URL', plugins_url('', __FILE__)); |
| 149 |
define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,backup*'); |
| 150 |
|
| 151 |
# The following can go in your wp-config.php |
| 152 |
if (!defined('UPDRAFTPLUS_ZIP_EXECUTABLE')) define('UPDRAFTPLUS_ZIP_EXECUTABLE', "/usr/bin/zip,/bin/zip,/usr/local/bin/zip,/usr/sfw/bin/zip,/usr/xdg4/bin/zip,/opt/bin/zip"); |
| 153 |
# If any individual file size is greater than this, then a warning is given |
| 154 |
if (!defined('UPDRAFTPLUS_WARN_FILE_SIZE')) define('UPDRAFTPLUS_WARN_FILE_SIZE', 1024*1024*250); |
| 155 |
# On a test on a Pentium laptop, 100,000 rows needed ~ 1 minute to write out - so 150,000 is around the CPanel default of 90 seconds execution time. |
| 156 |
if (!defined('UPDRAFTPLUS_WARN_DB_ROWS')) define('UPDRAFTPLUS_WARN_DB_ROWS', 150000); |
| 157 |
|
| 158 |
# The smallest value (in megabytes) that the "split zip files at" setting is allowed to be set to |
| 159 |
if (!defined('UPDRAFTPLUS_SPLIT_MIN')) define('UPDRAFTPLUS_SPLIT_MIN', 75); |
| 160 |
|
| 161 |
// Load add-ons and various files that may or may not be present, depending on where the plugin was distributed |
| 162 |
if (is_file(UPDRAFTPLUS_DIR.'/premium.php')) require_once(UPDRAFTPLUS_DIR.'/premium.php'); |
| 163 |
if (is_file(UPDRAFTPLUS_DIR.'/autoload.php')) require_once(UPDRAFTPLUS_DIR.'/autoload.php'); |
| 164 |
if (is_file(UPDRAFTPLUS_DIR.'/udaddons/updraftplus-addons.php')) include_once(UPDRAFTPLUS_DIR.'/udaddons/updraftplus-addons.php'); |
| 165 |
|
| 166 |
if (is_dir(UPDRAFTPLUS_DIR.'/addons') && $dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) { |
| 167 |
while (false !== ($e = readdir($dir_handle))) { |
| 168 |
if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e) && preg_match('/\.php$/', $e)) { |
| 169 |
include_once(UPDRAFTPLUS_DIR.'/addons/'.$e); |
| 170 |
} |
| 171 |
} |
| 172 |
@closedir($dir_handle); |
| 173 |
} |
| 174 |
|
| 175 |
if (!isset($updraftplus)) $updraftplus = new UpdraftPlus(); |
| 176 |
|
| 177 |
if (!$updraftplus->memory_check(192)) { |
| 178 |
// 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) |
| 179 |
# Add sanity checks - found someone who'd set WP_MAX_MEMORY_LIMIT to 256K ! |
| 180 |
if (!$updraftplus->memory_check($updraftplus->memory_check_current(WP_MAX_MEMORY_LIMIT))) { |
| 181 |
$new = absint($updraftplus->memory_check_current(WP_MAX_MEMORY_LIMIT)); |
| 182 |
if ($new>32 && $new<100000) { |
| 183 |
@ini_set('memory_limit', $new.'M'); //up the memory limit to the maximum WordPress is allowing for large backup files |
| 184 |
} |
| 185 |
} |
| 186 |
} |
| 187 |
|
| 188 |
if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options.php'); |
| 189 |
|
| 190 |
class UpdraftPlus { |
| 191 |
|
| 192 |
public $version; |
| 193 |
|
| 194 |
public $plugin_title = 'UpdraftPlus Backup/Restore'; |
| 195 |
|
| 196 |
// Choices will be shown in the admin menu in the order used here |
| 197 |
public $backup_methods = array ( |
| 198 |
"s3" => "Amazon S3", |
| 199 |
"dropbox" => "Dropbox", |
| 200 |
'cloudfiles' => 'Rackspace Cloud Files', |
| 201 |
"googledrive" => "Google Drive", |
| 202 |
"ftp" => "FTP", |
| 203 |
'sftp' => 'SFTP / SCP', |
| 204 |
'webdav' => 'WebDAV', |
| 205 |
's3generic' => 'S3-Compatible (Generic)', |
| 206 |
'dreamobjects' => 'DreamObjects', |
| 207 |
"email" => "Email" |
| 208 |
); |
| 209 |
|
| 210 |
public $errors = array(); |
| 211 |
public $nonce; |
| 212 |
public $logfile_name = ""; |
| 213 |
public $logfile_handle = false; |
| 214 |
public $backup_time; |
| 215 |
public $backup_time_ms; |
| 216 |
|
| 217 |
public $opened_log_time; |
| 218 |
private $backup_dir; |
| 219 |
|
| 220 |
private $jobdata; |
| 221 |
|
| 222 |
public $something_useful_happened = false; |
| 223 |
|
| 224 |
// Used to schedule resumption attempts beyond the tenth, if needed |
| 225 |
public $current_resumption; |
| 226 |
private $newresumption_scheduled = false; |
| 227 |
|
| 228 |
function __construct() { |
| 229 |
|
| 230 |
// Initialisation actions - takes place on plugin load |
| 231 |
|
| 232 |
if ($fp = fopen( __FILE__, 'r')) { |
| 233 |
$file_data = fread( $fp, 1024 ); |
| 234 |
if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) { |
| 235 |
$this->version = $matches[1]; |
| 236 |
} |
| 237 |
fclose($fp); |
| 238 |
} |
| 239 |
|
| 240 |
# Create admin page |
| 241 |
add_action('init', array($this, 'handle_url_actions')); |
| 242 |
// Run earlier than default - hence earlier than other components |
| 243 |
add_action('admin_init', array($this,'admin_init'), 9); |
| 244 |
// admin_menu runs earlier, and we need it because options.php wants to use $updraftplus_admin before admin_init happens |
| 245 |
add_action('admin_menu', array($this,'admin_init'), 9); |
| 246 |
add_action('updraft_backup', array($this,'backup_files')); |
| 247 |
add_action('updraft_backup_database', array($this,'backup_database')); |
| 248 |
# backup_all is used by the manual "Backup Now" button |
| 249 |
add_action('updraft_backup_all', array($this,'backup_all')); |
| 250 |
# this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc. |
| 251 |
add_action('updraft_backup_resume', array($this,'backup_resume'), 10, 3); |
| 252 |
# http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules. Raised priority because some plugins wrongly over-write all prior schedule changes (including BackupBuddy!) |
| 253 |
add_filter('cron_schedules', array($this,'modify_cron_schedules'), 30); |
| 254 |
add_action('plugins_loaded', array($this, 'load_translations')); |
| 255 |
|
| 256 |
register_deactivation_hook(__FILE__, array($this, 'deactivation')); |
| 257 |
|
| 258 |
} |
| 259 |
|
| 260 |
public function ensure_phpseclib($class = false, $class_path = false) { |
| 261 |
if ($class && class_exists($class)) return; |
| 262 |
if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/phpseclib')) set_include_path(get_include_path().PATH_SEPARATOR.UPDRAFTPLUS_DIR.'/includes/phpseclib'); |
| 263 |
if ($class_path) require_once(UPDRAFTPLUS_DIR.'/includes/phpseclib/'.$class_path.'.php'); |
| 264 |
} |
| 265 |
|
| 266 |
public function admin_init() { |
| 267 |
// We are in the admin area: now load all that code |
| 268 |
global $updraftplus_admin; |
| 269 |
if (empty($updraftplus_admin)) require_once(UPDRAFTPLUS_DIR.'/admin.php'); |
| 270 |
|
| 271 |
if (isset($_GET['wpnonce']) && isset($_GET['page']) && isset($_GET['action']) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadlatestmodlog' && wp_verify_nonce($_GET['wpnonce'], 'updraftplus_download')) { |
| 272 |
|
| 273 |
$updraft_dir = $this->backups_dir_location(); |
| 274 |
|
| 275 |
$log_file = ''; |
| 276 |
$mod_time = 0; |
| 277 |
|
| 278 |
if ($handle = opendir($updraft_dir)) { |
| 279 |
while (false !== ($entry = readdir($handle))) { |
| 280 |
// The latter match is for files created internally by zipArchive::addFile |
| 281 |
if (preg_match('/^log\.[a-z0-9]+\.txt$/i', $entry)) { |
| 282 |
$mtime = filemtime($updraft_dir.'/'.$entry); |
| 283 |
if ($mtime > $mod_time) { |
| 284 |
$mod_time = $mtime; |
| 285 |
$log_file = $updraft_dir.'/'.$entry; |
| 286 |
} |
| 287 |
} |
| 288 |
} |
| 289 |
@closedir($handle); |
| 290 |
} |
| 291 |
|
| 292 |
if ($mod_time >0) { |
| 293 |
if (is_readable($log_file)) { |
| 294 |
header('Content-type: text/plain'); |
| 295 |
readfile($log_file); |
| 296 |
exit; |
| 297 |
} else { |
| 298 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') ); |
| 299 |
} |
| 300 |
} else { |
| 301 |
add_action('admin_notices', array($this,'show_admin_warning_nolog') ); |
| 302 |
} |
| 303 |
} |
| 304 |
|
| 305 |
} |
| 306 |
|
| 307 |
public function add_curl_capath($handle) { |
| 308 |
if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) curl_setopt($handle, CURLOPT_CAINFO, UPDRAFTPLUS_DIR.'/includes/cacert.pem' ); |
| 309 |
} |
| 310 |
|
| 311 |
// Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?page=updraftplus&action=updraftmethod-googledrive-auth |
| 312 |
// Also handle action=downloadlog |
| 313 |
public function handle_url_actions() { |
| 314 |
|
| 315 |
// First, basic security check: must be an admin page, with ability to manage options, with the right parameters |
| 316 |
// Also, only on GET because WordPress on the options page repeats parameters sometimes when POST-ing via the _wp_referer field |
| 317 |
if (isset($_SERVER['REQUEST_METHOD']) && 'GET' == $_SERVER['REQUEST_METHOD'] && UpdraftPlus_Options::user_can_manage() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) { |
| 318 |
if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php')) { |
| 319 |
$method = $matches[1]; |
| 320 |
require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php'); |
| 321 |
$call_class = "UpdraftPlus_BackupModule_".$method; |
| 322 |
$call_method = "action_".$matches[2]; |
| 323 |
|
| 324 |
add_action('http_api_curl', array($this, 'add_curl_capath')); |
| 325 |
if (method_exists($call_class, $call_method)) call_user_func(array($call_class,$call_method)); |
| 326 |
remove_action('http_api_curl', array($this, 'add_curl_capath')); |
| 327 |
} elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) { |
| 328 |
// No WordPress nonce is needed here or for the next, since the backup is already nonce-based |
| 329 |
$updraft_dir = $this->backups_dir_location(); |
| 330 |
$log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt'; |
| 331 |
if (is_readable($log_file)) { |
| 332 |
header('Content-type: text/plain'); |
| 333 |
readfile($log_file); |
| 334 |
exit; |
| 335 |
} else { |
| 336 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') ); |
| 337 |
} |
| 338 |
} 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'])) { |
| 339 |
$updraft_dir = $this->backups_dir_location(); |
| 340 |
$spool_file = $updraft_dir.'/'.basename($_GET['updraftplus_file']); |
| 341 |
if (is_readable($spool_file)) { |
| 342 |
$dkey = (isset($_GET['decrypt_key'])) ? $_GET['decrypt_key'] : ""; |
| 343 |
$this->spool_file('db', $spool_file, $dkey); |
| 344 |
exit; |
| 345 |
} else { |
| 346 |
add_action('admin_notices', array($this,'show_admin_warning_unreadablefile') ); |
| 347 |
} |
| 348 |
} |
| 349 |
} |
| 350 |
} |
| 351 |
|
| 352 |
public function get_table_prefix() { |
| 353 |
global $wpdb; |
| 354 |
if (!empty($wpdb->base_prefix)) return $wpdb->base_prefix; |
| 355 |
return $wpdb->get_blog_prefix(0); |
| 356 |
} |
| 357 |
|
| 358 |
public function show_admin_warning_unreadablelog() { |
| 359 |
global $updraftplus_admin; |
| 360 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The log file could not be read.','updraftplus')); |
| 361 |
} |
| 362 |
|
| 363 |
public function show_admin_warning_nolog() { |
| 364 |
global $updraftplus_admin; |
| 365 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('No log files were found.','updraftplus')); |
| 366 |
} |
| 367 |
|
| 368 |
public function show_admin_warning_unreadablefile() { |
| 369 |
global $updraftplus_admin; |
| 370 |
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The given file could not be read.','updraftplus')); |
| 371 |
} |
| 372 |
|
| 373 |
function load_translations() { |
| 374 |
// Tell WordPress where to find the translations |
| 375 |
load_plugin_textdomain('updraftplus', false, basename(dirname(__FILE__)).'/languages'); |
| 376 |
} |
| 377 |
|
| 378 |
// Cleans up temporary files found in the updraft directory (and some in the site root - pclzip) |
| 379 |
// Always cleans up temporary files over 12 hours old. |
| 380 |
// With parameters, also cleans up those. |
| 381 |
// Also cleans out old job data older than 12 hours old (immutable value) |
| 382 |
public function clean_temporary_files($match = '', $older_than = 43200) { |
| 383 |
# Clean out old job data |
| 384 |
if ($older_than >10000) { |
| 385 |
global $wpdb; |
| 386 |
$all_jobs = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'updraft_jobdata_%' LIMIT 1", ARRAY_A); |
| 387 |
foreach ($all_jobs as $job) { |
| 388 |
$val = maybe_unserialize($job['option_value']); |
| 389 |
if (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) { |
| 390 |
delete_option($job['option_name']); |
| 391 |
} |
| 392 |
} |
| 393 |
|
| 394 |
} |
| 395 |
$updraft_dir = $this->backups_dir_location(); |
| 396 |
$now_time=time(); |
| 397 |
if ($handle = opendir($updraft_dir)) { |
| 398 |
while (false !== ($entry = readdir($handle))) { |
| 399 |
// This match is for files created internally by zipArchive::addFile |
| 400 |
$ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $entry); |
| 401 |
// zi followed by 6 characters is the pattern used by /usr/bin/zip on Linux systems. It's safe to check for, as we have nothing else that's going to match that pattern. |
| 402 |
$binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry); |
| 403 |
if ((preg_match("/$match\.tmp(\.gz)?$/i", $entry) || $ziparchive_match || $binzip_match) && is_file($updraft_dir.'/'.$entry)) { |
| 404 |
// We delete if a parameter was specified (and either it is a ZipArchive match or an order to delete of whatever age), or if over 12 hours old |
| 405 |
if (($match && ($ziparchive_match || $binzip_match || 0 == $older_than) && $now_time-filemtime($updraft_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($updraft_dir.'/'.$entry)>43200) { |
| 406 |
$this->log("Deleting old temporary file: $entry"); |
| 407 |
@unlink($updraft_dir.'/'.$entry); |
| 408 |
} |
| 409 |
} |
| 410 |
} |
| 411 |
@closedir($handle); |
| 412 |
} |
| 413 |
# Depending on the PHP setup, the current working directory could be ABSPATH or wp-admin - scan both |
| 414 |
foreach (array(ABSPATH, ABSPATH.'wp-admin/') as $path) { |
| 415 |
if ($handle = opendir($path)) { |
| 416 |
while (false !== ($entry = readdir($handle))) { |
| 417 |
# With the old pclzip temporary files, there is no need to keep them around after they're not in use - so we don't use $older_than here - just go for 15 minutes |
| 418 |
if (preg_match("/^pclzip-[a-z0-9]+.tmp$/", $entry) && $now_time-filemtime($path.$entry) >= 900) { |
| 419 |
$this->log("Deleting old PclZip temporary file: $entry"); |
| 420 |
@unlink($path.$entry); |
| 421 |
} |
| 422 |
} |
| 423 |
@closedir($handle); |
| 424 |
} |
| 425 |
} |
| 426 |
} |
| 427 |
|
| 428 |
public function backup_time_nonce() { |
| 429 |
$this->backup_time_ms = microtime(true); |
| 430 |
$this->backup_time = time(); |
| 431 |
$nonce = substr(md5(time().rand()), 20); |
| 432 |
$this->nonce = $nonce; |
| 433 |
} |
| 434 |
|
| 435 |
public function logfile_open($nonce) { |
| 436 |
|
| 437 |
//set log file name and open log file |
| 438 |
$updraft_dir = $this->backups_dir_location(); |
| 439 |
$this->logfile_name = $updraft_dir."/log.$nonce.txt"; |
| 440 |
|
| 441 |
if (file_exists($this->logfile_name)) { |
| 442 |
$seek_to = max((filesize($this->logfile_name) - 340), 1); |
| 443 |
$handle = fopen($this->logfile_name, 'r'); |
| 444 |
if ($handle) { |
| 445 |
# Returns 0 on success |
| 446 |
if (0 === @fseek($handle, $seek_to)) { |
| 447 |
$bytes_back = filesize($this->logfile_name) - $seek_to; |
| 448 |
# Return to the end of the file |
| 449 |
$read_recent = fread($handle, $bytes_back); |
| 450 |
# Move to end of file - ought to be redundant |
| 451 |
if (false !== strpos($read_recent, 'The backup apparently succeeded') && false !== strpos($read_recent, 'and is now complete')) { |
| 452 |
$this->backup_is_already_complete = true; |
| 453 |
} |
| 454 |
} |
| 455 |
fclose($handle); |
| 456 |
} |
| 457 |
} |
| 458 |
|
| 459 |
$this->logfile_handle = fopen($this->logfile_name, 'a'); |
| 460 |
|
| 461 |
$this->opened_log_time = microtime(true); |
| 462 |
$this->log('Opened log file at time: '.date('r')); |
| 463 |
global $wp_version; |
| 464 |
@include(ABSPATH.'wp-includes/version.php'); |
| 465 |
|
| 466 |
// Will need updating when WP stops being just plain MySQL |
| 467 |
$mysql_version = (function_exists('mysql_get_server_info')) ? @mysql_get_server_info() : '?'; |
| 468 |
|
| 469 |
$safe_mode = $this->detect_safe_mode(); |
| 470 |
|
| 471 |
$memory_usage = round(@memory_get_usage(false)/1048576, 1); |
| 472 |
$memory_usage2 = round(@memory_get_usage(true)/1048576, 1); |
| 473 |
$logline = "UpdraftPlus: ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".php_uname().") MySQL: $mysql_version Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: ".@ini_get("max_execution_time")." memory_limit: ".ini_get('memory_limit')." (used: ${memory_usage}M | ${memory_usage2}M) ZipArchive::addFile: "; |
| 474 |
|
| 475 |
// method_exists causes some faulty PHP installations to segfault, leading to support requests |
| 476 |
if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) { |
| 477 |
$logline .= 'Y'; |
| 478 |
} else { |
| 479 |
$logline .= (method_exists('ZipArchive', 'addFile')) ? "Y" : "N"; |
| 480 |
} |
| 481 |
|
| 482 |
$w3oc = 'N'; |
| 483 |
if (0 === $this->current_resumption) { |
| 484 |
if (defined('W3TC') && W3TC == true && function_exists('w3_instance')) { |
| 485 |
$modules = w3_instance('W3_ModuleStatus'); |
| 486 |
if ($modules->is_enabled('objectcache')) { |
| 487 |
$w3oc = 'Y'; |
| 488 |
} |
| 489 |
} |
| 490 |
$logline .= " W3TC/ObjectCache: $w3oc"; |
| 491 |
} |
| 492 |
|
| 493 |
$this->log($logline); |
| 494 |
|
| 495 |
if ('Y' === $w3oc) $this->log(__('W3 Total Cache\'s object cache is active. This is known to have a bug that messes with all scheduled tasks (including backup jobs).','updraftplus').' '.__('You should go to the W3 Total Cache settings page and turn it off.', 'updraftplus'), 'warning'); |
| 496 |
|
| 497 |
|
| 498 |
$disk_free_space = @disk_free_space($updraft_dir); |
| 499 |
if ($disk_free_space === false) { |
| 500 |
$this->log("Free space on disk containing Updraft's temporary directory: Unknown"); |
| 501 |
} else { |
| 502 |
$this->log("Free space on disk containing Updraft's temporary directory: ".round($disk_free_space/1048576,1)." Mb"); |
| 503 |
if ($disk_free_space < 50*1048576) $this->log(sprintf(__('Your free disk space is very low - only %s Mb remain', 'updraftplus'), round($disk_free_space/1048576, 1)), 'warning'); |
| 504 |
} |
| 505 |
} |
| 506 |
|
| 507 |
/* Logs the given line, adding (relative) time stamp and newline |
| 508 |
Note these subtleties of log handling: |
| 509 |
- Messages at level 'error' are not logged to file - it is assumed that a separate call to log() at another level will take place. This is because at level 'error', messages are translated; whereas the log file is for developers who may not know the translated language. Messages at level 'error' are for the user. |
| 510 |
- Messages at level 'error' do not persist through the job (they are only saved with save_backup_history(), and never restored from there - so only the final save_backup_history() errors persist); we presume that either a) they will be cleared on the next attempt, or b) they will occur again on the final attempt (at which point they will go to the user). But... |
| 511 |
- ... messages at level 'warning' persist. These are conditions that are unlikely to be cleared, not-fatal, but the user should be informed about. The $uniq_id field (which should not be numeric) can then be used for warnings that should only be logged once |
| 512 |
*/ |
| 513 |
public function log($line, $level = 'notice', $uniq_id = false) { |
| 514 |
|
| 515 |
if ('error' == $level || 'warning' == $level) { |
| 516 |
if ('error' == $level && $this->error_count() == 0) $this->log("An error condition has occurred for the first time during this job"); |
| 517 |
$this->errors[] = array('level' => $level, 'message' => $line); |
| 518 |
# Errors are logged separately |
| 519 |
if ('error' == $level) return; |
| 520 |
# It's a warning |
| 521 |
$warnings = $this->jobdata_get('warnings'); |
| 522 |
if (!is_array($warnings)) $warnings=array(); |
| 523 |
if ($uniq_id) { |
| 524 |
$warnings[$uniq_id] = $line; |
| 525 |
} else { |
| 526 |
$warnings[] = $line; |
| 527 |
} |
| 528 |
$this->jobdata_set('warnings', $warnings); |
| 529 |
} |
| 530 |
|
| 531 |
if ($this->logfile_handle) { |
| 532 |
# Record log file times relative to the backup start, if possible |
| 533 |
$rtime = (!empty($this->backup_time_ms)) ? microtime(true)-$this->backup_time_ms : microtime(true)-$this->opened_log_time; |
| 534 |
fwrite($this->logfile_handle, sprintf("%08.03f", round($rtime, 3))." (".$this->current_resumption.") $line\n"); |
| 535 |
} |
| 536 |
|
| 537 |
switch ($this->jobdata_get('job_type')) { |
| 538 |
case 'download': |
| 539 |
// Download messages are keyed on the job (since they could be running several), and type |
| 540 |
// The values of the POST array were checked before |
| 541 |
$findex = (!empty($_POST['findex'])) ? $_POST['findex'] : 0; |
| 542 |
set_transient('ud_dlmess_'.$_POST['timestamp'].'_'.$_POST['type'].'_'.$findex, $line." (".date('M d H:i:s').")", 3600); |
| 543 |
break; |
| 544 |
case 'restore': |
| 545 |
#if ('debug' != $level) echo $line."\n"; |
| 546 |
break; |
| 547 |
default: |
| 548 |
if ('debug' != $level) UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date_i18n('M d H:i:s').")"); |
| 549 |
break; |
| 550 |
} |
| 551 |
|
| 552 |
if (defined('UPDRAFTPLUS_CONSOLELOG')) print $line."\n"; |
| 553 |
if (defined('UPDRAFTPLUS_BROWSERLOG')) print htmlentities($line)."\n"; |
| 554 |
} |
| 555 |
|
| 556 |
# Q. Why is this abstracted into a separate function? A. To allow poedit and other parsers to pick up the need to translate strings passed to it (and not pick up all of those passed to log()). |
| 557 |
# 1st argument = the line to be logged (obligatory) |
| 558 |
# Further arguments = parameters for sprintf() |
| 559 |
public function log_e() { |
| 560 |
$args = func_get_args(); |
| 561 |
# Get first argument |
| 562 |
$pre_line = array_shift($args); |
| 563 |
# Now run sprintf on it, using any remaining arguments |
| 564 |
# TODO: Does not work - the line below passes an array; that's not right |
| 565 |
$line = sprintf(__($pre_line, 'updraftplus'), $args); |
| 566 |
echo $line.'<br>'; |
| 567 |
$this->log($line); |
| 568 |
} |
| 569 |
|
| 570 |
// 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 |
| 571 |
public function record_uploaded_chunk($percent, $extra, $file_path = false) { |
| 572 |
|
| 573 |
// Touch the original file, which helps prevent overlapping runs |
| 574 |
if ($file_path) touch($file_path); |
| 575 |
|
| 576 |
// Log it |
| 577 |
global $updraftplus_backup; |
| 578 |
$log = ucfirst($updraftplus_backup->current_service)." chunked upload: $percent % uploaded"; |
| 579 |
if ($extra) $log .= " ($extra)"; |
| 580 |
$this->log($log); |
| 581 |
// If we are on an 'overtime' resumption run, and we are still meaningfully uploading, then schedule a new resumption |
| 582 |
// Our definition of meaningful is that we must maintain an overall average of at least 0.7% per run, after allowing 9 runs for everything else to get going |
| 583 |
// i.e. Max 100/.7 + 9 = 150 runs = 760 minutes = 12 hrs 40, if spaced at 5 minute intervals. However, our algorithm now decreases the intervals if it can, so this should not really come into play |
| 584 |
// If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 59Kb/s upload speed required |
| 585 |
|
| 586 |
// What this means in effect is that at least one of the files touched during the run must reach this percentage (so lapping round from 100 is OK) |
| 587 |
if ($percent > 0.7 * ( $this->current_resumption - 9)) { |
| 588 |
$this->something_useful_happened(); |
| 589 |
} |
| 590 |
|
| 591 |
$upload_status = $this->jobdata_get('uploading_substatus'); |
| 592 |
if (is_array($upload_status)) { |
| 593 |
$upload_status['p'] = $percent/100; |
| 594 |
$this->jobdata_set('uploading_substatus', $upload_status); |
| 595 |
} |
| 596 |
|
| 597 |
} |
| 598 |
|
| 599 |
function detect_safe_mode() { |
| 600 |
return (@ini_get('safe_mode') && strtolower(@ini_get('safe_mode')) != "off") ? 1 : 0; |
| 601 |
} |
| 602 |
|
| 603 |
# We require -@ and -u -r to work - which is the usual Linux binzip |
| 604 |
function find_working_bin_zip($logit = true) { |
| 605 |
if ($this->detect_safe_mode()) return false; |
| 606 |
// The hosting provider may have explicitly disabled the popen or proc_open functions |
| 607 |
if (!function_exists('popen') || !function_exists('proc_open')) return false; |
| 608 |
$updraft_dir = $this->backups_dir_location(); |
| 609 |
foreach (explode(',', UPDRAFTPLUS_ZIP_EXECUTABLE) as $potzip) { |
| 610 |
if ($logit) $this->log("Testing: $potzip"); |
| 611 |
if (@is_executable($potzip)) { |
| 612 |
# Test it, see if it is compatible with Info-ZIP |
| 613 |
# If you have another kind of zip, then feel free to tell me about it |
| 614 |
@mkdir($updraft_dir.'/binziptest/subdir1/subdir2', 0777, true); |
| 615 |
file_put_contents($updraft_dir.'/binziptest/subdir1/subdir2/test.html', '<html></body><a href="http://updraftplus.com">UpdraftPlus is a great backup and restoration plugin for WordPress.</body></html>'); |
| 616 |
@unlink($updraft_dir.'/binziptest/test.zip'); |
| 617 |
if (is_file($updraft_dir.'/binziptest/subdir1/subdir2/test.html')) { |
| 618 |
|
| 619 |
$exec = "cd ".escapeshellarg($updraft_dir)."; $potzip -v -u -r binziptest/test.zip binziptest/subdir1"; |
| 620 |
|
| 621 |
$all_ok=true; |
| 622 |
$handle = popen($exec, "r"); |
| 623 |
if ($handle) { |
| 624 |
while (!feof($handle)) { |
| 625 |
$w = fgets($handle); |
| 626 |
if ($w && $logit) $this->log("Output: ".trim($w)); |
| 627 |
} |
| 628 |
$ret = pclose($handle); |
| 629 |
if ($ret !=0) { |
| 630 |
if ($logit) $this->log("Binary zip: error (code: $ret)"); |
| 631 |
$all_ok = false; |
| 632 |
} |
| 633 |
} else { |
| 634 |
if ($logit) $this->log("Error: popen failed"); |
| 635 |
$all_ok = false; |
| 636 |
} |
| 637 |
|
| 638 |
# Now test -@ |
| 639 |
if (true == $all_ok) { |
| 640 |
file_put_contents($updraft_dir.'/binziptest/subdir1/subdir2/test2.html', '<html></body><a href="http://updraftplus.com">UpdraftPlus is a really great backup and restoration plugin for WordPress.</body></html>'); |
| 641 |
|
| 642 |
$exec = $potzip." -v -@ binziptest/test.zip"; |
| 643 |
|
| 644 |
$all_ok=true; |
| 645 |
|
| 646 |
$descriptorspec = array( |
| 647 |
0 => array('pipe', 'r'), |
| 648 |
1 => array('pipe', 'w'), |
| 649 |
2 => array('pipe', 'w') |
| 650 |
); |
| 651 |
$handle = proc_open($exec, $descriptorspec, $pipes, $updraft_dir); |
| 652 |
if (is_resource($handle)) { |
| 653 |
if (!fwrite($pipes[0], "binziptest/subdir1/subdir2/test2.html\n")) { |
| 654 |
@fclose($pipes[0]); |
| 655 |
@fclose($pipes[1]); |
| 656 |
@fclose($pipes[2]); |
| 657 |
$all_ok = false; |
| 658 |
} else { |
| 659 |
fclose($pipes[0]); |
| 660 |
while (!feof($pipes[1])) { |
| 661 |
$w = fgets($pipes[1]); |
| 662 |
if ($w && $logit) $this->log("Output: ".trim($w)); |
| 663 |
} |
| 664 |
fclose($pipes[1]); |
| 665 |
|
| 666 |
while (!feof($pipes[2])) { |
| 667 |
$last_error = fgets($pipes[2]); |
| 668 |
if (!empty($last_error) && $logit) $this->log("Error output: ".trim($w)); |
| 669 |
} |
| 670 |
fclose($pipes[2]); |
| 671 |
|
| 672 |
$ret = proc_close($handle); |
| 673 |
if ($ret !=0) { |
| 674 |
if ($logit) $this->log("Binary zip: error (code: $ret)"); |
| 675 |
$all_ok = false; |
| 676 |
} |
| 677 |
|
| 678 |
} |
| 679 |
|
| 680 |
} else { |
| 681 |
if ($logit) $this->log("Error: proc_open failed"); |
| 682 |
$all_ok = false; |
| 683 |
} |
| 684 |
|
| 685 |
} |
| 686 |
|
| 687 |
// Do we now actually have a working zip? Need to test the created object using PclZip |
| 688 |
// If it passes, then remove dirs and then return $potzip; |
| 689 |
$found_first = false; |
| 690 |
$found_second = false; |
| 691 |
if ($all_ok && file_exists($updraft_dir.'/binziptest/test.zip')) { |
| 692 |
if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php'); |
| 693 |
$zip = new PclZip($updraft_dir.'/binziptest/test.zip'); |
| 694 |
$foundit = 0; |
| 695 |
if (($list = $zip->listContent()) != 0) { |
| 696 |
foreach ($list as $obj) { |
| 697 |
if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test.html' == $obj['stored_filename'] && $obj['size']==127) $found_first=true; |
| 698 |
if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test2.html' == $obj['stored_filename'] && $obj['size']==134) $found_second=true; |
| 699 |
} |
| 700 |
} |
| 701 |
} |
| 702 |
$this->remove_binzip_test_files($updraft_dir); |
| 703 |
if ($found_first && $found_second) { |
| 704 |
if ($logit) $this->log("Working binary zip found: $potzip"); |
| 705 |
return $potzip; |
| 706 |
} |
| 707 |
|
| 708 |
} |
| 709 |
$this->remove_binzip_test_files($updraft_dir); |
| 710 |
} |
| 711 |
} |
| 712 |
return false; |
| 713 |
} |
| 714 |
|
| 715 |
function remove_binzip_test_files($updraft_dir) { |
| 716 |
@unlink($updraft_dir.'/binziptest/subdir1/subdir2/test.html'); |
| 717 |
@unlink($updraft_dir.'/binziptest/subdir1/subdir2/test2.html'); |
| 718 |
@rmdir($updraft_dir.'/binziptest/subdir1/subdir2'); |
| 719 |
@rmdir($updraft_dir.'/binziptest/subdir1'); |
| 720 |
@unlink($updraft_dir.'/binziptest/test.zip'); |
| 721 |
@rmdir($updraft_dir.'/binziptest'); |
| 722 |
} |
| 723 |
|
| 724 |
// This function is purely for timing - we just want to know the maximum run-time; not whether we have achieved anything during it |
| 725 |
function record_still_alive() { |
| 726 |
// Update the record of maximum detected runtime on each run |
| 727 |
$time_passed = $this->jobdata_get('run_times'); |
| 728 |
if (!is_array($time_passed)) $time_passed = array(); |
| 729 |
|
| 730 |
$time_this_run = microtime(true)-$this->opened_log_time; |
| 731 |
$time_passed[$this->current_resumption] = $time_this_run; |
| 732 |
$this->jobdata_set('run_times', $time_passed); |
| 733 |
|
| 734 |
$resume_interval = $this->jobdata_get('resume_interval'); |
| 735 |
if ($time_this_run + 30 > $resume_interval) { |
| 736 |
$new_interval = ceil($time_this_run + 30); |
| 737 |
set_transient('updraft_initial_resume_interval', (int)$new_interval, 8*86400); |
| 738 |
$this->log("The time we have been running (".round($time_this_run,1).") is approaching the resumption interval ($resume_interval) - increasing resumption interval to $new_interval"); |
| 739 |
$this->jobdata_set('resume_interval', $new_interval); |
| 740 |
} |
| 741 |
|
| 742 |
} |
| 743 |
|
| 744 |
function something_useful_happened() { |
| 745 |
|
| 746 |
$this->record_still_alive(); |
| 747 |
|
| 748 |
if (!$this->something_useful_happened) { |
| 749 |
$useful_checkin = $this->jobdata_get('useful_checkin'); |
| 750 |
if (empty($useful_checkin) || $this->current_resumption > $useful_checkin) $this->jobdata_set('useful_checkin', $this->current_resumption); |
| 751 |
} |
| 752 |
|
| 753 |
$this->something_useful_happened = true; |
| 754 |
|
| 755 |
if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false) { |
| 756 |
$this->log("This is resumption ".$this->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled"); |
| 757 |
// We just use max here to make sure we get a number at all |
| 758 |
$resume_interval = max($this->jobdata_get('resume_interval'), 75); |
| 759 |
// Don't consult the minimum here |
| 760 |
// if (!is_numeric($resume_interval) || $resume_interval<300) { $resume_interval = 300; } |
| 761 |
$schedule_for = time()+$resume_interval; |
| 762 |
$this->newresumption_scheduled = $schedule_for; |
| 763 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce)); |
| 764 |
} else { |
| 765 |
$this->reschedule_if_needed(); |
| 766 |
} |
| 767 |
} |
| 768 |
|
| 769 |
// 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 |
| 770 |
function get_backupable_file_entities($include_others = true, $full_info = false) { |
| 771 |
|
| 772 |
$wp_upload_dir = wp_upload_dir(); |
| 773 |
|
| 774 |
if ($full_info) { |
| 775 |
$arr = array( |
| 776 |
'plugins' => array('path' => WP_PLUGIN_DIR, 'description' => __('Plugins','updraftplus')), |
| 777 |
'themes' => array('path' => WP_CONTENT_DIR.'/themes', 'description' => __('Themes','updraftplus')), |
| 778 |
'uploads' => array('path' => $wp_upload_dir['basedir'], 'description' => __('Uploads','updraftplus')) |
| 779 |
); |
| 780 |
} else { |
| 781 |
$arr = array( |
| 782 |
'plugins' => WP_PLUGIN_DIR, |
| 783 |
'themes' => WP_CONTENT_DIR.'/themes', |
| 784 |
'uploads' => $wp_upload_dir['basedir'] |
| 785 |
); |
| 786 |
} |
| 787 |
|
| 788 |
$arr = apply_filters('updraft_backupable_file_entities', $arr, $full_info); |
| 789 |
|
| 790 |
// We then add 'others' on to the end |
| 791 |
if ($include_others) { |
| 792 |
if ($full_info) { |
| 793 |
$arr['others'] = array('path' => WP_CONTENT_DIR, 'description' => __('Others','updraftplus')); |
| 794 |
} else { |
| 795 |
$arr['others'] = WP_CONTENT_DIR; |
| 796 |
} |
| 797 |
} |
| 798 |
|
| 799 |
// Entries that should be added after 'others' |
| 800 |
$arr = apply_filters('updraft_backupable_file_entities_final', $arr, $full_info); |
| 801 |
|
| 802 |
return $arr; |
| 803 |
|
| 804 |
} |
| 805 |
|
| 806 |
# This is just a long-winded way of forcing WP to get the value afresh from the db, instead of using the auto-loaded/cached value (which can be out of date, especially since backups are, by their nature, long-running) |
| 807 |
public function filter_pre_option_cron($v) { |
| 808 |
global $wpdb; |
| 809 |
$row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'cron' ) ); |
| 810 |
if (is_object($row )) return maybe_unserialize($row->option_value); |
| 811 |
return false; |
| 812 |
} |
| 813 |
|
| 814 |
public function backup_resume($resumption_no, $bnonce) { |
| 815 |
|
| 816 |
$this->current_resumption = $resumption_no; |
| 817 |
|
| 818 |
// 15 minutes |
| 819 |
@set_time_limit(900); |
| 820 |
|
| 821 |
@ignore_user_abort(true); |
| 822 |
// This is scheduled for 5 minutes after a backup job starts |
| 823 |
|
| 824 |
$runs_started = array(); |
| 825 |
$time_now = microtime(true); |
| 826 |
|
| 827 |
add_filter('pre_option_cron', array($this, 'filter_pre_option_cron')); |
| 828 |
|
| 829 |
// Restore state |
| 830 |
$resumption_extralog = ''; |
| 831 |
if ($resumption_no > 0) { |
| 832 |
$this->nonce = $bnonce; |
| 833 |
$this->backup_time = $this->jobdata_get('backup_time'); |
| 834 |
$this->backup_time_ms = $this->jobdata_get('backup_time_ms'); |
| 835 |
$this->logfile_open($bnonce); |
| 836 |
|
| 837 |
$runs_started = $this->jobdata_get('runs_started'); |
| 838 |
if (!is_array($runs_started)) $runs_started=array(); |
| 839 |
$time_passed = $this->jobdata_get('run_times'); |
| 840 |
if (!is_array($time_passed)) $time_passed = array(); |
| 841 |
foreach ($time_passed as $run => $passed) { |
| 842 |
if (isset($runs_started[$run]) && $runs_started[$run] + $time_passed[$run] + 30 > $time_now) { |
| 843 |
$this->terminate_due_to_activity('check-in', round($time_now,1), round($runs_started[$run] + $time_passed[$run],1)); |
| 844 |
} |
| 845 |
} |
| 846 |
|
| 847 |
$prev_resumption = $resumption_no - 1; |
| 848 |
if (isset($time_passed[$prev_resumption])) { |
| 849 |
$resumption_extralog = ", previous check-in=".round($time_passed[$prev_resumption], 1)."s"; |
| 850 |
} else { |
| 851 |
$this->no_checkin_last_time = true; |
| 852 |
} |
| 853 |
|
| 854 |
} |
| 855 |
|
| 856 |
$runs_started[$resumption_no] = $time_now; |
| 857 |
if (!empty($this->backup_time)) $this->jobdata_set('runs_started', $runs_started); |
| 858 |
|
| 859 |
// Import existing warnings. The purpose of this is so that when save_backup_history() is called, it has a complete set - because job data expires quickly, whilst the warnings of the last backup run need to persist |
| 860 |
$warnings = $this->jobdata_get('warnings'); |
| 861 |
if (is_array($warnings)) { |
| 862 |
foreach ($warnings as $warning) { |
| 863 |
$this->errors[] = array('level' => 'warning', 'message' => $warning); |
| 864 |
} |
| 865 |
} |
| 866 |
|
| 867 |
// Schedule again, to run in 5 minutes again, in case we again fail |
| 868 |
// The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping |
| 869 |
$resume_interval = max(intval($this->jobdata_get('resume_interval')), 100); |
| 870 |
|
| 871 |
if ($resumption_no > 0 && isset($runs_started[$prev_resumption])) { |
| 872 |
$our_expected_start = $runs_started[$prev_resumption] + $resume_interval; |
| 873 |
# More than 12 minutes late? |
| 874 |
if ($time_now > $our_expected_start + 720) { |
| 875 |
$this->log('Long time past since expected resumption time: approx expected= '.round($our_expected_start,1).", now=".round($time_now, 1).", diff=".round($time_now-$our_expected_start,1)); |
| 876 |
$this->log(__('Your website is visited infrequently and UpdraftPlus is not getting the resources it hoped for; please read this page:', 'updraftplus').' http://updraftplus.com/faqs/why-am-i-getting-warnings-about-my-site-not-having-enough-visitors/', 'warning', 'infrequentvisits'); |
| 877 |
} |
| 878 |
} |
| 879 |
|
| 880 |
$btime = $this->backup_time; |
| 881 |
$job_type = $this->jobdata_get('job_type'); |
| 882 |
|
| 883 |
$updraft_dir = $this->backups_dir_location(); |
| 884 |
|
| 885 |
$time_ago = time()-$btime; |
| 886 |
|
| 887 |
$this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime (${time_ago}s ago), job type=$job_type".$resumption_extralog); |
| 888 |
|
| 889 |
// This works round a bizarre bug seen in one WP install, where delete_transient and wp_clear_scheduled_hook both took no effect, and upon 'resumption' the entire backup would repeat. |
| 890 |
// Argh. In fact, this has limited effect, as apparently (at least on another install seen), the saving of the updated transient via jobdata_set() also took no effect. Still, it does not hurt. |
| 891 |
if (($resumption_no >= 1 && 'finished' == $this->jobdata_get('jobstatus')) || (!empty($this->backup_is_already_complete))) { |
| 892 |
$this->log('Terminate: This backup job is already finished.'); |
| 893 |
die; |
| 894 |
} |
| 895 |
|
| 896 |
// We just do this once, as we don't want to be in permanent conflict with the overlap detector |
| 897 |
if ($resumption_no == 8) { |
| 898 |
// $time_passed is set earlier |
| 899 |
list($max_time, $timings_string, $run_times_known) = $this->max_time_passed($time_passed, 7); |
| 900 |
$this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time)"); |
| 901 |
// Remember that 30 seconds is used as the 'perhaps something is still running' detection threshold, and that 45 seconds is used as the 'the next resumption is approaching - reschedule!' interval |
| 902 |
if ($run_times_known >= 6 && ($max_time + 52 < $resume_interval)) { |
| 903 |
$resume_interval = round($max_time + 52); |
| 904 |
$this->log("Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds"); |
| 905 |
$this->jobdata_set('resume_interval', $resume_interval); |
| 906 |
} |
| 907 |
} |
| 908 |
|
| 909 |
// A different argument than before is needed otherwise the event is ignored |
| 910 |
$next_resumption = $resumption_no+1; |
| 911 |
if ($next_resumption < 10) { |
| 912 |
if ($this->jobdata_get('one_shot') === true) { |
| 913 |
$this->log('We are in "one shot" mode - no resumptions will be scheduled'); |
| 914 |
} else { |
| 915 |
$schedule_resumption = true; |
| 916 |
} |
| 917 |
} else { |
| 918 |
// We're in over-time - we only reschedule if something useful happened last time (used to be that we waited for it to happen this time - but that meant that temporary errors, e.g. Google 400s on uploads, scuppered it all - we'd do better to have another chance |
| 919 |
$useful_checkin = $this->jobdata_get('useful_checkin'); |
| 920 |
$last_resumption = $resumption_no-1; |
| 921 |
|
| 922 |
if (empty($useful_checkin) || $useful_checkin < $last_resumption) { |
| 923 |
$this->log(sprintf('The current run is resumption number %d, and there was nothing useful done on the last run (last useful run: %s) - will not schedule a further attempt until we see something useful happening this time', $resumption_no, $useful_checkin)); |
| 924 |
} else { |
| 925 |
$schedule_resumption = true; |
| 926 |
} |
| 927 |
} |
| 928 |
if (isset($schedule_resumption)) { |
| 929 |
$schedule_for = time()+$resume_interval; |
| 930 |
$this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted"); |
| 931 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce)); |
| 932 |
$this->newresumption_scheduled = $schedule_for; |
| 933 |
} |
| 934 |
|
| 935 |
// Sanity check |
| 936 |
if (empty($this->backup_time)) { |
| 937 |
# TODO: We no longer use object cache-ing - alter this message |
| 938 |
$this->log('Abort this run: the backup_time parameter appears to be empty (this is either a one-time error caused by upgrading from a version earlier than 1.7.18 whilst a backup was in progress (which you can ignore), or 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))'); |
| 939 |
return false; |
| 940 |
} |
| 941 |
|
| 942 |
global $updraftplus_backup; |
| 943 |
// Bring in all the zip routines |
| 944 |
if (!is_a($updraftplus_backup, 'UpdraftPlus_Backup')) require_once(UPDRAFTPLUS_DIR.'/backup.php'); |
| 945 |
|
| 946 |
// This should be always called; if there were no files in this run, it returns us an empty array |
| 947 |
$backup_array = $updraftplus_backup->resumable_backup_of_files($resumption_no); |
| 948 |
|
| 949 |
// This save, if there was something, is then immediately picked up again |
| 950 |
if (is_array($backup_array)) { |
| 951 |
$this->log('Saving backup status to database (elements: '.count($backup_array).")"); |
| 952 |
$this->save_backup_history($backup_array); |
| 953 |
} |
| 954 |
|
| 955 |
// Switch of variable name is purely vestigial |
| 956 |
$our_files = $backup_array; |
| 957 |
if (!is_array($our_files)) $our_files = array(); |
| 958 |
|
| 959 |
$undone_files = array(); |
| 960 |
|
| 961 |
$backup_database = $this->jobdata_get('backup_database'); |
| 962 |
|
| 963 |
// The value of jobdata['backup_database'] is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed. |
| 964 |
if ($backup_database == "begun" || $backup_database == 'finished' || $backup_database == 'encrypted') { |
| 965 |
if ($backup_database == "begun") { |
| 966 |
if ($resumption_no > 0) { |
| 967 |
$this->log("Resuming creation of database dump"); |
| 968 |
} else { |
| 969 |
$this->log("Beginning creation of database dump"); |
| 970 |
} |
| 971 |
} elseif ($backup_database == 'encrypted') { |
| 972 |
$this->log("Database dump: Creation and encryption were completed already"); |
| 973 |
} else { |
| 974 |
$this->log("Database dump: Creation was completed already"); |
| 975 |
} |
| 976 |
|
| 977 |
$db_backup = $updraftplus_backup->backup_db($backup_database); |
| 978 |
if(is_array($our_files) && is_string($db_backup)) { |
| 979 |
$our_files['db'] = $db_backup; |
| 980 |
} |
| 981 |
|
| 982 |
if ($backup_database != 'encrypted') $this->jobdata_set('backup_database', 'finished'); |
| 983 |
} elseif ('no' == $backup_database) { |
| 984 |
$this->log('No database backup - not part of this run'); |
| 985 |
} else { |
| 986 |
$this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)"); |
| 987 |
} |
| 988 |
|
| 989 |
// Save this to our history so we can track backups for the retain feature |
| 990 |
$this->log("Saving backup history"); |
| 991 |
// 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. |
| 992 |
$this->save_backup_history($our_files); |
| 993 |
|
| 994 |
// Potentially encrypt the database if it is not already |
| 995 |
if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) { |
| 996 |
$our_files['db'] = $updraftplus_backup->encrypt_file($our_files['db']); |
| 997 |
// No need to save backup history now, as it will happen in a few lines time |
| 998 |
if (preg_match("/\.crypt$/", $our_files['db'])) $this->jobdata_set('backup_database', 'encrypted'); |
| 999 |
} |
| 1000 |
|
| 1001 |
if (isset($our_files['db']) && file_exists($updraft_dir.'/'.$our_files['db'])) { |
| 1002 |
$our_files['db-size'] = filesize($updraft_dir.'/'.$our_files['db']); |
| 1003 |
$this->save_backup_history($our_files); |
| 1004 |
} |
| 1005 |
|
| 1006 |
$backupable_entities = $this->get_backupable_file_entities(true); |
| 1007 |
|
| 1008 |
# Queue files for upload |
| 1009 |
foreach ($our_files as $key => $files) { |
| 1010 |
// Only continue if the stored info was about a dump |
| 1011 |
if (!isset($backupable_entities[$key]) && $key != 'db') continue; |
| 1012 |
if (is_string($files)) $files = array($files); |
| 1013 |
foreach ($files as $findex => $file) { |
| 1014 |
if ($this->is_uploaded($file)) { |
| 1015 |
$this->log("$file: $key: This file has already been successfully uploaded"); |
| 1016 |
} elseif (is_file($updraft_dir.'/'.$file)) { |
| 1017 |
$this->log("$file: $key: This file has not yet been successfully uploaded: will queue"); |
| 1018 |
$undone_files[$key.$findex] = $file; |
| 1019 |
} else { |
| 1020 |
$this->log("$file: $key: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem ($updraft_dir/$file)"); |
| 1021 |
$this->uploaded_file($file, null, true); |
| 1022 |
} |
| 1023 |
} |
| 1024 |
} |
| 1025 |
|
| 1026 |
if (count($undone_files) == 0) { |
| 1027 |
$this->log("Resume backup ($bnonce, $resumption_no): finish run"); |
| 1028 |
$this->log("There were no more files that needed uploading; backup job is complete"); |
| 1029 |
// No email, as the user probably already got one if something else completed the run |
| 1030 |
$this->backup_finish($next_resumption, true, false, $resumption_no); |
| 1031 |
return; |
| 1032 |
} else { |
| 1033 |
$this->log("Requesting upload of the files that have not yet been successfully uploaded (".count($undone_files).")"); |
| 1034 |
$updraftplus_backup->cloud_backup($undone_files); |
| 1035 |
} |
| 1036 |
|
| 1037 |
$this->log("Resume backup ($bnonce, $resumption_no): finish run"); |
| 1038 |
if (is_array($our_files)) $this->save_last_backup($our_files); |
| 1039 |
$this->backup_finish($next_resumption, true, true, $resumption_no); |
| 1040 |
|
| 1041 |
} |
| 1042 |
|
| 1043 |
function max_time_passed($time_passed, $upto) { |
| 1044 |
$max_time = 0; |
| 1045 |
$timings_string = ""; |
| 1046 |
$run_times_known=0; |
| 1047 |
for ($i=0; $i<=$upto; $i++) { |
| 1048 |
$timings_string .= "$i:"; |
| 1049 |
if (isset($time_passed[$i])) { |
| 1050 |
$timings_string .= round($time_passed[$i], 1).' '; |
| 1051 |
$run_times_known++; |
| 1052 |
if ($time_passed[$i] > $max_time) $max_time = round($time_passed[$i]); |
| 1053 |
} else { |
| 1054 |
$timings_string .= '? '; |
| 1055 |
} |
| 1056 |
} |
| 1057 |
return array($max_time, $timings_string, $run_times_known); |
| 1058 |
} |
| 1059 |
|
| 1060 |
function backup_all() { |
| 1061 |
$this->boot_backup(true,true); |
| 1062 |
} |
| 1063 |
|
| 1064 |
function backup_files() { |
| 1065 |
# Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules |
| 1066 |
$this->boot_backup(true,false); |
| 1067 |
} |
| 1068 |
|
| 1069 |
function backup_database() { |
| 1070 |
# Note that nothing will happen if the file backup had the same schedule |
| 1071 |
$this->boot_backup(false,true); |
| 1072 |
} |
| 1073 |
|
| 1074 |
public function jobdata_getarray($non) { |
| 1075 |
return get_site_option("updraft_jobdata_".$non, array()); |
| 1076 |
} |
| 1077 |
|
| 1078 |
// This works with any amount of settings, but we provide also a jobdata_set for efficiency as normally there's only one setting |
| 1079 |
private function jobdata_set_multi() { |
| 1080 |
if (!is_array($this->jobdata)) $this->jobdata = array(); |
| 1081 |
|
| 1082 |
$args = func_num_args(); |
| 1083 |
|
| 1084 |
for ($i=1; $i<=$args/2; $i++) { |
| 1085 |
$key = func_get_arg($i*2-2); |
| 1086 |
$value = func_get_arg($i*2-1); |
| 1087 |
$this->jobdata[$key] = $value; |
| 1088 |
} |
| 1089 |
if (!empty($this->nonce)) update_site_option("updraft_jobdata_".$this->nonce, $this->jobdata); |
| 1090 |
} |
| 1091 |
|
| 1092 |
public function jobdata_set($key, $value) { |
| 1093 |
if (!is_array($this->jobdata)) { |
| 1094 |
$this->jobdata = get_site_option("updraft_jobdata_".$this->nonce); |
| 1095 |
if (!is_array($this->jobdata)) $this->jobdata = array(); |
| 1096 |
} |
| 1097 |
$this->jobdata[$key] = $value; |
| 1098 |
update_site_option("updraft_jobdata_".$this->nonce, $this->jobdata); |
| 1099 |
} |
| 1100 |
|
| 1101 |
public function jobdata_delete($key) { |
| 1102 |
if (!is_array($this->jobdata)) { |
| 1103 |
$this->jobdata = get_site_option("updraft_jobdata_".$this->nonce); |
| 1104 |
if (!is_array($this->jobdata)) $this->jobdata = array(); |
| 1105 |
} |
| 1106 |
unset($this->jobdata[$key]); |
| 1107 |
update_site_option("updraft_jobdata_".$this->nonce, $this->jobdata); |
| 1108 |
} |
| 1109 |
|
| 1110 |
public function jobdata_get($key, $default = null) { |
| 1111 |
if (!is_array($this->jobdata)) { |
| 1112 |
$this->jobdata = get_site_option("updraft_jobdata_".$this->nonce, array()); |
| 1113 |
if (!is_array($this->jobdata)) return $default; |
| 1114 |
} |
| 1115 |
return (isset($this->jobdata[$key])) ? $this->jobdata[$key] : $default; |
| 1116 |
} |
| 1117 |
|
| 1118 |
// This procedure initiates a backup run |
| 1119 |
public function boot_backup($backup_files, $backup_database, $restrict_files_to_override = false, $one_shot = false, $service = false) { |
| 1120 |
|
| 1121 |
@ignore_user_abort(true); |
| 1122 |
// 15 minutes |
| 1123 |
@set_time_limit(900); |
| 1124 |
|
| 1125 |
//generate backup information |
| 1126 |
$this->backup_time_nonce(); |
| 1127 |
// The current_resumption is consulted within logfile_open() |
| 1128 |
$this->current_resumption = 0; |
| 1129 |
$this->logfile_open($this->nonce); |
| 1130 |
|
| 1131 |
if (!is_file($this->logfile_name)) { |
| 1132 |
$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.'); |
| 1133 |
$this->log(__('Could not create files in the backup directory. Backup aborted - check your UpdraftPlus settings.','updraftplus'), 'error'); |
| 1134 |
return false; |
| 1135 |
} |
| 1136 |
|
| 1137 |
// Some house-cleaning |
| 1138 |
$this->clean_temporary_files(); |
| 1139 |
do_action('updraftplus_boot_backup'); |
| 1140 |
|
| 1141 |
// Log some information that may be helpful |
| 1142 |
$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').")"); |
| 1143 |
|
| 1144 |
if (false === $one_shot) { |
| 1145 |
# If the files and database schedules are the same, and if this the file one, then we rope in database too. |
| 1146 |
# On the other hand, if the schedules were the same and this was the database run, then there is nothing to do. |
| 1147 |
if ('manual' != UpdraftPlus_Options::get_updraft_option('updraft_interval') && (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' )) { |
| 1148 |
$backup_database = ($backup_files == true) ? true : false; |
| 1149 |
} |
| 1150 |
$this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database"); |
| 1151 |
} |
| 1152 |
|
| 1153 |
if (!is_string($service) && !is_array($service)) $service = UpdraftPlus_Options::get_updraft_option('updraft_service'); |
| 1154 |
$service = $this->just_one($service); |
| 1155 |
|
| 1156 |
# If nothing to be done, then just finish |
| 1157 |
if (!$backup_files && !$backup_database) { |
| 1158 |
$this->backup_finish(1, false, false, 0); |
| 1159 |
return; |
| 1160 |
} |
| 1161 |
|
| 1162 |
// Allow the resume interval to be more than 300 if last time we know we went beyond that - but never more than 600 |
| 1163 |
$resume_interval = (int)min(max(300, get_transient('updraft_initial_resume_interval')), 600); |
| 1164 |
# We delete it because we only want to know about behaviour found during the very last backup run (so, if you move servers then old data is not retained) |
| 1165 |
delete_transient('updraft_initial_resume_interval'); |
| 1166 |
|
| 1167 |
$job_file_entities = array(); |
| 1168 |
if ($backup_files) { |
| 1169 |
$possible_backups = $this->get_backupable_file_entities(true); |
| 1170 |
foreach ($possible_backups as $youwhat => $whichdir) { |
| 1171 |
if ((false === $restrict_files_to_override && UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", apply_filters("updraftplus_defaultoption_include_$youwhat", true))) || (is_array($restrict_files_to_override) && in_array($youwhat, $restrict_files_to_override))) { |
| 1172 |
// The 0 indicates the zip file index |
| 1173 |
$job_file_entities[$youwhat] = array( |
| 1174 |
'index' => 0 |
| 1175 |
); |
| 1176 |
} |
| 1177 |
} |
| 1178 |
} |
| 1179 |
|
| 1180 |
$initial_jobdata = array( |
| 1181 |
'resume_interval', $resume_interval, |
| 1182 |
'job_type', 'backup', |
| 1183 |
'jobstatus', 'begun', |
| 1184 |
'backup_time', $this->backup_time, |
| 1185 |
'backup_time_ms', $this->backup_time_ms, |
| 1186 |
'service', $service, |
| 1187 |
'split_every', max(intval(UpdraftPlus_Options::get_updraft_option('updraft_split_every', 800)), UPDRAFTPLUS_SPLIT_MIN), |
| 1188 |
'maxzipbatch', 26214400, #25Mb |
| 1189 |
'job_file_entities', $job_file_entities, |
| 1190 |
'one_shot', $one_shot |
| 1191 |
); |
| 1192 |
|
| 1193 |
if ($one_shot) update_site_option('updraft_oneshotnonce', $this->nonce); |
| 1194 |
|
| 1195 |
// Save what *should* be done, to make it resumable from this point on |
| 1196 |
array_push($initial_jobdata, 'backup_database', (($backup_database) ? 'begun' : 'no')); |
| 1197 |
if ($backup_files) array_push($initial_jobdata, 'backup_files', 'begun'); |
| 1198 |
|
| 1199 |
// Use of jobdata_set_multi saves around 200ms |
| 1200 |
call_user_func_array(array($this, 'jobdata_set_multi'), $initial_jobdata); |
| 1201 |
|
| 1202 |
// Everything is set up; now go |
| 1203 |
$this->backup_resume(0, $this->nonce); |
| 1204 |
|
| 1205 |
if ($one_shot) delete_site_option('updraft_oneshotnonce'); |
| 1206 |
|
| 1207 |
} |
| 1208 |
|
| 1209 |
function backup_finish($cancel_event, $do_cleanup, $allow_email, $resumption_no) { |
| 1210 |
|
| 1211 |
$delete_jobdata = false; |
| 1212 |
|
| 1213 |
// The valid use of $do_cleanup is to indicate if in fact anything exists to clean up (if no job really started, then there may be nothing) |
| 1214 |
|
| 1215 |
// 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. |
| 1216 |
if ($this->error_count() == 0) { |
| 1217 |
if ($do_cleanup) { |
| 1218 |
$this->log("There were no errors in the uploads, so the 'resume' event ($cancel_event) is being unscheduled"); |
| 1219 |
# This apparently-worthless setting of metadata before deleting it is for the benefit of a WP install seen where wp_clear_scheduled_hook() and delete_transient() apparently did nothing (probably a faulty cache) |
| 1220 |
wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event, $this->nonce)); |
| 1221 |
$this->jobdata_set('jobstatus', 'finished'); |
| 1222 |
$delete_jobdata = true; |
| 1223 |
} |
| 1224 |
} else { |
| 1225 |
$this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled"); |
| 1226 |
$this->jobdata_set('jobstatus', 'resumingforerrors'); |
| 1227 |
} |
| 1228 |
|
| 1229 |
// Send the results email if appropriate, which means: |
| 1230 |
// - The caller allowed it (which is not the case in an 'empty' run) |
| 1231 |
// - And: An email address was set (which must be so in email mode) |
| 1232 |
// And one of: |
| 1233 |
// - Debug mode |
| 1234 |
// - There were no errors (which means we completed and so this is the final run - time for the final report) |
| 1235 |
// - It was the tenth resumption; everything failed |
| 1236 |
|
| 1237 |
$send_an_email = false; |
| 1238 |
|
| 1239 |
// Make sure that the final status is shown |
| 1240 |
if ($this->error_count() == 0) { |
| 1241 |
$send_an_email = true; |
| 1242 |
if ($this->error_count('warning') == 0) { |
| 1243 |
$final_message = __('The backup apparently succeeded and is now complete','updraftplus'); |
| 1244 |
# Ensure it is logged in English. Not hugely important; but helps with a tiny number of really broken setups in which the options cacheing is broken |
| 1245 |
if ('The backup apparently succeeded and is now complete' != $final_message) { |
| 1246 |
$updraftplus->log('The backup apparently succeeded and is now complete'); |
| 1247 |
} |
| 1248 |
} else { |
| 1249 |
$final_message = __('The backup apparently succeeded (with warnings) and is now complete','updraftplus'); |
| 1250 |
if ('The backup apparently succeeded (with warnings) and is now complete' != $final_message) { |
| 1251 |
$updraftplus->log('The backup apparently succeeded (with warnings) and is now complete'); |
| 1252 |
} |
| 1253 |
} |
| 1254 |
} elseif ($this->newresumption_scheduled == false) { |
| 1255 |
$send_an_email = true; |
| 1256 |
$final_message = __('The backup attempt has finished, apparently unsuccessfully', 'updraftplus'); |
| 1257 |
} else { |
| 1258 |
// There are errors, but a resumption will be attempted |
| 1259 |
$final_message = __('The backup has not finished; a resumption is scheduled', 'updraftplus'); |
| 1260 |
} |
| 1261 |
|
| 1262 |
// Now over-ride the decision to send an email, if needed |
| 1263 |
if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) { |
| 1264 |
$send_an_email = true; |
| 1265 |
$this->log("An email has been scheduled for this job, because we are in debug mode"); |
| 1266 |
} |
| 1267 |
// If there's no email address, or the set was empty, that is the final over-ride: don't send |
| 1268 |
if (!$allow_email) { |
| 1269 |
$send_an_email = false; |
| 1270 |
$this->log("No email will be sent - this backup set was empty."); |
| 1271 |
} elseif (UpdraftPlus_Options::get_updraft_option('updraft_email') == '') { |
| 1272 |
$send_an_email = false; |
| 1273 |
$this->log("No email will/can be sent - the user has not configured an email address."); |
| 1274 |
} |
| 1275 |
|
| 1276 |
global $updraftplus_backup; |
| 1277 |
if ($send_an_email) $updraftplus_backup->send_results_email($final_message); |
| 1278 |
|
| 1279 |
$this->log($final_message); |
| 1280 |
|
| 1281 |
@fclose($this->logfile_handle); |
| 1282 |
|
| 1283 |
// Don't delete the log file now; delete it upon rotation |
| 1284 |
//if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) @unlink($this->logfile_name); |
| 1285 |
|
| 1286 |
// This is left until last for the benefit of the front-end UI, which then gets maximum chance to display the 'finished' status |
| 1287 |
if ($delete_jobdata) delete_site_option('updraft_jobdata_'.$this->nonce); |
| 1288 |
|
| 1289 |
} |
| 1290 |
|
| 1291 |
public function error_count($level = 'error') { |
| 1292 |
$count = 0; |
| 1293 |
foreach ($this->errors as $err) { |
| 1294 |
if (('error' == $level && (is_string($err) || is_wp_error($err))) || (is_array($err) && $level == $err['level']) ) { $count++; } |
| 1295 |
} |
| 1296 |
return $count; |
| 1297 |
} |
| 1298 |
|
| 1299 |
public function list_errors() { |
| 1300 |
echo '<ul style="list-style: disc inside;">'; |
| 1301 |
foreach ($this->errors as $err) { |
| 1302 |
if (is_wp_error($err)) { |
| 1303 |
foreach ($err->get_error_messages() as $msg) { |
| 1304 |
echo '<li>'.htmlspecialchars($msg).'<li>'; |
| 1305 |
} |
| 1306 |
} elseif (is_array($err) && 'error' == $err['level']) { |
| 1307 |
echo "<li>".htmlspecialchars($err['message'])."</li>"; |
| 1308 |
} elseif (is_string($err)) { |
| 1309 |
echo "<li>".htmlspecialchars($err)."</li>"; |
| 1310 |
} else { |
| 1311 |
print "<li>".print_r($err,true)."</li>"; |
| 1312 |
} |
| 1313 |
} |
| 1314 |
echo '</ul>'; |
| 1315 |
} |
| 1316 |
|
| 1317 |
function save_last_backup($backup_array) { |
| 1318 |
$success = ($this->error_count() == 0) ? 1 : 0; |
| 1319 |
|
| 1320 |
$last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors, 'backup_nonce' => $this->nonce); |
| 1321 |
|
| 1322 |
UpdraftPlus_Options::update_updraft_option('updraft_last_backup', $last_backup); |
| 1323 |
} |
| 1324 |
|
| 1325 |
// This should be called whenever a file is successfully uploaded |
| 1326 |
function uploaded_file($file, $id = false, $force = false) { |
| 1327 |
|
| 1328 |
global $updraftplus_backup; |
| 1329 |
|
| 1330 |
$service = (empty($updraftplus_backup->current_service)) ? '' : $updraftplus_backup->current_service; |
| 1331 |
$shash = $service.'-'.md5($file); |
| 1332 |
$this->jobdata_set("uploaded_".$shash, 'yes'); |
| 1333 |
|
| 1334 |
if ($force || $updraftplus_backup->last_service) { |
| 1335 |
$hash = md5($file); |
| 1336 |
$this->log("Recording as successfully uploaded: $file ($hash)"); |
| 1337 |
$this->jobdata_set("uploaded_".$hash, 'yes'); |
| 1338 |
} else { |
| 1339 |
$this->log("Recording as successfully uploaded: $file (".$updraftplus_backup->current_service.", more services to follow)"); |
| 1340 |
} |
| 1341 |
|
| 1342 |
$upload_status = $this->jobdata_get('uploading_substatus'); |
| 1343 |
if (is_array($upload_status) && isset($upload_status['i'])) { |
| 1344 |
$upload_status['i']++; |
| 1345 |
$upload_status['p']=0; |
| 1346 |
$this->jobdata_set('uploading_substatus', $upload_status); |
| 1347 |
} |
| 1348 |
|
| 1349 |
if ($id) { |
| 1350 |
$ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() ); |
| 1351 |
$ids[$file] = $id; |
| 1352 |
UpdraftPlus_Options::update_updraft_option('updraft_file_ids',$ids); |
| 1353 |
$this->log("Stored file<->id correlation in database ($file <-> $id)"); |
| 1354 |
} |
| 1355 |
|
| 1356 |
// Delete local files immediately if the option is set |
| 1357 |
// Where we are only backing up locally, only the "prune" function should do deleting |
| 1358 |
if ($updraftplus_backup->last_service && ($this->jobdata_get('service') !== '' && ((is_array($this->jobdata_get('service')) && count($this->jobdata_get('service')) >0) || (is_string($this->jobdata_get('service')) && $this->jobdata_get('service') !== 'none')))) { |
| 1359 |
$this->delete_local($file); |
| 1360 |
} |
| 1361 |
} |
| 1362 |
|
| 1363 |
function is_uploaded($file, $service = '') { |
| 1364 |
$hash = $service.'-'.md5($file); |
| 1365 |
return ($this->jobdata_get("uploaded_$hash") === "yes") ? true : false; |
| 1366 |
} |
| 1367 |
|
| 1368 |
function delete_local($file) { |
| 1369 |
if(UpdraftPlus_Options::get_updraft_option('updraft_delete_local')) { |
| 1370 |
$this->log("Deleting local file: $file"); |
| 1371 |
//need error checking so we don't delete what isn't successfully uploaded? |
| 1372 |
$fullpath = $this->backups_dir_location().'/'.$file; |
| 1373 |
return unlink($fullpath); |
| 1374 |
} |
| 1375 |
return true; |
| 1376 |
} |
| 1377 |
|
| 1378 |
// This function is not needed for backup success, according to the design, but it helps with efficient scheduling |
| 1379 |
function reschedule_if_needed() { |
| 1380 |
// If nothing is scheduled, then return |
| 1381 |
if (empty($this->newresumption_scheduled)) return; |
| 1382 |
$time_now = time(); |
| 1383 |
$time_away = $this->newresumption_scheduled - $time_now; |
| 1384 |
// 45 is chosen because it is 15 seconds more than what is used to detect recent activity on files (file mod times). (If we use exactly the same, then it's more possible to slightly miss each other) |
| 1385 |
if ($time_away >1 && $time_away <= 45) { |
| 1386 |
$this->log('The scheduled resumption is within 45 seconds - will reschedule'); |
| 1387 |
// Push 45 seconds into the future |
| 1388 |
// $this->reschedule(60); |
| 1389 |
// Increase interval generally by 45 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 45 seconds *this* time) |
| 1390 |
$this->increase_resume_and_reschedule(45); |
| 1391 |
} |
| 1392 |
} |
| 1393 |
|
| 1394 |
function reschedule($how_far_ahead) { |
| 1395 |
// Reschedule - remove presently scheduled event |
| 1396 |
$next_resumption = $this->current_resumption + 1; |
| 1397 |
wp_clear_scheduled_hook('updraft_backup_resume', array($next_resumption, $this->nonce)); |
| 1398 |
// Add new event |
| 1399 |
if ($how_far_ahead < 300) $how_far_ahead=300; |
| 1400 |
$schedule_for = time() + $how_far_ahead; |
| 1401 |
$this->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)"); |
| 1402 |
wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $this->nonce)); |
| 1403 |
$this->newresumption_scheduled = $schedule_for; |
| 1404 |
} |
| 1405 |
|
| 1406 |
function increase_resume_and_reschedule($howmuch = 120, $force_schedule = false) { |
| 1407 |
|
| 1408 |
$resume_interval = max(intval($this->jobdata_get('resume_interval')), 300); |
| 1409 |
|
| 1410 |
if (empty($this->newresumption_scheduled) && $force_schedule) { |
| 1411 |
$this->log("A new resumption will be scheduled to prevent the job ending"); |
| 1412 |
} |
| 1413 |
|
| 1414 |
$new_resume = $resume_interval + $howmuch; |
| 1415 |
# It may be that we're increasing for the second (or more) time during a run, and that we already know that the new value will be insufficient, and can be increased |
| 1416 |
if ($this->opened_log_time > 100 && microtime(true)-$this->opened_log_time > $new_resume) { |
| 1417 |
$new_resume = ceil(microtime(true)-$this->opened_log_time)+45; |
| 1418 |
$howmuch = $new_resume-$resume_interval; |
| 1419 |
} |
| 1420 |
|
| 1421 |
if (!empty($this->newresumption_scheduled) || $force_schedule) $this->reschedule($new_resume); |
| 1422 |
$this->jobdata_set('resume_interval', $new_resume); |
| 1423 |
|
| 1424 |
$this->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = $new_resume"); |
| 1425 |
} |
| 1426 |
|
| 1427 |
// For detecting another run, and aborting if one was found |
| 1428 |
function check_recent_modification($file) { |
| 1429 |
if (file_exists($file)) { |
| 1430 |
$time_mod = (int)@filemtime($file); |
| 1431 |
$time_now = time(); |
| 1432 |
if ($time_mod>100 && ($time_now-$time_mod)<30) { |
| 1433 |
$this->terminate_due_to_activity($file, $time_now, $time_mod); |
| 1434 |
} |
| 1435 |
} |
| 1436 |
} |
| 1437 |
|
| 1438 |
public function really_is_writable($dir) { |
| 1439 |
// Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks. |
| 1440 |
if (!@is_writable($dir)) return false; |
| 1441 |
// Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed |
| 1442 |
$rand_file = "$dir/test-".md5(rand().time()).".txt"; |
| 1443 |
while (file_exists($rand_file)) { |
| 1444 |
$rand_file = "$dir/test-".md5(rand().time()).".txt"; |
| 1445 |
} |
| 1446 |
$ret = @file_put_contents($rand_file, 'testing...'); |
| 1447 |
@unlink($rand_file); |
| 1448 |
return ($ret > 0); |
| 1449 |
} |
| 1450 |
|
| 1451 |
function backup_others_dirlist() { |
| 1452 |
# Create an array of directories to be skipped |
| 1453 |
$others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE)); |
| 1454 |
# Make the values into the keys |
| 1455 |
$others_skip = array_flip($others_skip); |
| 1456 |
|
| 1457 |
$possible_backups_dirs = array_flip($this->get_backupable_file_entities(false)); |
| 1458 |
|
| 1459 |
$other_dirlist = $this->compile_folder_list_for_backup(WP_CONTENT_DIR, $possible_backups_dirs, $others_skip); |
| 1460 |
|
| 1461 |
return $other_dirlist; |
| 1462 |
|
| 1463 |
} |
| 1464 |
|
| 1465 |
/** |
| 1466 |
* Add backquotes to tables and db-names in |
| 1467 |
* SQL queries. Taken from phpMyAdmin. |
| 1468 |
*/ |
| 1469 |
public function backquote($a_name) { |
| 1470 |
if (!empty($a_name) && $a_name != '*') { |
| 1471 |
if (is_array($a_name)) { |
| 1472 |
$result = array(); |
| 1473 |
reset($a_name); |
| 1474 |
while(list($key, $val) = each($a_name)) |
| 1475 |
$result[$key] = '`' . $val . '`'; |
| 1476 |
return $result; |
| 1477 |
} else { |
| 1478 |
return '`' . $a_name . '`'; |
| 1479 |
} |
| 1480 |
} else { |
| 1481 |
return $a_name; |
| 1482 |
} |
| 1483 |
} |
| 1484 |
|
| 1485 |
// avoid_these_dirs and skip_these_dirs ultimately do the same thing; but avoid_these_dirs takes full paths whereas skip_these_dirs takes basenames; and they are logged differently (dirs in avoid are potentially dangerous to include; skip is just a user-level preference). They are allowed to overlap. |
| 1486 |
function compile_folder_list_for_backup($backup_from_inside_dir, $avoid_these_dirs, $skip_these_dirs) { |
| 1487 |
|
| 1488 |
// Entries in $skip_these_dirs are allowed to end in *, which means "and anything else as a suffix". It's not a full shell glob, but it covers what is needed to-date. |
| 1489 |
|
| 1490 |
$dirlist = array(); |
| 1491 |
|
| 1492 |
$this->log('Looking for candidates to back up in: '.$backup_from_inside_dir); |
| 1493 |
|
| 1494 |
$updraft_dir = $this->backups_dir_location(); |
| 1495 |
if ($handle = opendir($backup_from_inside_dir)) { |
| 1496 |
|
| 1497 |
while (false !== ($entry = readdir($handle))) { |
| 1498 |
// $candidate: full path; $entry = one-level |
| 1499 |
$candidate = $backup_from_inside_dir.'/'.$entry; |
| 1500 |
if ($entry != "." && $entry != "..") { |
| 1501 |
if (isset($avoid_these_dirs[$candidate])) { |
| 1502 |
$this->log("finding files: $entry: skipping: this is the ".$avoid_these_dirs[$candidate]." directory"); |
| 1503 |
} elseif ($candidate == $updraft_dir) { |
| 1504 |
$this->log("finding files: $entry: skipping: this is the updraft directory"); |
| 1505 |
} elseif (isset($skip_these_dirs[$entry])) { |
| 1506 |
$this->log("finding files: $entry: skipping: excluded by options"); |
| 1507 |
} else { |
| 1508 |
$add_to_list = true; |
| 1509 |
// Now deal with entries in $skip_these_dirs ending in * |
| 1510 |
foreach ($skip_these_dirs as $skip => $sind) { |
| 1511 |
if ('*' == substr($skip, -1, 1)) { |
| 1512 |
if (substr($entry, 0, strlen($skip)-1) == substr($skip, 0, strlen($skip)-1)) { |
| 1513 |
$this->log("finding files: $entry: skipping: excluded by options (glob)"); |
| 1514 |
$add_to_list = false; |
| 1515 |
} |
| 1516 |
} |
| 1517 |
} |
| 1518 |
if ($add_to_list) { |
| 1519 |
$this->log("finding files: $entry: adding to list"); |
| 1520 |
array_push($dirlist, $candidate); |
| 1521 |
} |
| 1522 |
} |
| 1523 |
} |
| 1524 |
} |
| 1525 |
@closedir($handle); |
| 1526 |
} else { |
| 1527 |
$this->log('ERROR: Could not read the directory: '.$backup_from_inside_dir); |
| 1528 |
$this->log(__('Could not read the directory', 'updraftplus').': '.$backup_from_inside_dir, 'error'); |
| 1529 |
} |
| 1530 |
|
| 1531 |
return $dirlist; |
| 1532 |
|
| 1533 |
} |
| 1534 |
|
| 1535 |
function save_backup_history($backup_array) { |
| 1536 |
if(is_array($backup_array)) { |
| 1537 |
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); |
| 1538 |
$backup_history = (is_array($backup_history)) ? $backup_history : array(); |
| 1539 |
$backup_array['nonce'] = $this->nonce; |
| 1540 |
$backup_array['service'] = $this->jobdata_get('service'); |
| 1541 |
$backup_history[$this->backup_time] = $backup_array; |
| 1542 |
UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history); |
| 1543 |
} else { |
| 1544 |
$this->log('Could not save backup history because we have no backup array. Backup probably failed.'); |
| 1545 |
$this->log(__('Could not save backup history because we have no backup array. Backup probably failed.','updraftplus'), 'error'); |
| 1546 |
} |
| 1547 |
} |
| 1548 |
|
| 1549 |
public function is_db_encrypted($file) { |
| 1550 |
return preg_match('/\.crypt$/i', $file); |
| 1551 |
} |
| 1552 |
|
| 1553 |
public function get_backup_history($timestamp = false) { |
| 1554 |
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history'); |
| 1555 |
// In fact, it looks like the line below actually *introduces* a race condition |
| 1556 |
//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 |
| 1557 |
// global $wpdb; |
| 1558 |
// $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'"))); |
| 1559 |
if(is_array($backup_history)) { |
| 1560 |
krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop. |
| 1561 |
} else { |
| 1562 |
$backup_history = array(); |
| 1563 |
} |
| 1564 |
if (!$timestamp) return $backup_history; |
| 1565 |
return (isset($backup_history[$timestamp])) ? $backup_history[$timestamp] : array(); |
| 1566 |
} |
| 1567 |
|
| 1568 |
function terminate_due_to_activity($file, $time_now, $time_mod) { |
| 1569 |
# We check-in, to avoid 'no check in last time!' detectors firing |
| 1570 |
$this->record_still_alive(); |
| 1571 |
$file_size = file_exists($file) ? round(filesize($file)/1024,1). 'Kb' : 'n/a'; |
| 1572 |
$this->log("Terminate: ".basename($file)." exists with activity within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".(floor($time_now-$time_mod)).", size=$file_size). This likely means that another UpdraftPlus run is at work; so we will exit."); |
| 1573 |
$this->increase_resume_and_reschedule(120, true); |
| 1574 |
die; |
| 1575 |
} |
| 1576 |
|
| 1577 |
# Replace last occurence |
| 1578 |
public function str_lreplace($search, $replace, $subject) { |
| 1579 |
$pos = strrpos($subject, $search); |
| 1580 |
if($pos !== false) $subject = substr_replace($subject, $replace, $pos, strlen($search)); |
| 1581 |
return $subject; |
| 1582 |
} |
| 1583 |
|
| 1584 |
public function str_replace_once($needle, $replace, $haystack) { |
| 1585 |
$pos = strpos($haystack,$needle); |
| 1586 |
return ($pos !== false) ? substr_replace($haystack,$replace,$pos,strlen($needle)) : $haystack; |
| 1587 |
} |
| 1588 |
|
| 1589 |
/* |
| 1590 |
this function is both the backup scheduler and ostensibly a filter callback for saving the option. |
| 1591 |
it is called in the register_setting for the updraft_interval, which means when the admin settings |
| 1592 |
are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter) |
| 1593 |
so the option can be properly saved. |
| 1594 |
*/ |
| 1595 |
public function schedule_backup($interval) { |
| 1596 |
//clear schedule and add new so we don't stack up scheduled backups |
| 1597 |
wp_clear_scheduled_hook('updraft_backup'); |
| 1598 |
switch($interval) { |
| 1599 |
case 'every4hours': |
| 1600 |
case 'every8hours': |
| 1601 |
case 'twicedaily': |
| 1602 |
case 'daily': |
| 1603 |
case 'weekly': |
| 1604 |
case 'fortnightly': |
| 1605 |
case 'monthly': |
| 1606 |
$first_time = apply_filters('updraftplus_schedule_firsttime_files', time()+30); |
| 1607 |
wp_schedule_event($first_time, $interval, 'updraft_backup'); |
| 1608 |
break; |
| 1609 |
} |
| 1610 |
return wp_filter_nohtml_kses($interval); |
| 1611 |
} |
| 1612 |
|
| 1613 |
public function deactivation () { |
| 1614 |
// wp_clear_scheduled_hook('updraftplus_weekly_ping'); |
| 1615 |
} |
| 1616 |
|
| 1617 |
// Acts as a WordPress options filter |
| 1618 |
public function googledrive_clientid_checkchange($client_id) { |
| 1619 |
if (UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid') != $client_id) { |
| 1620 |
require_once(UPDRAFTPLUS_DIR.'/methods/googledrive.php'); |
| 1621 |
add_action('http_api_curl', array($this, 'add_curl_capath')); |
| 1622 |
UpdraftPlus_BackupModule_googledrive::gdrive_auth_revoke(true); |
| 1623 |
remove_action('http_api_curl', array($this, 'add_curl_capath')); |
| 1624 |
|
| 1625 |
} |
| 1626 |
return $client_id; |
| 1627 |
} |
| 1628 |
|
| 1629 |
public function schedule_backup_database($interval) { |
| 1630 |
//clear schedule and add new so we don't stack up scheduled backups |
| 1631 |
wp_clear_scheduled_hook('updraft_backup_database'); |
| 1632 |
switch($interval) { |
| 1633 |
case 'every4hours': |
| 1634 |
case 'every8hours': |
| 1635 |
case 'twicedaily': |
| 1636 |
case 'daily': |
| 1637 |
case 'weekly': |
| 1638 |
case 'fortnightly': |
| 1639 |
case 'monthly': |
| 1640 |
$first_time = apply_filters('updraftplus_schedule_firsttime_db', time()+30); |
| 1641 |
wp_schedule_event($first_time, $interval, 'updraft_backup_database'); |
| 1642 |
break; |
| 1643 |
} |
| 1644 |
return wp_filter_nohtml_kses($interval); |
| 1645 |
} |
| 1646 |
|
| 1647 |
//wp-cron only has hourly, daily and twicedaily, so we need to add some of our own |
| 1648 |
public function modify_cron_schedules($schedules) { |
| 1649 |
$schedules['weekly'] = array( 'interval' => 604800, 'display' => 'Once Weekly' ); |
| 1650 |
$schedules['fortnightly'] = array( 'interval' => 1209600, 'display' => 'Once Each Fortnight' ); |
| 1651 |
$schedules['monthly'] = array( 'interval' => 2592000, 'display' => 'Once Monthly' ); |
| 1652 |
$schedules['every4hours'] = array( 'interval' => 14400, 'display' => 'Every 4 hours' ); |
| 1653 |
$schedules['every8hours'] = array( 'interval' => 28800, 'display' => 'Every 8 hours' ); |
| 1654 |
return $schedules; |
| 1655 |
} |
| 1656 |
|
| 1657 |
// Returns without any trailing slash |
| 1658 |
public 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 |
# When newly installing, if someone had (e.g.) wp-content/updraft in their database from a previous, deleted pre-1.7.18 install but had removed the updraft directory before re-installing, without this fix they'd end up with wp-content/wp-content/updraft. |
| 1664 |
if (preg_match('/^wp-content\/(.*)$/', $updraft_dir, $matches) && ABSPATH.'wp-content' === WP_CONTENT_DIR) { |
| 1665 |
UpdraftPlus_Options::update_updraft_option('updraft_dir', $matches[1]); |
| 1666 |
$updraft_dir = WP_CONTENT_DIR.'/'.$matches[1]; |
| 1667 |
} |
| 1668 |
$default_backup_dir = WP_CONTENT_DIR.'/updraft'; |
| 1669 |
$updraft_dir = ($updraft_dir) ? $updraft_dir : $default_backup_dir; |
| 1670 |
|
| 1671 |
// Do a test for a relative path |
| 1672 |
if ('/' != substr($updraft_dir, 0, 1) && "\\" != substr($updraft_dir, 0, 1) && !preg_match('/^[a-zA-Z]:/', $updraft_dir)) { |
| 1673 |
# Legacy - file paths stored related to ABSPATH |
| 1674 |
if (is_dir(ABSPATH.$updraft_dir) && is_file(ABSPATH.$updraft_dir.'/index.html') && is_file(ABSPATH.$updraft_dir.'/.htaccess') && !is_file(ABSPATH.$updraft_dir.'/index.php') && false !== strpos(file_get_contents(ABSPATH.$updraft_dir.'/.htaccess', false, null, 0, 20), 'deny from all')) { |
| 1675 |
$updraft_dir = ABSPATH.$updraft_dir; |
| 1676 |
} else { |
| 1677 |
# File paths stored relative to WP_CONTENT_DIR |
| 1678 |
$updraft_dir = trailingslashit(WP_CONTENT_DIR).$updraft_dir; |
| 1679 |
} |
| 1680 |
} |
| 1681 |
|
| 1682 |
// Check for the existence of the dir and prevent enumeration |
| 1683 |
// index.php is for a sanity check - make sure that we're not somewhere unexpected |
| 1684 |
if((!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) && !is_file($updraft_dir.'/index.php')) { |
| 1685 |
@mkdir($updraft_dir, 0775, true); |
| 1686 |
@file_put_contents($updraft_dir.'/index.html',"<html><body><a href=\"http://updraftplus.com\">WordPress backups by UpdraftPlus</a></body></html>"); |
| 1687 |
if (!is_file($updraft_dir.'/.htaccess')) @file_put_contents($updraft_dir.'/.htaccess','deny from all'); |
| 1688 |
} |
| 1689 |
|
| 1690 |
$this->backup_dir = $updraft_dir; |
| 1691 |
|
| 1692 |
return $updraft_dir; |
| 1693 |
} |
| 1694 |
|
| 1695 |
public function spool_file($type, $fullpath, $encryption = "") { |
| 1696 |
|
| 1697 |
@set_time_limit(900); |
| 1698 |
|
| 1699 |
if (file_exists($fullpath)) { |
| 1700 |
|
| 1701 |
$file = basename($fullpath); |
| 1702 |
|
| 1703 |
$len = filesize($fullpath); |
| 1704 |
|
| 1705 |
$filearr = explode('.',$file); |
| 1706 |
|
| 1707 |
$file_ext = array_pop($filearr); |
| 1708 |
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 |
| 1709 |
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past |
| 1710 |
header("Content-Length: $len;"); |
| 1711 |
|
| 1712 |
if ($file_ext == 'crypt') { |
| 1713 |
if ($encryption == "") $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase'); |
| 1714 |
if ($encryption == "") { |
| 1715 |
header('Content-type: text/plain'); |
| 1716 |
_e("Decryption failed. The database file is encrypted, but you have no encryption key entered.",'updraftplus'); |
| 1717 |
$this->log('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.', 'error'); |
| 1718 |
} else { |
| 1719 |
$this->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael'); |
| 1720 |
$rijndael = new Crypt_Rijndael(); |
| 1721 |
$rijndael->setKey($encryption); |
| 1722 |
$ciphertext = $rijndael->decrypt(file_get_contents($fullpath)); |
| 1723 |
if ($ciphertext) { |
| 1724 |
header('Content-type: application/x-gzip'); |
| 1725 |
header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";"); |
| 1726 |
print $ciphertext; |
| 1727 |
} else { |
| 1728 |
header('Content-type: text/plain'); |
| 1729 |
echo __("Decryption failed. The most likely cause is that you used the wrong key.",'updraftplus')." ".__('The decryption key used:','updraftplus').' '.$encryption; |
| 1730 |
|
| 1731 |
} |
| 1732 |
} |
| 1733 |
} else { |
| 1734 |
if ($file_ext == 'zip') { |
| 1735 |
header('Content-type: application/zip'); |
| 1736 |
} else { |
| 1737 |
// When we sent application/x-gzip, we found a case where the server compressed it a second time |
| 1738 |
header('Content-type: application/octet-stream'); |
| 1739 |
} |
| 1740 |
header("Content-Disposition: attachment; filename=\"$file\";"); |
| 1741 |
# Prevent the file being read into memory |
| 1742 |
@ob_end_flush(); |
| 1743 |
readfile($fullpath); |
| 1744 |
} |
| 1745 |
// $this->delete_local($file); |
| 1746 |
} else { |
| 1747 |
echo __('File not found', 'updraftplus'); |
| 1748 |
} |
| 1749 |
} |
| 1750 |
|
| 1751 |
public function retain_range($input) { |
| 1752 |
$input = (int)$input; |
| 1753 |
return ($input > 0 && $input < 3650) ? $input : 1; |
| 1754 |
} |
| 1755 |
|
| 1756 |
public function just_one($input) { |
| 1757 |
$oinput = $input; |
| 1758 |
$rinput = (is_array($input)) ? array_pop($input) : $input; |
| 1759 |
return apply_filters('updraftplus_savestorage', $rinput, $oinput); |
| 1760 |
} |
| 1761 |
|
| 1762 |
function memory_check_current($memory_limit = false) { |
| 1763 |
# Returns in megabytes |
| 1764 |
if ($memory_limit == false) $memory_limit = ini_get('memory_limit'); |
| 1765 |
$memory_limit = rtrim($memory_limit); |
| 1766 |
$memory_unit = $memory_limit[strlen($memory_limit)-1]; |
| 1767 |
$memory_limit = substr($memory_limit,0,strlen($memory_limit)-1); |
| 1768 |
switch($memory_unit) { |
| 1769 |
case 'K': |
| 1770 |
$memory_limit = floor($memory_limit/1024); |
| 1771 |
break; |
| 1772 |
case 'G': |
| 1773 |
$memory_limit = $memory_limit*1024; |
| 1774 |
break; |
| 1775 |
case 'M': |
| 1776 |
//assumed size, no change needed |
| 1777 |
break; |
| 1778 |
} |
| 1779 |
return $memory_limit; |
| 1780 |
} |
| 1781 |
|
| 1782 |
function memory_check($memory, $check_using = false) { |
| 1783 |
$memory_limit = $this->memory_check_current($check_using); |
| 1784 |
return ($memory_limit >= $memory)?true:false; |
| 1785 |
} |
| 1786 |
|
| 1787 |
private function url_start($urls,$url) { |
| 1788 |
return ($urls) ? '<a href="http://'.$url.'">' : ""; |
| 1789 |
} |
| 1790 |
|
| 1791 |
private function url_end($urls,$url) { |
| 1792 |
return ($urls) ? '</a>' : " (http://$url)"; |
| 1793 |
} |
| 1794 |
|
| 1795 |
public function wordshell_random_advert($urls) { |
| 1796 |
if (defined('UPDRAFTPLUS_NOADS3')) return ""; |
| 1797 |
$rad = rand(0,8); |
| 1798 |
switch ($rad) { |
| 1799 |
case 0: |
| 1800 |
return $this->url_start($urls,'updraftplus.com')."Want more features or paid, guaranteed support? Check out UpdraftPlus.Com".$this->url_end($urls,'updraftplus.com'); |
| 1801 |
break; |
| 1802 |
case 1: |
| 1803 |
if (defined('WPLANG') && strlen(WPLANG)>0 && !is_file(UPDRAFTPLUS_DIR.'/languages/updraftplus-'.WPLANG. |
| 1804 |
'.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/'); |
| 1805 |
|
| 1806 |
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'); |
| 1807 |
break; |
| 1808 |
case 2: |
| 1809 |
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"; |
| 1810 |
break; |
| 1811 |
case 3: |
| 1812 |
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'); |
| 1813 |
break; |
| 1814 |
case 4: |
| 1815 |
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'); |
| 1816 |
break; |
| 1817 |
case 5: |
| 1818 |
if (!defined('UPDRAFTPLUS_NOADS3')) { |
| 1819 |
return $this->url_start($urls,'updraftplus.com').__("Need even more features and support? Check out UpdraftPlus Premium",'updraftplus').$this->url_end($urls,'updraftplus.com'); |
| 1820 |
} else { |
| 1821 |
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."; |
| 1822 |
} |
| 1823 |
break; |
| 1824 |
case 6: |
| 1825 |
// 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/'); |
| 1826 |
return __("Subscribe to the UpdraftPlus blog to get up-to-date news and offers",'updraftplus')." - ".$this->url_start($urls,'updraftplus.com/news/').__("Blog link",'updraftplus').$this->url_end($urls,'updraftplus.com/news/').' - '.$this->url_start($urls,'feeds.feedburner.com/UpdraftPlus').__("RSS link",'updraftplus').$this->url_end($urls,'feeds.feedburner.com/UpdraftPlus'); |
| 1827 |
break; |
| 1828 |
case 7: |
| 1829 |
return $this->url_start($urls,'updraftplus.com').__("Check out UpdraftPlus.Com for help, add-ons and support",'updraftplus').$this->url_end($urls,'updraftplus.com'); |
| 1830 |
break; |
| 1831 |
case 8: |
| 1832 |
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/'); |
| 1833 |
break; |
| 1834 |
} |
| 1835 |
} |
| 1836 |
|
| 1837 |
} |
| 1838 |
|
| 1839 |
|
| 1840 |
?> |
| 1841 |
|