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

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