PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.4.6
UpdraftPlus: WP Backup & Migration Plugin v1.4.6
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / updraftplus.php

updraftplus.php in UpdraftPlus: WP Backup & Migration Plugin 1.4.6, at updraftplus.php

2,355 lines 108.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: UpdraftPlus - Backup/Restore
4 Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5 Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
6 Author: David Anderson.
7 Version: 1.4.6
8 Donate link: http://david.dw-perspective.org.uk/donate
9 License: GPLv3 or later
10 Author URI: http://wordshell.net
11 */
12
13 /*
14 TODO
15 //Put in old-WP-version warning, and point them to where they can get help
16 //Add SFTP, Box.Net, SugarSync and Microsoft Skydrive support??
17 //The restorer has a hard-coded wp-content - fix
18 //Button for wiping files. Also auto-wipe on de-activate/de-install.
19 //Change DB encryption to not require whole gzip in memory (twice)
20 //improve error reporting / pretty up return messages in admin area. One thing: have a "backup is now finished" flag. Otherwise with the resuming things get ambiguous/confusing. See http://wordpress.org/support/topic/backup-status - user was not aware that backup completely failed. Maybe a "backup status" field for each nonce that gets updated? (Even via AJAX?)
21 //?? On 'backup now', open up a Lightbox, count down 5 seconds, then start examining the log file (if it can be found)
22 //Should make clear in dashboard what is a non-fatal error (i.e. can be retried) - leads to unnecessary bug reports
23 // 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.
24 // 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??
25 // Warn the user if their zip-file creation is slooowww...
26 // Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests...
27 // Resuming partial FTP uploads
28 // 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
29 // Multiple jobs
30 // Expert setting: force PCLZip
31 // Don't stop at 10 retries if something useful is still measurably being done (in particular, chunked uploads are proceeding - set a flag to indicate "try it again")
32 // Change FTP to use SSL by default
33 // When looking for files to delete, is the current encryption setting used? Should not be.
34 // Create single zip, containing even WordPress itself
35 // When a new backup starts, AJAX-update the 'Last backup' display in the admin page.
36 // Remove the recurrence of admin notices when settings are saved due to _wp_referer
37 // Auto-detect what the real execution time is (max_execution_time is just one of the upper limits, there can be others, some insivible directly), and tweak our resumption time accordingly
38 //http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/
39 // Specify the exact time to run the backup (useful if you have big site, using a lot of CPU)
40
41 Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
42 // Does not delete old custom directories upon a restore?
43 // Re-do making of zip files to allow resumption (every x files, store the state in a transient)
44 // New sub-module to verify that the backups are there, independently of backup thread
45 */
46
47 /* Portions copyright 2010 Paul Kehrer
48 Portions copyright 2011-12 David Anderson
49 Other portions copyright as indicated authors in the relevant files
50 Particular thanks to Sorin Iclanzan, author of the "Backup" plugin, from which much Google Drive code was taken under the GPLv3+
51
52 This program is free software; you can redistribute it and/or modify
53 it under the terms of the GNU General Public License as published by
54 the Free Software Foundation; either version 3 of the License, or
55 (at your option) any later version.
56
57 This program is distributed in the hope that it will be useful,
58 but WITHOUT ANY WARRANTY; without even the implied warranty of
59 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60 GNU General Public License for more details.
61
62 You should have received a copy of the GNU General Public License
63 along with this program; if not, write to the Free Software
64 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
65 */
66
67 // 15 minutes
68 @set_time_limit(900);
69
70 define('UPDRAFTPLUS_DIR', dirname(__FILE__));
71 define('UPDRAFTPLUS_URL', plugins_url('', __FILE__));
72 define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php,backup,backups');
73 // This is used in various places, based on our assumption of the maximum time any job should take. May need lengthening in future if we get reports which show enormous sets hitting the limit.
74 // Also one section requires at least 1% progress each run, so on a 5-minute schedule, that equals just under 9 hours
75 define('UPDRAFT_TRANSTIME', 3600*9);
76
77 // Load add-ons
78 if (is_file(UPDRAFTPLUS_DIR.'/premium.php')) require_once(UPDRAFTPLUS_DIR.'/premium.php');
79
80 if ($dir_handle = @opendir(UPDRAFTPLUS_DIR.'/addons')) {
81 while ($e = readdir($dir_handle)) {
82 if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e)) {
83 include_once(UPDRAFTPLUS_DIR.'/addons/'.$e);
84 }
85 }
86 }
87
88 if (!isset($updraftplus)) $updraftplus = new UpdraftPlus();
89
90 if (!$updraftplus->memory_check(192)) {
91 # TODO: Better solution is to split the backup set into manageable chunks based on this limit
92 @ini_set('memory_limit', '192M'); //up the memory limit for large backup files
93 }
94
95 if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options.php');
96
97 class UpdraftPlus {
98
99 var $version = '1.4.6';
100 var $plugin_title = 'UpdraftPlus Backup/Restore';
101
102 // Choices will be shown in the admin menu in the order used here
103 var $backup_methods = array (
104 "s3" => "Amazon S3",
105 "dropbox" => "Dropbox",
106 "googledrive" => "Google Drive",
107 "ftp" => "FTP",
108 "email" => "Email"
109 );
110
111 var $dbhandle;
112 var $dbhandle_isgz;
113 var $errors = array();
114 var $nonce;
115 var $logfile_name = "";
116 var $logfile_handle = false;
117 var $backup_time;
118
119 var $opened_log_time;
120 var $backup_dir;
121
122 var $jobdata;
123
124 // Used to schedule resumption attempts beyond the tenth, if needed
125 var $current_resumption;
126 var $newresumption_scheduled = false;
127
128 var $zipfiles_added;
129 var $zipfiles_existingfiles;
130 var $zipfiles_dirbatched;
131 var $zipfiles_batched;
132
133 function __construct() {
134 // Initialisation actions - takes place on plugin load
135 # Create admin page
136 add_action('admin_init', array($this, 'admin_init'));
137 add_action('updraft_backup', array($this,'backup_files'));
138 add_action('updraft_backup_database', array($this,'backup_database'));
139 # backup_all is used by the manual "Backup Now" button
140 add_action('updraft_backup_all', array($this,'backup_all'));
141 # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
142 add_action('updraft_backup_resume', array($this,'backup_resume'), 10, 3);
143 add_action('wp_enqueue_scripts', array($this, 'ajax_enqueue') );
144 add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
145 add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler'));
146 # http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules
147 add_filter('cron_schedules', array($this,'modify_cron_schedules'));
148 add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
149 add_action('init', array($this, 'handle_url_actions'));
150
151 }
152
153 // Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?page=updraftplus&action=updraftmethod-googledrive-auth
154 // Also handle action=downloadlog
155 function handle_url_actions() {
156 // First, basic security check: must be an admin page, with ability to manage options, with the right parameters
157 if ( UpdraftPlus_Options::user_can_manage() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) {
158 if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php')) {
159 $method = $matches[1];
160 require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
161 $call_class = "UpdraftPlus_BackupModule_".$method;
162 $call_method = "action_".$matches[2];
163 if (method_exists($call_class, $call_method)) call_user_func(array($call_class,$call_method));
164 } elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) {
165 $updraft_dir = $this->backups_dir_location();
166 $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt';
167 if (is_readable($log_file)) {
168 header('Content-type: text/plain');
169 readfile($log_file);
170 exit;
171 } else {
172 add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') );
173 }
174 }
175 }
176 }
177
178 # Adds the settings link under the plugin on the plugin screen.
179 function plugin_action_links($links, $file) {
180 if ($file == plugin_basename(__FILE__)){
181 $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
182 array_unshift($links, $settings_link);
183 $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
184 array_unshift($links, $settings_link);
185 }
186 return $links;
187 }
188
189 function backup_time_nonce() {
190 $this->backup_time = time();
191 $nonce = substr(md5(time().rand()), 20);
192 $this->nonce = $nonce;
193 }
194
195 function logfile_open($nonce) {
196 //set log file name and open log file
197 $updraft_dir = $this->backups_dir_location();
198 $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
199 // Use append mode in case it already exists
200 $this->logfile_handle = fopen($this->logfile_name, 'a');
201 $this->opened_log_time = microtime(true);
202 $this->log("Opened log file at time: ".date('r'));
203 global $wp_version;
204 $logline = "UpdraftPlus: ".$this->version." WordPress: ".$wp_version." PHP: ".phpversion()." (".@php_uname().") PHP Max Execution Time: ".@ini_get("max_execution_time")." ZipArchive::addFile exists: ";
205 $logline .= (method_exists('ZipArchive', 'addFile')) ? "Y" : "N";
206 $this->log($logline);
207 }
208
209 # Logs the given line, adding (relative) time stamp and newline
210 function log($line) {
211 if ($this->logfile_handle) fwrite($this->logfile_handle, sprintf("%08.03f", round(microtime(true)-$this->opened_log_time, 3))." ".$line."\n");
212 UpdraftPlus_Options::update_updraft_option("updraft_lastmessage", $line." (".date('M d H:i:s').")");
213 }
214
215 // 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
216 function record_uploaded_chunk($percent, $extra) {
217 // Log it
218 $service = $this->jobdata_get('service');
219 $log = ucfirst($service)." chunked upload: $percent % uploaded";
220 if ($extra) $log .= " ($extra)";
221 $this->log($log);
222 // If we are on an 'overtime' resumption run, and we are still meainingfully uploading, then schedule a new resumption
223 // Our definition of meaningful is that we must maintain an overall average of at least 1% per run, after allowing 5 runs for everything else to get going
224 // i.e. Max 100 runs = 500 minutes = 8 hrs 40
225 // If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 87Kb/s upload speed required
226
227 if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false && $percent > ( $this->current_resumption - 5)) {
228 $resume_interval = $this->jobdata_get('resume_interval');
229 if (!is_numeric($resume_interval) || $resume_interval<200) { $resume_interval = 200; }
230 $schedule_for = time()+$resume_interval;
231 $this->newresumption_scheduled = $schedule_for;
232 $this->log("This is resumption ".$this->current_resumption.", but meaningful uploading is still taking place; so a new one will be scheduled");
233 wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce, $this->backup_time));
234 }
235 }
236
237 function backup_resume($resumption_no, $bnonce, $btime) {
238
239 @ignore_user_abort(true);
240 // This is scheduled for 5 minutes after a backup job starts
241
242 // Restore state
243 if ($resumption_no > 0) {
244 $this->nonce = $bnonce;
245 $this->backup_time = $btime;
246 $this->logfile_open($bnonce);
247 }
248
249 $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
250 $this->current_resumption = $resumption_no;
251
252 // Schedule again, to run in 5 minutes again, in case we again fail
253 // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
254 $resume_interval = $this->jobdata_get('resume_interval');
255 if (!is_numeric($resume_interval) || $resume_interval<200) $resume_interval = 200;
256
257 // A different argument than before is needed otherwise the event is ignored
258 $next_resumption = $resumption_no+1;
259 if ($next_resumption < 10) {
260 $this->log("Scheduling a resumption ($next_resumption) in case this run gets aborted");
261 $schedule_for = time()+$resume_interval;
262 wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce, $btime));
263 $this->newresumption_scheduled = $schedule_for;
264 } else {
265 $this->log("The current run is our tenth attempt - will not schedule a further attempt until we see something useful happening");
266 }
267
268 // This should be always called; if there were no files in this run, it returns us an empty array
269 $backup_array = $this->resumable_backup_of_files($resumption_no);
270 // This save, if there was something, is then immediately picked up again
271 if (is_array($backup_array)) $this->save_backup_history($backup_array);
272
273 // Returns an array, most recent first, of backup sets
274 $backup_history = $this->get_backup_history();
275 if (!isset($backup_history[$btime])) {
276 $this->log("Could not find a record in the database of a backup with this timestamp");
277 }
278
279 $our_files=$backup_history[$btime];
280 if (!is_array($our_files)) $our_files = array();
281
282 $undone_files = array();
283
284 $backup_database = $this->jobdata_get('backup_database');
285
286 // The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed.
287 if ($backup_database == "begun" || $backup_database == "finished" || $backup_database == "encrypted") {
288 if ($backup_database == "begun") {
289 if ($resumption_no > 0) {
290 $this->log("Resuming creation of database dump");
291 } else {
292 $this->log("Beginning creation of database dump");
293 }
294 } elseif ($backup_database == 'encrypted') {
295 $this->log("Database dump: Creation and encryption were completed already");
296 } else {
297 $this->log("Database dump: Creation was completed already");
298 }
299 $db_backup = $this->backup_db($backup_database);
300 if(is_array($our_files) && is_string($db_backup)) $our_files['db'] = $db_backup;
301 if ($backup_database != 'encrypted') $this->jobdata_set("backup_database", 'finished');
302 } else {
303 $this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)");
304 }
305
306 // Save this to our history so we can track backups for the retain feature
307 $this->log("Saving backup history");
308 // 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.
309 $this->save_backup_history($our_files);
310
311 // Potentially encrypt the database if it is not already
312 if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) {
313 $our_files['db'] = $this->encrypt_file($our_files['db']);
314 $this->save_backup_history($our_files);
315 $this->jobdata_set("backup_database", "encrypted");
316 }
317
318 foreach ($our_files as $key => $file) {
319
320 // Only continue if the stored info was about a dump
321 if ($key != 'plugins' && $key != 'themes' && $key != 'others' && $key != 'uploads' && $key != 'db') continue;
322
323 $hash = md5($file);
324 $fullpath = $this->backups_dir_location().'/'.$file;
325 if ($this->jobdata_get("uploaded_$hash") === "yes") {
326 $this->log("$file: $key: This file has already been successfully uploaded");
327 } elseif (is_file($fullpath)) {
328 $this->log("$file: $key: This file has not yet been successfully uploaded: will queue");
329 $undone_files[$key] = $file;
330 } else {
331 $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
332 $this->uploaded_file($file);
333 }
334 }
335
336 if (count($undone_files) == 0) {
337 $this->log("There were no more files that needed uploading; backup job is complete");
338 // No email, as the user probably already got one if something else completed the run
339 $this->backup_finish($next_resumption, true, false, $resumption_no);
340 return;
341 }
342
343 $this->log("Requesting backup of the files that were not successfully uploaded");
344 $this->cloud_backup($undone_files);
345
346 $this->log("Resume backup ($bnonce, $resumption_no): finish run");
347 if (is_array($our_files)) $this->save_last_backup($our_files);
348 $this->backup_finish($next_resumption, true, true, $resumption_no);
349
350 }
351
352 function backup_all() {
353 $this->boot_backup(true,true);
354 }
355
356 function backup_files() {
357 # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
358 $this->boot_backup(true,false);
359 }
360
361 function backup_database() {
362 # Note that nothing will happen if the file backup had the same schedule
363 $this->boot_backup(false,true);
364 }
365
366 function jobdata_set($key, $value) {
367 if (is_array($this->jobdata)) {
368 $this->jobdata[$key] = $value;
369 } else {
370 $this->jobdata = array($key => $value);
371 }
372 set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400);
373 }
374
375 function jobdata_get($key) {
376 if (!is_array($this->jobdata)) {
377 $this->jobdata = get_transient("updraft_jobdata_".$this->nonce);
378 if (!is_array($this->jobdata)) return false;
379 }
380 return (isset($this->jobdata[$key])) ? $this->jobdata[$key] : false;
381 }
382
383 // This uses a transient; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the transient just helps save resources.
384 function resumable_backup_of_files($resumption_no) {
385 //backup directories and return a numerically indexed array of file paths to the backup files
386 $transient_status = $this->jobdata_get("backup_files");
387 if ($transient_status == "finished") {
388 $this->log("Creation of backups of directories: already finished");
389 } elseif ($transient_status == "begun") {
390 if ($resumption_no>0) {
391 $this->log("Creation of backups of directories: had begun; will resume");
392 } else {
393 $this->log("Creation of backups of directories: beginning");
394 }
395 } else {
396 # This is not necessarily a backup run which is meant to contain files at all
397 $this->log("This backup run is not intended for files - skipping");
398 return array();
399 }
400 // We want this array, even if already finished
401 $backup_array = $this->backup_dirs($transient_status);
402 // This can get over-written later
403 $this->jobdata_set('backup_files', 'finished');
404 return $backup_array;
405 }
406
407 // This procedure initiates a backup run
408 function boot_backup($backup_files, $backup_database) {
409
410 @ignore_user_abort(true);
411
412 //generate backup information
413 $this->backup_time_nonce();
414 $this->logfile_open($this->nonce);
415
416 // Log some information that may be helpful
417 $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').")");
418
419 # If the files and database schedules are the same, and if this the file one, then we rope in database too.
420 # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
421 if (UpdraftPlus_Options::get_updraft_option('updraft_interval') == UpdraftPlus_Options::get_updraft_option('updraft_interval_database') || UpdraftPlus_Options::get_updraft_option('updraft_interval_database','xyz') == 'xyz' ) {
422 $backup_database = ($backup_files == true) ? true : false;
423 }
424
425 $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
426
427 # If nothing to be done, then just finish
428 if (!$backup_files && !$backup_database) {
429 $this->backup_finish(1, false, false, 0);
430 return;
431 }
432
433 // Save what *should* be done, to make it resumable from this point on
434 if ($backup_database) $this->jobdata_set("backup_database", "begun");
435 if ($backup_files) $this->jobdata_set("backup_files", "begun");
436 $this->jobdata_set('service', UpdraftPlus_Options::get_updraft_option('updraft_service'));
437
438 // This can be adapted if we see a need
439 $this->jobdata_set('resume_interval', 300);
440
441 // Everthing is now set up; now go
442 $this->backup_resume(0, $this->nonce, $this->backup_time);
443
444 }
445
446 // Encrypts the file if the option is set; returns the basename of the file (according to whether it was encrypted or nto)
447 function encrypt_file($file) {
448 $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
449 if (strlen($encryption) > 0) {
450 $this->log("$file: applying encryption");
451 $encryption_error = 0;
452 $microstart = microtime(true);
453 require_once(UPDRAFTPLUS_DIR.'/includes/Rijndael.php');
454 $rijndael = new Crypt_Rijndael();
455 $rijndael->setKey($encryption);
456 $updraft_dir = $this->backups_dir_location();
457 $file_size = @filesize($updraft_dir.'/'.$file)/1024;
458 if (false === file_put_contents($updraft_dir.'/'.$file.'.crypt' , $rijndael->encrypt(file_get_contents($updraft_dir.'/'.$file)))) {$encryption_error = 1;}
459 if (0 == $encryption_error) {
460 $time_taken = max(0.000001, microtime(true)-$microstart);
461 $this->log("$file: encryption successful: ".round($file_size,1)."Kb in ".round($time_taken,1)."s (".round($file_size/$time_taken, 1)."Kb/s)");
462 # Delete unencrypted file
463 @unlink($updraft_dir.'/'.$file);
464 return basename($file.'.crypt');
465 } else {
466 $this->log("Encryption error occurred when encrypting database. Encryption aborted.");
467 $this->error("Encryption error occurred when encrypting database. Encryption aborted.");
468 return basename($file);
469 }
470 } else {
471 return basename($file);
472 }
473 }
474
475 function backup_finish($cancel_event, $clear_nonce_transient, $allow_email, $resumption_no) {
476
477 // 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.
478 if (empty($this->errors)) {
479 if ($clear_nonce_transient) {
480 $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
481 wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event, $this->nonce, $this->backup_time));
482 // TODO: Delete the job transient (is presently useful for debugging, and only lasts 4 hours)
483 }
484 } else {
485 $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
486 }
487
488 // Send the results email if appropriate, which means:
489 // - The caller allowed it (which is not the case in an 'empty' run)
490 // - And: An email address was set (which must be so in email mode)
491 // And one of:
492 // - Debug mode
493 // - There were no errors (which means we completed and so this is the final run - time for the final report)
494 // - It was the tenth resumption; everything failed
495
496 $send_an_email = false;
497
498 // Make sure that the final status is shown
499 if (empty($this->errors)) {
500 $send_an_email = true;
501 $final_message = "The backup apparently succeeded and is now complete";
502 } elseif ($this->newresumption_scheduled == false) {
503 $send_an_email = true;
504 $final_message = "The backup attempt has finished, apparently unsuccesfully";
505 } else {
506 // There are errors, but a resumption will be attempted
507 $final_message = "The backup has not finished; a resumption is scheduled within 5 minutes";
508 }
509
510 // Now over-ride the decision to send an email, if needed
511 if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
512 $send_an_email = true;
513 $this->log("An email has been scheduled for this job, because we are in debug mode");
514 }
515 // If there's no email address, or the set was empty, that is the final over-ride: don't send
516 if (!$allow_email) {
517 $send_an_email = false;
518 $this->log("No email will be sent - this backup set was empty.");
519 } elseif (UpdraftPlus_Options::get_updraft_option('updraft_email') == '') {
520 $send_an_email = false;
521 $this->log("No email will/can be sent - the user has not configured an email address.");
522 }
523
524 if ($send_an_email) $this->send_results_email();
525
526 $this->log($final_message);
527
528 @fclose($this->logfile_handle);
529
530 // Don't delete the log file now; delete it upon rotation
531 //if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) @unlink($this->logfile_name);
532
533 }
534
535 function send_results_email() {
536
537 $debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode');
538
539 $sendmail_to = UpdraftPlus_Options::get_updraft_option('updraft_email');
540
541 $this->log("Sending email report to: ".substr($sendmail_to, 0, 5)."...");
542
543 $append_log = ($debug_mode && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
544
545 $backup_files = $this->jobdata_get("backup_files");
546 $backup_db = $this->jobdata_get("backup_database");
547
548 if ($backup_files == "finished" && ( $backup_db == "finished" || $backup_db == "encrypted" ) ) {
549 $backup_contains = "Files and database";
550 } elseif ($backup_files == "finished") {
551 $backup_contains = ($backup_db == "begun") ? "Files (database backup has not completed)" : "Files only (database was not part of this particular schedule)";
552 } elseif ($backup_db == "finished" || $backup_db == "encrypted") {
553 $backup_contains = ($backup_files == "begun") ? "Database (files backup has not completed)" : "Database only (files were not part of this particular schedule)";
554 } else {
555 $backup_contains = "Unknown/unexpected error - please raise a support request";
556 }
557
558 wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".$backup_contains."\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
559
560 }
561
562 function save_last_backup($backup_array) {
563 $success = (empty($this->errors)) ? 1 : 0;
564
565 $last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors, 'backup_nonce' => $this->nonce);
566
567 UpdraftPlus_Options::update_updraft_option('updraft_last_backup', $last_backup);
568 }
569
570 // This should be called whenever a file is successfully uploaded
571 function uploaded_file($file, $id = false) {
572 $hash = md5($file);
573 $this->log("Recording as successfully uploaded: $file ($hash)");
574 $this->jobdata_set("uploaded_$hash", "yes");
575 if ($id) {
576 $ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() );
577 $ids[$file] = $id;
578 UpdraftPlus_Options::update_updraft_option('updraft_file_ids',$ids);
579 $this->log("Stored file<->id correlation in database ($file <-> $id)");
580 }
581 // Delete local files immediately if the option is set
582 // Where we are only backing up locally, only the "prune" function should do deleting
583 if ($this->jobdata_get('service') != '' && $this->jobdata_get('service') != 'none') $this->delete_local($file);
584 }
585
586 // Dispatch to the relevant function
587 function cloud_backup($backup_array) {
588
589 $service = $this->jobdata_get('service');
590 $this->log("Cloud backup selection: ".$service);
591 @set_time_limit(900);
592
593 $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
594 if (file_exists($method_include)) require_once($method_include);
595
596 if ($service == "none") {
597 $this->log("No remote despatch: user chose no remote backup service");
598 } else {
599 $this->log("Beginning dispatch of backup to remote");
600 }
601
602 $objname = "UpdraftPlus_BackupModule_${service}";
603 if (method_exists($objname, "backup")) {
604 // New style - external, allowing more plugability
605 $remote_obj = new $objname;
606 $remote_obj->backup($backup_array);
607 } elseif ($service == "none") {
608 $this->prune_retained_backups("none", null, null);
609 }
610 }
611
612 function prune_file($service, $dofile, $method_object = null, $object_passback = null ) {
613 $this->log("Delete this file: $dofile, service=$service");
614 $fullpath = $this->backups_dir_location().'/'.$dofile;
615 // delete it if it's locally available
616 if (file_exists($fullpath)) {
617 $this->log("Deleting local copy ($fullpath)");
618 @unlink($fullpath);
619 }
620
621 // Despatch to the particular method's deletion routine
622 if (!is_null($method_object)) $method_object->delete($dofile, $object_passback);
623 }
624
625 // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
626 function prune_retained_backups($service, $backup_method_object = null, $backup_passback = null) {
627
628 // If they turned off deletion on local backups, then there is nothing to do
629 if (UpdraftPlus_Options::get_updraft_option('updraft_delete_local') == 0 && $service == 'none') {
630 $this->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups");
631 return;
632 }
633
634 $this->log("Retain: beginning examination of existing backup sets");
635
636 // Number of backups to retain - files
637 $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 1);
638 $updraft_retain = (is_numeric($updraft_retain)) ? $updraft_retain : 1;
639 $this->log("Retain files: user setting: number to retain = $updraft_retain");
640
641 // Number of backups to retain - db
642 $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain);
643 $updraft_retain_db = (is_numeric($updraft_retain_db)) ? $updraft_retain_db : 1;
644 $this->log("Retain db: user setting: number to retain = $updraft_retain_db");
645
646 // Returns an array, most recent first, of backup sets
647 $backup_history = $this->get_backup_history();
648 $db_backups_found = 0;
649 $file_backups_found = 0;
650 $this->log("Number of backup sets in history: ".count($backup_history));
651
652 foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
653 // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
654 // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
655 $this->log("Examining backup set with datestamp: $backup_datestamp");
656
657 if (isset($backup_to_examine['db'])) {
658 $db_backups_found++;
659 $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
660 if ($db_backups_found > $updraft_retain_db) {
661 $this->log("$backup_datestamp: over retain limit ($updraft_retain_db); will delete this database");
662 $dofile = $backup_to_examine['db'];
663 if (!empty($dofile)) $this->prune_file($service, $dofile, $backup_method_object, $backup_passback);
664 unset($backup_to_examine['db']);
665 }
666 }
667 if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads']) || isset($backup_to_examine['others'])) {
668 $file_backups_found++;
669 $this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
670 if ($file_backups_found > $updraft_retain) {
671 $this->log("$backup_datestamp: over retain limit ($updraft_retain); will delete this file set");
672 $file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
673 $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
674 $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
675 $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
676 foreach (array($file, $file2, $file3, $file4) as $dofile) {
677 if (!empty($dofile)) $this->prune_file($service, $dofile, $backup_method_object, $backup_passback);
678 }
679 unset($backup_to_examine['plugins']);
680 unset($backup_to_examine['themes']);
681 unset($backup_to_examine['uploads']);
682 unset($backup_to_examine['others']);
683 }
684 }
685 // Delete backup set completely if empty, o/w just remove DB
686 if (count($backup_to_examine) == 0 || (count($backup_to_examine) == 1 && isset($backup_to_examine['nonce']))) {
687 $this->log("$backup_datestamp: this backup set is now empty; will remove from history");
688 unset($backup_history[$backup_datestamp]);
689 if (isset($backup_to_examine['nonce'])) {
690 $fullpath = $this->backups_dir_location().'/log.'.$backup_to_examine['nonce'].'.txt';
691 if (is_file($fullpath)) {
692 $this->log("$backup_datestamp: deleting log file (log.".$backup_to_examine['nonce'].".txt)");
693 @unlink($fullpath);
694 } else {
695 $this->log("$backup_datestamp: corresponding log file not found - must have already been deleted");
696 }
697 } else {
698 $this->log("$backup_datestamp: no nonce record found in the backup set, so cannot delete any remaining log file");
699 }
700 } else {
701 $this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
702 $backup_history[$backup_datestamp] = $backup_to_examine;
703 }
704 }
705 $this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
706 UpdraftPlus_Options::update_updraft_option('updraft_backup_history',$backup_history);
707 }
708
709 function delete_local($file) {
710 if(UpdraftPlus_Options::get_updraft_option('updraft_delete_local')) {
711 $this->log("Deleting local file: $file");
712 //need error checking so we don't delete what isn't successfully uploaded?
713 $fullpath = $this->backups_dir_location().'/'.$file;
714 return unlink($fullpath);
715 }
716 return true;
717 }
718
719 function reschedule($how_far_ahead) {
720 // Reschedule - remove presently scheduled event
721 wp_clear_scheduled_hook('updraft_backup_resume', array($this->current_resumption + 1, $this->nonce, $this->backup_time));
722 // Add new event
723 if ($how_far_ahead < 200) $how_far_ahead=200;
724 $schedule_for = time() + $how_far_ahead;
725 wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce, $this->backup_time));
726 $this->newresumption_scheduled = $schedule_for;
727 }
728
729 function increase_resume_and_reschedule($howmuch = 120) {
730 $resume_interval = $this->jobdata_get('resume_interval');
731 if (!is_numeric($resume_interval) || $resume_interval<200) { $resume_interval = 200; }
732 if ($this->newresumption_scheduled != false) $this->reschedule($resume_interval+$howmuch);
733 $this->jobdata_set('resume_interval', $resume_interval+$howmuch);
734 $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: ".($resume_interval+$howmuch));
735 }
736
737 function create_zip($create_from_dir, $whichone, $create_in_dir, $backup_file_basename) {
738 // Note: $create_from_dir can be an array or a string
739 @set_time_limit(900);
740
741 if ($whichone != "others") $this->log("Beginning creation of dump of $whichone");
742
743 $full_path = $create_in_dir.'/'.$backup_file_basename.'-'.$whichone.'.zip';
744
745 if (file_exists($full_path)) {
746 $this->log("$backup_file_basename-$whichone.zip: this file has already been created");
747 return basename($full_path);
748 }
749
750 // Temporary file, to be able to detect actual completion (upon which, it is renamed)
751
752 // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active
753 $zip_name = $full_path.'.tmp';
754 $time_now = time();
755 $time_mod = (int)@filemtime($zip_name);
756 if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
757 $file_size = filesize($zip_name);
758 $this->log("Terminate: the temporary file $zip_name already exists, and was modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=$file_size). This likely means that another UpdraftPlus run is still at work; so we will exit.");
759 $this->increase_resume_and_reschedule(120);
760 die;
761 } elseif (file_exists($zip_name)) {
762 $this->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")");
763 }
764
765 $microtime_start = microtime(true);
766 # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
767 $zipcode = $this->make_zipfile($create_from_dir, $zip_name);
768 if ($zipcode !== true) {
769 $this->log("ERROR: Zip failure: /*Could not create*/ $whichone zip: code=$zipcode");
770 $this->error("Could not create $whichone zip: code $zipcode. Consult the log file for more information.");
771 return false;
772 } else {
773 rename($full_path.'.tmp', $full_path);
774 $timetaken = max(microtime(true)-$microtime_start, 0.000001);
775 $kbsize = filesize($full_path)/1024;
776 $rate = round($kbsize/$timetaken, 1);
777 $this->log("Created $whichone zip - file size is ".round($kbsize,1)." Kb in ".round($timetaken,1)." s ($rate Kb/s)");
778 }
779
780 return basename($full_path);
781 }
782
783 // This function is resumable
784 function backup_dirs($transient_status) {
785
786 if(!$this->backup_time) $this->backup_time_nonce();
787
788 $updraft_dir = $this->backups_dir_location();
789 if(!is_writable($updraft_dir)) {
790 $this->log('Backup directory is not writable, or does not exist');
791 $this->error('Backup directory is not writable, or does not exist.');
792 return array();
793 }
794
795 //get the blog name and rip out all non-alphanumeric chars other than _
796 $blog_name = str_replace(' ','_',get_bloginfo());
797 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
798 if(!$blog_name) $blog_name = 'non_alpha_name';
799
800 $backup_file_basename = 'backup_'.date('Y-m-d-Hi', $this->backup_time).'_'.$blog_name.'_'.$this->nonce;
801
802 $backup_array = array();
803
804 $wp_themes_dir = WP_CONTENT_DIR.'/themes';
805 $wp_upload_dir = wp_upload_dir();
806 $wp_upload_dir = $wp_upload_dir['basedir'];
807 $wp_plugins_dir = WP_PLUGIN_DIR;
808
809 $possible_backups = array ('plugins' => $wp_plugins_dir, 'themes' => $wp_themes_dir, 'uploads' => $wp_upload_dir);
810
811 # Plugins, themes, uploads
812 foreach ($possible_backups as $youwhat => $whichdir) {
813 if (UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", true)) {
814 if ($transient_status == 'finished') {
815 $backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip';
816 } else {
817 $created = $this->create_zip($whichdir, $youwhat, $updraft_dir, $backup_file_basename);
818 if ($created) $backup_array[$youwhat] = $created;
819 }
820 } else {
821 $this->log("No backup of $youwhat: excluded by user's options");
822 }
823 }
824
825 # Others
826 if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
827
828 if ($transient_status == 'finished') {
829 $backup_array['others'] = $backup_file_basename.'-others.zip';
830 } else {
831 $this->log("Beginning backup of other directories found in the content directory");
832
833 // http://www.phpconcept.net/pclzip/user-guide/53
834 /* First parameter to create is:
835 An array of filenames or dirnames,
836 or
837 A string containing the filename or a dirname,
838 or
839 A string containing a list of filename or dirname separated by a comma.
840 */
841
842 # Initialise
843 $other_dirlist = array();
844
845 $others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
846 # Make the values into the keys
847 $others_skip = array_flip($others_skip);
848
849 $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
850 if ($handle = opendir(WP_CONTENT_DIR)) {
851 while (false !== ($entry = readdir($handle))) {
852 $candidate = WP_CONTENT_DIR.'/'.$entry;
853 if ($entry == "." || $entry == "..") { ; }
854 elseif ($candidate == $updraft_dir) { $this->log("others: $entry: skipping: this is the updraft directory"); }
855 elseif ($candidate == $wp_themes_dir) { $this->log("others: $entry: skipping: this is the themes directory"); }
856 elseif ($candidate == $wp_upload_dir) { $this->log("others: $entry: skipping: this is the uploads directory"); }
857 elseif ($candidate == $wp_plugins_dir) { $this->log("others: $entry: skipping: this is the plugins directory"); }
858 elseif (isset($others_skip[$entry])) { $this->log("others: $entry: skipping: excluded by options"); }
859 else { $this->log("others: $entry: adding to list"); array_push($other_dirlist, $candidate); }
860 }
861 } else {
862 $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
863 $this->error('Could not read the content directory: '.WP_CONTENT_DIR);
864 }
865
866 if (count($other_dirlist)>0) {
867 $created = $this->create_zip($other_dirlist, 'others', $updraft_dir, $backup_file_basename);
868 if ($created) $backup_array['others'] = $created;
869 } else {
870 $this->log("No backup of other directories: there was nothing found to back up");
871 }
872 # If we are not already finished
873 }
874 } else {
875 $this->log("No backup of other directories: excluded by user's options");
876 }
877 return $backup_array;
878 }
879
880 function save_backup_history($backup_array) {
881 if(is_array($backup_array)) {
882 $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
883 $backup_history = (is_array($backup_history)) ? $backup_history : array();
884 $backup_array['nonce'] = $this->nonce;
885 $backup_history[$this->backup_time] = $backup_array;
886 UpdraftPlus_Options::update_updraft_option('updraft_backup_history',$backup_history);
887 } else {
888 $this->log('Could not save backup history because we have no backup array. Backup probably failed.');
889 $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
890 }
891 }
892
893 function get_backup_history() {
894 //$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
895 //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
896 global $wpdb;
897 $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
898 if(is_array($backup_history)) {
899 krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop.
900 } else {
901 $backup_history = array();
902 }
903 return $backup_history;
904 }
905
906 // Open a file, store its filehandle
907 function backup_db_open($file, $allow_gz = true) {
908 if (function_exists('gzopen') && $allow_gz == true) {
909 $this->dbhandle = @gzopen($file, 'w');
910 $this->dbhandle_isgz = true;
911 } else {
912 $this->dbhandle = @fopen($file, 'w');
913 $this->dbhandle_isgz = false;
914 }
915 if(!$this->dbhandle) {
916 $this->log("ERROR: $file: Could not open the backup file for writing");
917 $this->error("$file: Could not open the backup file for writing");
918 }
919 }
920
921 function backup_db_header() {
922
923 //Begin new backup of MySql
924 $this->stow("# " . 'WordPress MySQL database backup' . "\n");
925 $this->stow("#\n");
926 $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
927 $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
928 $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
929 $this->stow("# --------------------------------------------------------\n");
930
931 if (defined("DB_CHARSET")) {
932 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
933 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
934 $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
935 $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
936 }
937 $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n");
938 }
939
940 /* This function is resumable, using the following method:
941 - Each table is written out to ($final_filename).table.tmp
942 - When the writing finishes, it is renamed to ($final_filename).table
943 - When all tables are finished, they are concatenated into the final file
944 */
945 function backup_db($already_done = "begun") {
946
947 // Get the file prefix
948 $updraft_dir = $this->backups_dir_location();
949
950 if(!$this->backup_time) $this->backup_time_nonce();
951 if (!$this->opened_log_time) $this->logfile_open($this->nonce);
952
953 // Get the blog name and rip out all non-alphanumeric chars other than _
954 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', get_bloginfo()));
955 if (!$blog_name) $blog_name = 'non_alpha_name';
956 $file_base = 'backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
957 $backup_file_base = $updraft_dir.'/'.$file_base;
958
959 if ("finished" == $already_done) return basename($backup_file_base.'-db.gz');
960 if ("encrypted" == $already_done) return basename($backup_file_base.'-db.gz.crypt');
961
962 $total_tables = 0;
963
964 global $table_prefix, $wpdb;
965
966 $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
967 $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
968
969 if (!is_writable($updraft_dir)) {
970 $this->log('The backup directory is not writable.');
971 $this->error('The backup directory is not writable.');
972 return false;
973 }
974
975 $stitch_files = array();
976
977 foreach ($all_tables as $table) {
978 $total_tables++;
979 // Increase script execution time-limit to 15 min for every table.
980 if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60);
981 // The table file may already exist if we have produced it on a previous run
982 $table_file_prefix = $file_base.'-db-table-'.$table.'.table';
983 if (file_exists($updraft_dir.'/'.$table_file_prefix.'.gz')) {
984 $this->log("Table $table: corresponding file already exists; moving on");
985 } else {
986 // Open file, store the handle
987 $this->backup_db_open($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true);
988 # === is needed, otherwise 'false' matches (i.e. prefix does not match)
989 if ( strpos($table, $table_prefix) === 0 ) {
990 // Create the SQL statements
991 $this->stow("# --------------------------------------------------------\n");
992 $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
993 $this->stow("# --------------------------------------------------------\n");
994 $this->backup_table($table);
995 } else {
996 $this->stow("# --------------------------------------------------------\n");
997 $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
998 $this->stow("# --------------------------------------------------------\n");
999 }
1000 // Close file
1001 $this->close($this->dbhandle);
1002 $this->log("Table $table: finishing file (${table_file_prefix}.gz)");
1003 rename($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', $updraft_dir.'/'.$table_file_prefix.'.gz');
1004 }
1005 $stitch_files[] = $table_file_prefix;
1006 }
1007
1008 // Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side
1009 $backup_final_file_name = $backup_file_base.'-db.gz';
1010 $time_now = time();
1011 $time_mod = (int)@filemtime($backup_final_file_name);
1012 if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<20) {
1013 $file_size = filesize($backup_final_file_name);
1014 $this->log("Terminate: the final database file ($backup_final_file_name) exists, and was modified within the last 20 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=$file_size). This likely means that another UpdraftPlus run is at work; so we will exit.");
1015 $this->increase_resume_and_reschedule(120);
1016 die;
1017 } elseif (file_exists($backup_final_file_name)) {
1018 $this->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 20 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another UpdraftPlus terminated; thus we will continue.");
1019 }
1020
1021 // Finally, stitch the files together
1022 $this->backup_db_open($backup_final_file_name, true);
1023 $this->backup_db_header();
1024
1025 // We delay the unlinking because if two runs go concurrently and fail to detect each other (should not happen, but there's no harm in assuming the detection failed) then that leads to files missing from the db dump
1026 $unlink_files = array();
1027
1028 foreach ($stitch_files as $table_file) {
1029 $this->log("{$table_file}.gz: adding to final database dump");
1030 if (!$handle = gzopen($updraft_dir.'/'.$table_file.'.gz', "r")) {
1031 $this->log("Error: Failed to open database file for reading: ${table_file}.gz");
1032 $this->error(" Failed to open database file for reading: ${table_file}.gz");
1033 } else {
1034 while ($line = gzgets($handle, 2048)) { $this->stow($line); }
1035 gzclose($handle);
1036 $unlink_files[] = $updraft_dir.'/'.$table_file.'.gz';
1037 }
1038 }
1039
1040 if (defined("DB_CHARSET")) {
1041 $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
1042 $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
1043 $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
1044 }
1045
1046 $this->log($file_base.'-db.gz: finished writing out complete database file');
1047 $this->close($this->dbhandle);
1048
1049 foreach ($unlink_files as $unlink_files) {
1050 @unlink($unlink_file);
1051 }
1052
1053 if (count($this->errors)) {
1054 return false;
1055 } else {
1056 # We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop
1057 $this->log("Total database tables backed up: $total_tables");
1058 return basename($backup_file_base.'-db.gz');
1059 }
1060
1061 } //wp_db_backup
1062
1063 /**
1064 * Taken partially from phpMyAdmin and partially from
1065 * Alain Wolf, Zurich - Switzerland
1066 * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
1067 * Modified by Scott Merrill (http://www.skippy.net/)
1068 * to use the WordPress $wpdb object
1069 * @param string $table
1070 * @param string $segment
1071 * @return void
1072 */
1073 function backup_table($table, $segment = 'none') {
1074 global $wpdb;
1075
1076 $microtime = microtime(true);
1077
1078 $total_rows = 0;
1079
1080 $table_structure = $wpdb->get_results("DESCRIBE $table");
1081 if (! $table_structure) {
1082 //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
1083 return false;
1084 }
1085
1086 if(($segment == 'none') || ($segment == 0)) {
1087 // Add SQL statement to drop existing table
1088 $this->stow("\n\n");
1089 $this->stow("#\n");
1090 $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1091 $this->stow("#\n");
1092 $this->stow("\n");
1093 $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
1094
1095 // Table structure
1096 // Comment in SQL-file
1097 $this->stow("\n\n");
1098 $this->stow("#\n");
1099 $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1100 $this->stow("#\n");
1101 $this->stow("\n");
1102
1103 $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
1104 if (false === $create_table) {
1105 $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
1106 //$this->error($err_msg);
1107 $this->stow("#\n# $err_msg\n#\n");
1108 }
1109 $this->stow($create_table[0][1] . ' ;');
1110
1111 if (false === $table_structure) {
1112 $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
1113 //$this->error($err_msg);
1114 $this->stow("#\n# $err_msg\n#\n");
1115 }
1116
1117 // Comment in SQL-file
1118 $this->stow("\n\n#\n# " . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n#\n");
1119 }
1120
1121 // In UpdraftPlus, segment is always 'none'
1122 if(($segment == 'none') || ($segment >= 0)) {
1123 $defs = array();
1124 $integer_fields = array();
1125 // $table_structure was from "DESCRIBE $table"
1126 foreach ($table_structure as $struct) {
1127 if ( (0 === strpos($struct->Type, 'tinyint')) || (0 === strpos(strtolower($struct->Type), 'smallint')) ||
1128 (0 === strpos(strtolower($struct->Type), 'mediumint')) || (0 === strpos(strtolower($struct->Type), 'int')) || (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
1129 $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
1130 $integer_fields[strtolower($struct->Field)] = "1";
1131 }
1132 }
1133
1134 if($segment == 'none') {
1135 $row_start = 0;
1136 $row_inc = 100;
1137 } else {
1138 $row_start = $segment * 100;
1139 $row_inc = 100;
1140 }
1141
1142 do {
1143 if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1144 $table_data = $wpdb->get_results("SELECT * FROM $table LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1145 $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
1146 // \x08\\x09, not required
1147 $search = array("\x00", "\x0a", "\x0d", "\x1a");
1148 $replace = array('\0', '\n', '\r', '\Z');
1149 if($table_data) {
1150 foreach ($table_data as $row) {
1151 $total_rows++;
1152 $values = array();
1153 foreach ($row as $key => $value) {
1154 if (isset($integer_fields[strtolower($key)])) {
1155 // make sure there are no blank spots in the insert syntax,
1156 // yet try to avoid quotation marks around integers
1157 $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
1158 $values[] = ( '' === $value ) ? "''" : $value;
1159 } else {
1160 $values[] = "'" . str_replace($search, $replace, str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value))) . "'";
1161 }
1162 }
1163 $this->stow(" \n" . $entries . implode(', ', $values) . ');');
1164 }
1165 $row_start += $row_inc;
1166 }
1167 } while((count($table_data) > 0) and ($segment=='none'));
1168 }
1169
1170 if(($segment == 'none') || ($segment < 0)) {
1171 // Create footer/closing comment in SQL-file
1172 $this->stow("\n");
1173 $this->stow("#\n");
1174 $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1175 $this->stow("# --------------------------------------------------------\n");
1176 $this->stow("\n");
1177 }
1178 $this->log("Table $table: Total rows added: $total_rows in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds");
1179
1180 } // end backup_table()
1181
1182 function stow($query_line) {
1183 if ($this->dbhandle_isgz) {
1184 if(! @gzwrite($this->dbhandle, $query_line)) {
1185 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1186 }
1187 } else {
1188 if(false === @fwrite($this->dbhandle, $query_line)) {
1189 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1190 }
1191 }
1192 }
1193
1194 function close($handle) {
1195 if ($this->dbhandle_isgz) {
1196 gzclose($handle);
1197 } else {
1198 fclose($handle);
1199 }
1200 }
1201
1202 function error($error) {
1203 if (count($this->errors) == 0) $this->log("An error condition has occurred for the first time on this run");
1204 $this->errors[] = $error;
1205 return true;
1206 }
1207
1208 /**
1209 * Add backquotes to tables and db-names in
1210 * SQL queries. Taken from phpMyAdmin.
1211 */
1212 function backquote($a_name) {
1213 if (!empty($a_name) && $a_name != '*') {
1214 if (is_array($a_name)) {
1215 $result = array();
1216 reset($a_name);
1217 while(list($key, $val) = each($a_name))
1218 $result[$key] = '`' . $val . '`';
1219 return $result;
1220 } else {
1221 return '`' . $a_name . '`';
1222 }
1223 } else {
1224 return $a_name;
1225 }
1226 }
1227
1228 /*END OF WP-DB-BACKUP BLOCK */
1229
1230 /*
1231 this function is both the backup scheduler and ostensibly a filter callback for saving the option.
1232 it is called in the register_setting for the updraft_interval, which means when the admin settings
1233 are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
1234 so the option can be properly saved.
1235 */
1236 function schedule_backup($interval) {
1237 //clear schedule and add new so we don't stack up scheduled backups
1238 wp_clear_scheduled_hook('updraft_backup');
1239 switch($interval) {
1240 case 'every4hours':
1241 case 'every8hours':
1242 case 'twicedaily':
1243 case 'daily':
1244 case 'weekly':
1245 case 'fortnightly':
1246 case 'monthly':
1247 wp_schedule_event(time()+30, $interval, 'updraft_backup');
1248 break;
1249 }
1250 return wp_filter_nohtml_kses($interval);
1251 }
1252
1253 // Acts as a WordPress options filter
1254 function googledrive_clientid_checkchange($client_id) {
1255 if (UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != $client_id) {
1256 require_once(UPDRAFTPLUS_DIR.'/methods/googledrive.php');
1257 UpdraftPlus_BackupModule_googledrive::gdrive_auth_revoke(true);
1258 }
1259 return $client_id;
1260 }
1261
1262 function schedule_backup_database($interval) {
1263 //clear schedule and add new so we don't stack up scheduled backups
1264 wp_clear_scheduled_hook('updraft_backup_database');
1265 switch($interval) {
1266 case 'every4hours':
1267 case 'every8hours':
1268 case 'twicedaily':
1269 case 'daily':
1270 case 'weekly':
1271 case 'fortnightly':
1272 case 'monthly':
1273 wp_schedule_event(time()+30, $interval, 'updraft_backup_database');
1274 break;
1275 }
1276 return wp_filter_nohtml_kses($interval);
1277 }
1278
1279 //wp-cron only has hourly, daily and twicedaily, so we need to add some of our own
1280 function modify_cron_schedules($schedules) {
1281 $schedules['weekly'] = array( 'interval' => 604800, 'display' => 'Once Weekly' );
1282 $schedules['fortnightly'] = array( 'interval' => 1209600, 'display' => 'Once Each Fortnight' );
1283 $schedules['monthly'] = array( 'interval' => 2592000, 'display' => 'Once Monthly' );
1284 $schedules['every4hours'] = array( 'interval' => 14400, 'display' => 'Every 4 hours' );
1285 $schedules['every8hours'] = array( 'interval' => 28800, 'display' => 'Every 8 hours' );
1286 return $schedules;
1287 }
1288
1289 function backups_dir_location() {
1290 if (isset($this->backup_dir)) return $this->backup_dir;
1291 $updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir'));
1292 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1293 //if the option isn't set, default it to /backups inside the upload dir
1294 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1295 //check for the existence of the dir and an enumeration preventer.
1296 if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
1297 @mkdir($updraft_dir, 0775, true);
1298 @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
1299 @file_put_contents($updraft_dir.'/.htaccess','deny from all');
1300 }
1301 $this->backup_dir = $updraft_dir;
1302 return $updraft_dir;
1303 }
1304
1305 // Called via AJAX
1306 function updraft_ajax_handler() {
1307 // Test the nonce (probably not needed, since we're presumably admin-authed, but there's no harm)
1308 $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
1309 if (! wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
1310
1311 if ('lastlog' == $_GET['subaction']) {
1312 echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '(Nothing yet logged)'));
1313 } elseif ($_POST['subaction'] == 'credentials_test') {
1314 $method = (preg_match("/^[a-z0-9]+$/", $_POST['method'])) ? $_POST['method'] : "";
1315
1316 // Test the credentials, return a code
1317 require_once(UPDRAFTPLUS_DIR."/methods/$method.php");
1318
1319 $objname = "UpdraftPlus_BackupModule_${method}";
1320 if (method_exists($objname, "credentials_test")) call_user_func(array('UpdraftPlus_BackupModule_'.$method, 'credentials_test'));
1321 }
1322
1323 die;
1324
1325 }
1326
1327 function updraft_download_backup() {
1328 $type = $_POST['type'];
1329 $timestamp = (int)$_POST['timestamp'];
1330 $backup_history = $this->get_backup_history();
1331 $file = $backup_history[$timestamp][$type];
1332 $fullpath = $this->backups_dir_location().'/'.$file;
1333 if(!is_readable($fullpath)) {
1334 //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
1335 $this->download_backup($file);
1336 }
1337 if(@is_readable($fullpath) && is_file($fullpath)) {
1338 $len = filesize($fullpath);
1339
1340 $filearr = explode('.',$file);
1341 // //we've only got zip and gz...for now
1342 $file_ext = array_pop($filearr);
1343 if($file_ext == 'zip') {
1344 header('Content-type: application/zip');
1345 } else {
1346 // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
1347 header('Content-type: application/x-gzip');
1348 }
1349 header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
1350 header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
1351 header("Content-Length: $len;");
1352 if ($file_ext == 'crypt') {
1353 header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
1354 } else {
1355 header("Content-Disposition: attachment; filename=\"$file\";");
1356 }
1357 ob_end_flush();
1358 if ($file_ext == 'crypt') {
1359 $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
1360 if ($encryption == "") {
1361 $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
1362 } else {
1363 require_once(dirname(__FILE__).'/includes/Rijndael.php');
1364 $rijndael = new Crypt_Rijndael();
1365 $rijndael->setKey($encryption);
1366 $in_handle = fopen($fullpath,'r');
1367 $ciphertext = "";
1368 while (!feof ($in_handle)) {
1369 $ciphertext .= fread($in_handle, 16384);
1370 }
1371 fclose ($in_handle);
1372 print $rijndael->decrypt($ciphertext);
1373 }
1374 } else {
1375 readfile($fullpath);
1376 }
1377 $this->delete_local($file);
1378 exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end
1379 } else {
1380 echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.';
1381 }
1382 }
1383
1384 function download_backup($file) {
1385 $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
1386
1387 $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
1388 if (file_exists($method_include)) require_once($method_include);
1389
1390 $objname = "UpdraftPlus_BackupModule_${service}";
1391 if (method_exists($objname, "download")) {
1392 $remote_obj = new $objname;
1393 $remote_obj->download($file);
1394 } else {
1395 $this->error("Automatic backup restoration is not available with the method: $service.");
1396 }
1397
1398 }
1399
1400 function restore_backup($timestamp) {
1401 global $wp_filesystem;
1402 $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
1403 if(!is_array($backup_history[$timestamp])) {
1404 echo '<p>This backup does not exist in the backup history - restoration aborted. Timestamp: '.$timestamp.'</p><br/>';
1405 return false;
1406 }
1407
1408 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
1409 WP_Filesystem($credentials);
1410 if ( $wp_filesystem->errors->get_error_code() ) {
1411 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1412 show_message($message);
1413 exit;
1414 }
1415
1416 //if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?)
1417 echo '<span style="font-weight:bold">Restoration Progress</span><div id="updraft-restore-progress">';
1418
1419 $updraft_dir = $this->backups_dir_location().'/';
1420 foreach($backup_history[$timestamp] as $type => $file) {
1421 if ($type == 'nonce') continue;
1422 $fullpath = $updraft_dir.$file;
1423 if(!is_readable($fullpath) && $type != 'db') {
1424 $this->download_backup($file);
1425 }
1426 # Types: uploads, themes, plugins, others, db
1427 if(is_readable($fullpath) && $type != 'db') {
1428 if(!class_exists('WP_Upgrader')) require_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
1429 require_once(UPDRAFTPLUS_DIR.'/includes/updraft-restorer.php');
1430 $restorer = new Updraft_Restorer();
1431 $val = $restorer->restore_backup($fullpath, $type);
1432 if(is_wp_error($val)) {
1433 print_r($val);
1434 echo '</div>'; //close the updraft_restore_progress div even if we error
1435 return false;
1436 }
1437 }
1438 }
1439 echo '</div>'; //close the updraft_restore_progress div
1440 # The 'off' check is for badly configured setups - http://wordpress.org/support/topic/plugin-wp-super-cache-warning-php-safe-mode-enabled-but-safe-mode-is-off
1441 if(@ini_get('safe_mode') && strtolower(@ini_get('safe_mode')) != "off") {
1442 echo "<p>DB could not be restored because PHP safe_mode is active on your server. You will need to manually restore the file via phpMyAdmin or another method.</p><br/>";
1443 return false;
1444 }
1445 return true;
1446 }
1447
1448 //deletes the -old directories that are created when a backup is restored.
1449 function delete_old_dirs() {
1450 global $wp_filesystem;
1451 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
1452 WP_Filesystem($credentials);
1453 if ( $wp_filesystem->errors->get_error_code() ) {
1454 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1455 show_message($message);
1456 exit;
1457 }
1458
1459 $to_delete = array('themes-old','plugins-old','uploads-old','others-old');
1460
1461 foreach($to_delete as $name) {
1462 //recursively delete
1463 if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
1464 return false;
1465 }
1466 }
1467 return true;
1468 }
1469
1470 //scans the content dir to see if any -old dirs are present
1471 function scan_old_dirs() {
1472 $dirArr = scandir(WP_CONTENT_DIR);
1473 foreach($dirArr as $dir) {
1474 if(strpos($dir,'-old') !== false) {
1475 return true;
1476 }
1477 }
1478 return false;
1479 }
1480
1481
1482 function retain_range($input) {
1483 $input = (int)$input;
1484 if($input > 0 && $input < 3650) {
1485 return $input;
1486 } else {
1487 return 1;
1488 }
1489 }
1490
1491 function create_backup_dir() {
1492 global $wp_filesystem;
1493 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
1494 WP_Filesystem($credentials);
1495 if ( $wp_filesystem->errors->get_error_code() ) {
1496 foreach ( $wp_filesystem->errors->get_error_messages() as $message ) show_message($message);
1497 exit;
1498 }
1499
1500 $updraft_dir = $this->backups_dir_location();
1501 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1502 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1503
1504 //chmod the backup dir to 0777. ideally we'd rather chgrp it but i'm not sure if it's possible to detect the group apache is running under (or what if it's not apache...)
1505 if(!$wp_filesystem->mkdir($updraft_dir, 0777)) return false;
1506
1507 return true;
1508 }
1509
1510 function memory_check_current() {
1511 # Returns in megabytes
1512 $memory_limit = ini_get('memory_limit');
1513 $memory_unit = $memory_limit[strlen($memory_limit)-1];
1514 $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
1515 switch($memory_unit) {
1516 case 'K':
1517 $memory_limit = $memory_limit/1024;
1518 break;
1519 case 'G':
1520 $memory_limit = $memory_limit*1024;
1521 break;
1522 case 'M':
1523 //assumed size, no change needed
1524 break;
1525 }
1526 return $memory_limit;
1527 }
1528
1529 function memory_check($memory) {
1530 $memory_limit = $this->memory_check_current();
1531 return ($memory_limit >= $memory)?true:false;
1532 }
1533
1534 function execution_time_check($time) {
1535 $setting = ini_get('max_execution_time');
1536 return ( $setting==0 || $setting >= $time) ? true : false;
1537 }
1538
1539 function admin_init() {
1540 if(UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
1541 @ini_set('display_errors',1);
1542 @error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
1543 @ini_set('track_errors',1);
1544 }
1545 wp_enqueue_script('jquery');
1546
1547 if (UpdraftPlus_Options::user_can_manage() && UpdraftPlus_Options::get_updraft_option('updraft_service') == "googledrive" && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid','') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token','') == '') {
1548 add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1549 }
1550
1551 if (UpdraftPlus_Options::user_can_manage() && UpdraftPlus_Options::get_updraft_option('updraft_service') == "dropbox" && UpdraftPlus_Options::get_updraft_option('updraft_dropboxtk_request_token','') == '') {
1552 add_action('admin_notices', array($this,'show_admin_warning_dropbox') );
1553 }
1554 }
1555
1556 function ajax_enqueue() {
1557 // wp_enqueue_script('updraftplus-ajax', plugins_url('/includes/ajax.js', __FILE__) );
1558 // wp_localize_script('updraftplus-ajax', 'updraft_credentials_test', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
1559 }
1560
1561 function url_start($urls,$url) {
1562 return ($urls) ? '<a href="http://'.$url.'">' : "";
1563 }
1564
1565 function url_end($urls,$url) {
1566 return ($urls) ? '</a>' : " (http://$url)";
1567 }
1568
1569 function wordshell_random_advert($urls) {
1570 if (defined('UPDRAFTPLUS_PREMIUM')) return "";
1571 $rad = rand(0,6);
1572 switch ($rad) {
1573 case 0:
1574 return "Like automating WordPress operations? Use the CLI? ".$this->url_start($urls,'wordshell.net')."You will love WordShell".$this->url_end($urls,'www.wordshell.net')." - saves time and money fast.";
1575 break;
1576 case 1:
1577 return "Find UpdraftPlus useful? ".$this->url_start($urls,'david.dw-perspective.org.uk/donate')."Please make a donation.".$this->url_end($urls,'david.dw-perspective.org.uk/donate');
1578 case 2:
1579 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";
1580 break;
1581 case 3:
1582 return "Want some more useful plugins? ".$this->url_start($urls,'profiles.wordpress.org/DavidAnderson/')."See my WordPress profile page for others.".$this->url_end($urls,'profiles.wordpress.org/DavidAnderson/');
1583 break;
1584 case 4:
1585 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');
1586 break;
1587 case 5:
1588 if (!defined('UPDRAFTPLUS_PREMIUM')) {
1589 return $this->url_start($urls,'www.updraftplus.com')."Need even more features and support? Check out UpdraftPlus Premium".$this->url_end($urls,'www.updraftplus.com');
1590 } else {
1591 return "Thanks for being an UpdraftPlus premium user. Keep visiting ".$this->url_start($urls,'www.updraftplus.com')."updraftplus.com".$this->url_end($urls,'www.updraftplus.com')." to see what's going on.";
1592 }
1593 break;
1594 case 6:
1595 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/');
1596 break;
1597 }
1598 }
1599
1600 function settings_formcontents() {
1601 $updraft_dir = $this->backups_dir_location();
1602 ?>
1603 <table class="form-table" style="width:850px;">
1604 <tr>
1605 <th>File backup intervals:</th>
1606 <td><select name="updraft_interval">
1607 <?php
1608 $intervals = array ("manual" => "Manual", 'every4hours' => "Every 4 hours", 'every8hours' => "Every 8 hours", 'twicedaily' => "Every 12 hours", 'daily' => "Daily", 'weekly' => "Weekly", 'fortnightly' => "Fortnightly", 'monthly' => "Monthly");
1609 foreach ($intervals as $cronsched => $descrip) {
1610 echo "<option value=\"$cronsched\" ";
1611 if ($cronsched == UpdraftPlus_Options::get_updraft_option('updraft_interval','manual')) echo 'selected="selected"';
1612 echo ">$descrip</option>\n";
1613 }
1614 ?>
1615 </select>
1616 and retain this many backups: <?php
1617 $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 1);
1618 $updraft_retain = ((int)$updraft_retain > 0) ? (int)$updraft_retain : 1;
1619 ?> <input type="text" name="updraft_retain" value="<?php echo $updraft_retain ?>" style="width:40px;" />
1620 </td>
1621 </tr>
1622 <tr>
1623 <th>Database backup intervals:</th>
1624 <td><select name="updraft_interval_database">
1625 <?php
1626 foreach ($intervals as $cronsched => $descrip) {
1627 echo "<option value=\"$cronsched\" ";
1628 if ($cronsched == UpdraftPlus_Options::get_updraft_option('updraft_interval_database', UpdraftPlus_Options::get_updraft_option('updraft_interval'))) echo 'selected="selected"';
1629 echo ">$descrip</option>\n";
1630 }
1631 ?>
1632 </select>
1633 and retain this many backups: <?php
1634 $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain);
1635 $updraft_retain_db = ((int)$updraft_retain_db > 0) ? (int)$updraft_retain_db : 1;
1636 ?> <input type="text" name="updraft_retain_db" value="<?php echo $updraft_retain_db ?>" style="width:40px" />
1637 </td>
1638 </tr>
1639 <tr class="backup-interval-description">
1640 <td></td><td>If you would like to automatically schedule backups, choose schedules from the dropdowns above. Backups will occur at the intervals specified starting just after the current time. If the two schedules are the same, then the two backups will take place together. If you choose &quot;manual&quot; then you must click the &quot;Backup Now!&quot; button whenever you wish a backup to occur. </td>
1641 </tr>
1642 <?php
1643 # The true (default value if non-existent) here has the effect of forcing a default of on.
1644 $include_themes = (UpdraftPlus_Options::get_updraft_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
1645 $include_plugins = (UpdraftPlus_Options::get_updraft_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
1646 $include_uploads = (UpdraftPlus_Options::get_updraft_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
1647 $include_others = (UpdraftPlus_Options::get_updraft_option('updraft_include_others',true)) ? 'checked="checked"' : "";
1648 $include_others_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE);
1649 ?>
1650 <tr>
1651 <th>Include in files backup:</th>
1652 <td>
1653 <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br>
1654 <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br>
1655 <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br>
1656 <input type="checkbox" name="updraft_include_others" value="1" <?php echo $include_others; ?> /> Any other directories found inside wp-content <?php if (is_multisite()) echo "(which on a multisite install includes users' blog contents) "; ?>- but exclude these directories: <input type="text" name="updraft_include_others_exclude" size="44" value="<?php echo htmlspecialchars($include_others_exclude); ?>"/><br>
1657 Include all of these, unless you are backing them up outside of UpdraftPlus. The above directories are usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way. (<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br></td>
1658 </td>
1659 </tr>
1660 <tr>
1661 <th>Email:</th>
1662 <td><input type="text" style="width:260px" name="updraft_email" value="<?php echo UpdraftPlus_Options::get_updraft_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
1663 </tr>
1664
1665 <tr>
1666 <th>Database encryption phrase:</th>
1667 <?php
1668 $updraft_encryptionphrase = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
1669 ?>
1670 <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
1671 </tr>
1672 <tr class="backup-crypt-description">
1673 <td></td><td>If you enter text here, it is used to encrypt backups (Rijndael). <strong>Do make a separate record of it and do not lose it, or all your backups <em>will</em> be useless.</strong> Presently, only the database file is encrypted. This is also the key used to decrypt backups from this admin interface (so if you change it, then automatic decryption will not work until you change it back). You can also use the file example-decrypt.php from inside the UpdraftPlus plugin directory to decrypt manually.</td>
1674 </tr>
1675 </table>
1676
1677 <h2>Copying Your Backup To Remote Storage</h2>
1678
1679 <table class="form-table" style="width:850px;">
1680 <tr>
1681 <th>Choose your remote storage:</th>
1682 <td><select name="updraft_service" id="updraft-service">
1683 <?php
1684 $debug_mode = (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1685
1686 $set = 'selected="selected"';
1687
1688 // Should be one of s3, dropbox, ftp, googledrive, email, or whatever else is added
1689 $active_service = UpdraftPlus_Options::get_updraft_option('updraft_service');
1690
1691 ?>
1692 <option value="none" <?php
1693 if ($active_service == "none") echo $set; ?>>None</option>
1694 <?php
1695 foreach ($this->backup_methods as $method => $description) {
1696 echo "<option value=\"$method\"";
1697 if ($active_service == $method) echo ' '.$set;
1698 echo '>'.$description;
1699 echo "</option>\n";
1700 }
1701 ?>
1702 </select></td>
1703 </tr>
1704 <?php
1705 foreach ($this->backup_methods as $method => $description) {
1706 require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
1707 $call_method = "UpdraftPlus_BackupModule_$method";
1708 call_user_func(array($call_method, 'config_print'));
1709 }
1710 ?>
1711 </table>
1712 <script type="text/javascript">
1713 /* <![CDATA[ */
1714 var lastlog_lastmessage = "";
1715 var lastlog_sdata = {
1716 action: 'updraft_ajax',
1717 subaction: 'lastlog',
1718 nonce: '<?php echo wp_create_nonce('updraftplus-credentialtest-nonce'); ?>'
1719 };
1720 function updraft_showlastlog(){
1721 jQuery.get(ajaxurl, lastlog_sdata, function(response) {
1722 nexttimer = 1500;
1723 if (lastlog_lastmessage == response) { nexttimer = 4500; }
1724 window.setTimeout(function(){updraft_showlastlog()}, nexttimer);
1725 jQuery('#updraft_lastlogcontainer').html(response);
1726 lastlog_lastmessage = response;
1727 });
1728 }
1729 jQuery(document).ready(function() {
1730 jQuery('#enableexpertmode').click(function() {
1731 jQuery('.expertmode').fadeIn();
1732 return false;
1733 });
1734 <?php if (!is_writable($updraft_dir)) echo "jQuery('.backupdirrow').show();\n"; ?>
1735 window.setTimeout(function(){updraft_showlastlog()}, 1200);
1736 jQuery('.updraftplusmethod').hide();
1737 <?php
1738 if ($active_service) echo "jQuery('.${active_service}').show();";
1739 foreach ($this->backup_methods as $method => $description) {
1740 // already done: require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
1741 $call_method = "UpdraftPlus_BackupModule_$method";
1742 if (method_exists($call_method, 'config_print_javascript_onready')) call_user_func(array($call_method, 'config_print_javascript_onready'));
1743 }
1744 ?>
1745 });
1746 /* ]]> */
1747 </script>
1748 <table class="form-table" style="width:850px;">
1749 <tr>
1750 <td colspan="2"><h2>Advanced / Debugging Settings</h2></td>
1751 </tr>
1752 <tr>
1753 <th>Debug mode:</th>
1754 <td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br>Check this to receive more information and emails on the backup process - useful if something is going wrong. You <strong>must</strong> send me this log if you are filing a bug report.</td>
1755 </tr>
1756 <tr>
1757 <th>Expert settings:</th>
1758 <td><a id="enableexpertmode" href="#">Show expert settings</a> - click this to show some further options; don't bother with this unless you have a problem or are curious.</td>
1759 </tr>
1760 <?php
1761 $delete_local = UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1);
1762 ?>
1763
1764 <tr class="deletelocal expertmode" style="display:none;">
1765 <th>Delete local backup:</th>
1766 <td><input type="checkbox" name="updraft_delete_local" value="1" <?php if ($delete_local) echo 'checked="checked"'; ?>> <br>Uncheck this to prevent deletion of any superfluous backup files from your server after the backup run finishes (i.e. any files despatched remotely will also remain locally, and any files being kept locally will not be subject to the retention limits).</td>
1767 </tr>
1768
1769 <tr class="expertmode backupdirrow" style="display:none;">
1770 <th>Backup directory:</th>
1771 <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo htmlspecialchars($updraft_dir); ?>" /></td>
1772 </tr>
1773 <tr class="expertmode backupdirrow" style="display:none;">
1774 <td></td><td><?php
1775
1776 if(is_writable($updraft_dir)) {
1777 $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
1778 } else {
1779 $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
1780 }
1781
1782 echo $dir_info ?> This is where UpdraftPlus will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
1783 </tr>
1784 <tr>
1785 <td></td>
1786 <td>
1787 <?php
1788 $ws_ad = $this->wordshell_random_advert(1);
1789 if ($ws_ad) {
1790 ?>
1791 <p style="margin: 10px 0; padding: 10px; font-size: 140%; background-color: lightYellow; border-color: #E6DB55; border: 1px solid; border-radius: 4px;">
1792 <?php echo $ws_ad; ?>
1793 </p>
1794 <?php
1795 }
1796 ?>
1797 </td>
1798 </tr>
1799 <tr>
1800 <td></td>
1801 <td>
1802 <input type="hidden" name="action" value="update" />
1803 <input type="submit" class="button-primary" value="Save Changes" />
1804 </td>
1805 </tr>
1806 </table>
1807 <?php
1808 }
1809
1810 function settings_output() {
1811
1812 /*
1813 we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1814 for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
1815 passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
1816 */
1817 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
1818 $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1819 if(empty($this->errors) && $backup_success == true) {
1820 echo '<p>Restore successful!</p><br/>';
1821 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1822 return;
1823 } else {
1824 echo '<p>Restore failed...</p><ul>';
1825 foreach ($this->errors as $err) {
1826 echo "<li>";
1827 if (is_string($err)) { echo htmlspecialchars($err); } else {
1828 print_r($err);
1829 }
1830 echo "</li>";
1831 }
1832 echo '</ul><b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1833 return;
1834 }
1835 //uncomment the below once i figure out how i want the flow of a restoration to work.
1836 //echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1837 }
1838 $deleted_old_dirs = false;
1839 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1840 if($this->delete_old_dirs()) {
1841 $deleted_old_dirs = true;
1842 } else {
1843 echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1844 }
1845 echo '<p>Old directories successfully removed.</p><br/>';
1846 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1847 return;
1848 }
1849
1850 if(isset($_GET['error'])) {
1851 $this->show_admin_warning(htmlspecialchars($_GET['error']), 'error');
1852 }
1853 if(isset($_GET['message'])) {
1854 $this->show_admin_warning(htmlspecialchars($_GET['message']));
1855 }
1856
1857 if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1858 if(!$this->create_backup_dir()) {
1859 echo '<p>Backup directory could not be created...</p><br/>';
1860 }
1861 echo '<p>Backup directory successfully created.</p><br/>';
1862 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1863 return;
1864 }
1865
1866 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1867 echo '<div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;"><strong>Schedule backup:</strong> ';
1868 if (wp_schedule_single_event(time()+5, 'updraft_backup_all') === false) {
1869 $this->log("A backup run failed to schedule");
1870 echo "Failed.";
1871 } else {
1872 echo "OK. Now load any page from your site to make sure the schedule can trigger.";
1873 $this->log("A backup run has been scheduled");
1874 }
1875 echo '</div>';
1876 }
1877
1878 // updraft_file_ids is not deleted
1879 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->boot_backup(true,true); }
1880 elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
1881 elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
1882 $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_dropbox_folder', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_others_exclude', 'updraft_lastmessage', 'updraft_googledrive_clientid', 'updraft_googledrive_token', 'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder', 'updraft_last_backup');
1883 foreach ($settings as $s) {
1884 UpdraftPlus_Options::delete_updraft_option($s);
1885 }
1886 $this->show_admin_warning("Your settings have been wiped.");
1887 }
1888
1889 ?>
1890 <div class="wrap">
1891 <h1><?php echo $this->plugin_title; ?></h1>
1892
1893 Maintained by <b>David Anderson</b> (<a href="http://david.dw-perspective.org.uk">Homepage</a><?php if (!defined('UPDRAFTPLUS_PREMIUM')) { ?> | <a href="http://updraftplus.com">Premium</a> | <a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a><?php } ?> | <a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
1894 <br>
1895 <?php
1896 if(isset($_GET['updraft_restore_success'])) {
1897 echo "<div style=\"color:blue\">Your backup has been restored. Your old themes, uploads, and plugins directories have been retained with \"-old\" appended to their name. Remove them when you are satisfied that the backup worked properly. At this time Updraft does not automatically restore your DB. You will need to use an external tool like phpMyAdmin to perform that task.</div>";
1898 }
1899
1900 $ws_advert = $this->wordshell_random_advert(1);
1901 if ($ws_advert) { echo '<div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;">'.$ws_advert.'</div>'; }
1902
1903 if($deleted_old_dirs) echo '<div style="color:blue">Old directories successfully deleted.</div>';
1904
1905 if(!$this->memory_check(96)) {?>
1906 <div style="color:orange">Your PHP memory limit is too low. UpdraftPlus attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
1907 <?php
1908 }
1909 if(!$this->execution_time_check(300)) {?>
1910 <div style="color:orange">Your PHP max_execution_time is less than 300 seconds. This probably means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, there is a chance Updraft will be unable to complete a backup. Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
1911 <?php
1912 }
1913
1914 if($this->scan_old_dirs()) {?>
1915 <div style="color:orange">You have old directories from a previous backup. Click to delete them after you have verified that the restoration worked.</div>
1916 <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
1917 <input type="hidden" name="action" value="updraft_delete_old_dirs" />
1918 <input type="submit" class="button-primary" value="Delete Old Dirs" onclick="return(confirm('Are you sure you want to delete the old directories? This cannot be undone.'))" />
1919 </form>
1920 <?php
1921 }
1922 if(!empty($this->errors)) {
1923 foreach($this->errors as $error) {
1924 // ignoring severity
1925 echo '<div style="color:red">'.$error['error'].'</div>';
1926 }
1927 }
1928 ?>
1929
1930 <h2 style="clear:left;">Existing Schedule And Backups</h2>
1931 <table class="form-table" style="float:left; clear: both; width:545px;">
1932 <tr>
1933 <?php
1934 $updraft_dir = $this->backups_dir_location();
1935 $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1936 $next_scheduled_backup = ($next_scheduled_backup) ? date('D, F j, Y H:i T',$next_scheduled_backup) : 'No backups are scheduled at this time.';
1937 $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
1938 if (UpdraftPlus_Options::get_updraft_option('updraft_interval_database',UpdraftPlus_Options::get_updraft_option('updraft_interval')) == UpdraftPlus_Options::get_updraft_option('updraft_interval')) {
1939 $next_scheduled_backup_database = "Will take place at the same time as the files backup.";
1940 } else {
1941 $next_scheduled_backup_database = ($next_scheduled_backup_database) ? date('D, F j, Y H:i T',$next_scheduled_backup_database) : 'No backups are scheduled at this time.';
1942 }
1943 $current_time = date('D, F j, Y H:i T',time());
1944 $updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
1945 if($updraft_last_backup) {
1946 $last_backup = ($updraft_last_backup['success']) ? date('D, F j, Y H:i T',$updraft_last_backup['backup_time']) : implode("<br>",$updraft_last_backup['errors']);
1947 $last_backup_color = ($updraft_last_backup['success']) ? 'green' : 'red';
1948 if (!empty($updraft_last_backup['backup_nonce'])) {
1949 $potential_log_file = $updraft_dir."/log.".$updraft_last_backup['backup_nonce'].".txt";
1950 if (is_readable($potential_log_file)) $last_backup .= "<br><a href=\"?page=updraftplus&action=downloadlog&updraftplus_backup_nonce=".$updraft_last_backup['backup_nonce']."\">Download log file</a>";
1951 }
1952 } else {
1953 $last_backup = 'No backup has been completed.';
1954 $last_backup_color = 'blue';
1955 }
1956
1957 if(is_writable($updraft_dir)) {
1958 $backup_disabled = "";
1959 } else {
1960 $backup_disabled = 'disabled="disabled"';
1961 }
1962 ?>
1963
1964 <th>Time now:</th>
1965 <td style="color:blue"><?php echo $current_time?></td>
1966 </tr>
1967 <tr>
1968 <th>Next scheduled files backup:</th>
1969 <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1970 </tr>
1971 <tr>
1972 <th>Next scheduled DB backup:</th>
1973 <td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
1974 </tr>
1975 <tr>
1976 <th>Last backup:</th>
1977 <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1978 </tr>
1979 </table>
1980 <div style="float:left; width:200px; padding-top: 40px;">
1981 <form method="post" action="">
1982 <input type="hidden" name="action" value="updraft_backup" />
1983 <p><input type="submit" <?php echo $backup_disabled ?> class="button-primary" value="Backup Now!" style="padding-top:2px;padding-bottom:2px;font-size:22px !important" onclick="return(confirm('This will schedule a one-time backup. To trigger the backup you should go ahead, then wait 10 seconds, then visit any page on your site. WordPress should then start the backup running in the background.'))"></p>
1984 </form>
1985 <div style="position:relative">
1986 <div style="position:absolute;top:0;left:0">
1987 <?php
1988 $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
1989 $backup_history = (is_array($backup_history))?$backup_history:array();
1990 $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
1991 ?>
1992 <input type="button" class="button-primary" <?php echo $restore_disabled ?> value="Restore" style="padding-top:2px;padding-bottom:2px;font-size:22px !important" onclick="jQuery('#backup-restore').fadeIn('slow');jQuery(this).parent().fadeOut('slow')">
1993 </div>
1994 <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
1995 <form method="post" action="">
1996 <b>Choose: </b>
1997 <select name="backup_timestamp" style="display:inline">
1998 <?php
1999 foreach($backup_history as $key=>$value) {
2000 echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
2001 }
2002 ?>
2003 </select>
2004
2005 <input type="hidden" name="action" value="updraft_restore" />
2006 <input type="submit" <?php echo $restore_disabled ?> class="button-primary" value="Restore Now!" style="padding-top:7px;margin-top:5px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('Restoring from backup will replace this site\'s themes, plugins, uploads and other content directories (according to what is contained in the backup set which you select). Database restoration cannot be done through this process - you must download the database and import yourself (e.g. through PHPMyAdmin). Do you wish to continue with the restoration process?'))" />
2007 </form>
2008 </div>
2009 </div>
2010 </div>
2011 <br style="clear:both" />
2012 <table class="form-table">
2013 <tr>
2014 <th>Last backup log message:</th>
2015 <td id="updraft_lastlogcontainer"><?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '(Nothing yet logged)')); ?></td>
2016 </tr>
2017 <tr>
2018 <th>Download backups and logs:</th>
2019 <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
2020 </tr>
2021 <tr>
2022 <td></td><td class="download-backups" style="display:none">
2023 <em>Click on a button to download the corresponding file to your computer. If you are using the <a href="http://opera.com">Opera web browser</a> then you should turn Turbo mode off. <strong>Note</strong> - if you use remote storage (e.g. Amazon, Dropbox, FTP, Google Drive), then pressing a button will make UpdraftPlus try to bring a backup file back from the remote storage to your webserver, and from there to your computer. If the backup file is very big, then likely you will run out of time using this method. In that case you should get the file directly (i.e. visit Amazon S3's or Dropbox's website, etc.).</em>
2024 <table>
2025 <?php
2026 foreach($backup_history as $key=>$value) {
2027 ?>
2028 <tr>
2029 <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
2030 <td>
2031 <?php if (isset($value['db'])) { ?>
2032 <form action="admin-ajax.php" method="post">
2033 <input type="hidden" name="action" value="updraft_download_backup" />
2034 <input type="hidden" name="type" value="db" />
2035 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2036 <input type="submit" value="Database" />
2037 </form>
2038 <?php } else { echo "(No database)"; } ?>
2039 </td>
2040 <td>
2041 <?php if (isset($value['plugins'])) { ?>
2042 <form action="admin-ajax.php" method="post">
2043 <input type="hidden" name="action" value="updraft_download_backup" />
2044 <input type="hidden" name="type" value="plugins" />
2045 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2046 <input type="submit" value="Plugins" />
2047 </form>
2048 <?php } else { echo "(No plugins)"; } ?>
2049 </td>
2050 <td>
2051 <?php if (isset($value['themes'])) { ?>
2052 <form action="admin-ajax.php" method="post">
2053 <input type="hidden" name="action" value="updraft_download_backup" />
2054 <input type="hidden" name="type" value="themes" />
2055 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2056 <input type="submit" value="Themes" />
2057 </form>
2058 <?php } else { echo "(No themes)"; } ?>
2059 </td>
2060 <td>
2061 <?php if (isset($value['uploads'])) { ?>
2062 <form action="admin-ajax.php" method="post">
2063 <input type="hidden" name="action" value="updraft_download_backup" />
2064 <input type="hidden" name="type" value="uploads" />
2065 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2066 <input type="submit" value="Uploads" />
2067 </form>
2068 <?php } else { echo "(No uploads)"; } ?>
2069 </td>
2070 <td>
2071 <?php if (isset($value['others'])) { ?>
2072 <form action="admin-ajax.php" method="post">
2073 <input type="hidden" name="action" value="updraft_download_backup" />
2074 <input type="hidden" name="type" value="others" />
2075 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2076 <input type="submit" value="Others" />
2077 </form>
2078 <?php } else { echo "(No others)"; } ?>
2079 </td>
2080 <td>
2081 <?php if (isset($value['nonce']) && preg_match("/^[0-9a-f]{12}$/",$value['nonce']) && is_readable($updraft_dir.'/log.'.$value['nonce'].'.txt')) { ?>
2082 <form action="options-general.php" method="get">
2083 <input type="hidden" name="action" value="downloadlog" />
2084 <input type="hidden" name="page" value="updraftplus" />
2085 <input type="hidden" name="updraftplus_backup_nonce" value="<?php echo $value['nonce']; ?>" />
2086 <input type="submit" value="Backup Log" />
2087 </form>
2088 <?php } else { echo "(No backup log)"; } ?>
2089 </td>
2090 </tr>
2091 <?php }?>
2092 </table>
2093 </td>
2094 </tr>
2095 </table>
2096 <?php
2097 if (!defined('UPDRAFTPLUS_PREMIUM') && is_multisite()) {
2098 ?>
2099 <h2>UpdraftPlus Premium</h2>
2100 <table>
2101 <tr>
2102 <td>
2103 <p style="max-width:800px;">Do you need WordPress Multisite support? Please check out <a href="http://updraftplus.com">UpdraftPlus Premium</a> - and in coming weeks, it will add even more premium features. Why not support UpdraftPlus development?</p>
2104 </td>
2105 </tr>
2106 </table>
2107 <?php } ?>
2108 <h2>Configure Backup Contents And Schedule</h2>
2109 <?php UpdraftPlus_Options::options_form_begin(); ?>
2110 <?php $this->settings_formcontents(); ?>
2111 </form>
2112 <div style="padding-top: 40px; display:none;" class="expertmode">
2113 <hr>
2114 <h3>Debug Information And Expert Options</h3>
2115 <p>
2116 <?php
2117 $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
2118 $memory_usage = memory_get_usage(true)/1024/1024;
2119 echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
2120 echo 'Current memory usage: '.$memory_usage.' MB<br/>';
2121 echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
2122 ?>
2123 </p>
2124 <p style="max-width: 600px;">The buttons below will immediately execute a backup run, independently of WordPress's scheduler. If these work whilst your scheduled backups and the &quot;Backup Now&quot; button do absolutely nothing (i.e. not even produce a log file), then it means that your scheduler is broken. You should then disable all your other plugins, and try the &quot; Backup Now&quot; button. If that fails, then contact your web hosting company and ask them if they have disabled wp-cron. If it succeeds, then re-activate your other plugins one-by-one, and find the one that is the problem and report a bug to them.</p>
2125
2126 <form method="post">
2127 <input type="hidden" name="action" value="updraft_backup_debug_all" />
2128 <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug Full Backup" onclick="return(confirm('This will cause an immediate backup. The page will stall loading until it finishes (ie, unscheduled).'))" /></p>
2129 </form>
2130 <form method="post">
2131 <input type="hidden" name="action" value="updraft_backup_debug_db" />
2132 <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug DB Backup" onclick="return(confirm('This will cause an immediate DB backup. The page will stall loading until it finishes (ie, unscheduled). The backup may well run out of time; really this button is only helpful for checking that the backup is able to get through the initial stages, or for small WordPress sites.'))" /></p>
2133 </form>
2134 <h3>Wipe Settings</h3>
2135 <p style="max-width: 600px;">This button will delete all UpdraftPlus settings (but not any of your existing backups from your cloud storage). You will then need to enter all your settings again. You can also do this before deactivating/deinstalling UpdraftPlus if you wish.</p>
2136 <form method="post">
2137 <input type="hidden" name="action" value="updraft_wipesettings" />
2138 <p><input type="submit" class="button-primary" value="Wipe All Settings" onclick="return(confirm('This will delete all your UpdraftPlus settings - are you sure you want to do this?'))" /></p>
2139 </form>
2140 </div>
2141
2142 <script type="text/javascript">
2143 /* <![CDATA[ */
2144 jQuery(document).ready(function() {
2145 jQuery('#updraft-service').change(function() {
2146 jQuery('.updraftplusmethod').hide();
2147 var active_class = jQuery(this).val();
2148 jQuery('.'+active_class).show();
2149 })
2150 })
2151 jQuery(window).load(function() {
2152 //this is for hiding the restore progress at the top after it is done
2153 setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
2154 jQuery('#updraft-restore-progress-toggle').click(function() {
2155 jQuery('#updraft-restore-progress').toggle(500)
2156 })
2157 })
2158 /* ]]> */
2159 </script>
2160 <?php
2161 }
2162
2163 function show_admin_warning($message, $class = "updated") {
2164 echo '<div id="updraftmessage" class="'.$class.' fade">'."<p>$message</p></div>";
2165 }
2166
2167 function show_admin_warning_unreadablelog() {
2168 $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> The log file could not be read.');
2169 }
2170
2171 function show_admin_warning_dropbox() {
2172 $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="options-general.php?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=doit">Click here to authenticate your Dropbox account (you will not be able to back up to Dropbox without it).</a>');
2173 }
2174
2175 function show_admin_warning_googledrive() {
2176 $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="options-general.php?page=updraftplus&action=updraftmethod-googledrive-auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
2177 }
2178
2179 // Caution: $source is allowed to be an array, not just a filename
2180 function make_zipfile($source, $destination) {
2181
2182 // Fallback to PclZip - which my tests show is 25% slower
2183 if (!method_exists('ZipArchive', 'addFile')) {
2184 if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
2185 $zip_object = new PclZip($destination);
2186 $zipcode = $zip_object->create($source, PCLZIP_OPT_REMOVE_PATH, WP_CONTENT_DIR);
2187 if ($zipcode == 0 ) {
2188 $this->log("PclZip Error: ".$zip_object->errorName());
2189 return $zip_object->errorCode();
2190 } else {
2191 return true;
2192 }
2193 }
2194
2195 $this->existing_files = array();
2196
2197 // TODO: Resuming! :-)
2198 // If the file exists, then we should grab its index of files inside, and sizes
2199 // Then, when we come to write a file, we should check if it's already there, and only add if it is not
2200 if (file_exists($destination) && is_readable($destination)) {
2201 $zip = new ZipArchive;
2202 $zip->open($destination);
2203 $this->log(basename($destination).": Zip file already exists, with ".$zip->numFiles." files");
2204 for ($i=0; $i<$zip->numFiles; $i++) {
2205 $si = $zip->statIndex($i);
2206 $name = $si['name'];
2207 $this->existing_files[$name] = $si['size'];
2208 }
2209 } elseif (file_exists($destination)) {
2210 $this->log("Zip file already exists, but is not readable; will remove: $destination");
2211 @unlink($destination);
2212 }
2213
2214 $this->zipfiles_added = 0;
2215 $this->zipfiles_dirbatched = array();
2216 $this->zipfiles_batched = array();
2217
2218 $last_error = -1;
2219 if (is_array($source)) {
2220 foreach ($source as $element) {
2221 $howmany = $this->makezip_recursive_add($destination, $element, basename($element), $element);
2222 if ($howmany < 0) {
2223 $last_error = $howmany;
2224 }
2225 }
2226 } else {
2227 $howmany = $this->makezip_recursive_add($destination, $source, basename($source), $source);
2228 if ($howmany < 0) {
2229 $last_error = $howmany;
2230 }
2231 }
2232
2233 // Any not yet dispatched?
2234 if (count($this->zipfiles_dirbatched)>0 || count($this->zipfiles_batched)>0) {
2235 $howmany = $this->makezip_addfiles($destination);
2236 if ($howmany < 0) {
2237 $last_error = $howmany;
2238 }
2239 }
2240
2241 if ($this->zipfiles_added >= 0) {
2242 return true;
2243 } else {
2244 return $last_error;
2245 }
2246
2247 }
2248
2249 // Q. Why don't we only open and close the zip file just once?
2250 // A. Because apparently PHP doesn't write out until the final close, and it will return an error if anything file has vanished in the meantime. So going directory-by-directory reduces our chances of hitting an error if the filesystem is changing underneath us (which is very possible if dealing with e.g. 1Gb of files)
2251
2252 // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
2253 function makezip_addfiles($zipfile) {
2254 $zip = new ZipArchive();
2255 if (file_exists($zipfile)) {
2256 $opencode = $zip->open($zipfile);
2257 } else {
2258 $opencode = $zip->open($zipfile, ZIPARCHIVE::CREATE);
2259 }
2260 if ($opencode !== true) return array($opencode, 0);
2261 // Make sure all directories are created before we start creating files
2262 while ($dir = array_pop($this->zipfiles_dirbatched)) {
2263 $zip->addEmptyDir($dir);
2264 }
2265 foreach ($this->zipfiles_batched as $file => $add_as) {
2266 if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != filesize($file)) {
2267 $zip->addFile($file, $add_as);
2268 }
2269 $this->zipfiles_added++;
2270 if ($this->zipfiles_added % 100 == 0) $this->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (size: ".round(filesize($zipfile)/1024,1)." Kb)");
2271 }
2272 // Reset the array
2273 $this->zipfiles_batched = array();
2274 return $zip->close();
2275 }
2276
2277 // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
2278 function makezip_recursive_add($zipfile, $fullpath, $use_path_when_storing, $original_fullpath) {
2279
2280 // De-reference
2281 $fullpath = realpath($fullpath);
2282
2283 // Is the place we've ended up above the original base? That leads to infinite recursion
2284 if (($fullpath !== $original_fullpath && strpos($original_fullpath, $fullpath) === 0) || ($original_fullpath == $fullpath && strpos($use_path_when_storing, '/') !== false) ) {
2285 $this->log("Infinite recursion: symlink lead us to $fullpath, which is within $original_fullpath");
2286 $this->error("Infinite recursion: consult your log for more information");
2287 return false;
2288 }
2289
2290 if(is_file($fullpath)) {
2291 if (is_readable($fullpath)) {
2292 $key = $use_path_when_storing.'/'.basename($fullpath);
2293 $this->zipfiles_batched[$fullpath] = $use_path_when_storing.'/'.basename($fullpath);
2294 @touch($zipfile);
2295 } else {
2296 $this->log("$fullpath: unreadable file");
2297 $this->error("$fullpath: unreadable file");
2298 }
2299 } elseif (is_dir($fullpath)) {
2300 if (!isset($this->existing_files[$use_path_when_storing])) $this->zipfiles_dirbatched[] = $use_path_when_storing;
2301 if (!$dir_handle = @opendir($fullpath)) {
2302 $this->log("Failed to open directory: $fullpath");
2303 $this->error("Failed to open directory: $fullpath");
2304 return;
2305 }
2306 while ($e = readdir($dir_handle)) {
2307 if ($e != '.' && $e != '..') {
2308 if (is_link($fullpath.'/'.$e)) {
2309 $deref = realpath($fullpath.'/'.$e);
2310 if (is_file($deref)) {
2311 if (is_readable($deref)) {
2312 $this->zipfiles_batched[$deref] = $use_path_when_storing.'/'.$e;
2313 @touch($zipfile);
2314 } else {
2315 $this->log("$deref: unreadable file");
2316 $this->error("$deref: unreadable file");
2317 }
2318 } elseif (is_dir($deref)) {
2319 $this->makezip_recursive_add($zipfile, $deref, $use_path_when_storing.'/'.$e, $original_fullpath);
2320 }
2321 } elseif (is_file($fullpath.'/'.$e)) {
2322 if (is_readable($fullpath.'/'.$e)) {
2323 $this->zipfiles_batched[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e;
2324 @touch($zipfile);
2325 } else {
2326 $this->log("$fullpath/$e: unreadable file");
2327 $this->error("$fullpath/$e: unreadable file");
2328 }
2329 } elseif (is_dir($fullpath.'/'.$e)) {
2330 // no need to addEmptyDir here, as it gets done when we recurse
2331 $this->makezip_recursive_add($zipfile, $fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath);
2332 }
2333 }
2334 }
2335 closedir($dir_handle);
2336 }
2337
2338 // We don't want to touch the zip file on every single file, so we batch them up
2339 // We go every 25 files, because if you wait too much longer, the contents may have changed from under you
2340 // And for some redundancy (redundant because of the touches going on anyway), we try to touch the file after 20 seconds, to help with the "recently modified" check on resumption (we saw a case where the file went for 155 seconds without being touched and so the other runner was not detected)
2341 if (count($this->zipfiles_batched) > 25 || (file_exists($zipfile) && ((time()-filemtime($zipfile)) > 20) )) {
2342 $ret = $this->makezip_addfiles($zipfile);
2343 } else {
2344 $ret = true;
2345 }
2346
2347 return $ret;
2348
2349 }
2350
2351 }
2352
2353
2354 ?>
2355