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