PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.3.19
UpdraftPlus: WP Backup & Migration Plugin v1.3.19
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.3.19, at updraftplus.php

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