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

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