PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 0.7.7
UpdraftPlus: WP Backup & Migration Plugin v0.7.7
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 0.7.7, at updraftplus.php

1,549 lines 64.7 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: UpdraftPlus - Backup/Restore is a plugin designed to back up your WordPress site. Uploads, themes, plugins, and your DB can be backed up to Amazon S3, sent to an FTP server, or even emailed to you on a scheduled basis.
6 Author: David Anderson.
7 Version: 0.7.7
8 Author URI: http://wordshell.net
9 */
10
11 //TODO:
12 //Put DB and file backups onto separate schedules. If the option is set identically then do in one run, otherwise do separately.
13 //Add DropBox support
14 //Add more logging
15 //Struggles with large uploads - runs out of time before finishing. Break into chunks? Resume download on later run? (Add a new scheduled event to check on progress? Separate the upload from the creation?). Add in some logging (in a .php file that exists first).
16 //More logging
17 //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
18 //better implementation of retain. one that isn't dependent on being inside the cloud_backup method
19 //list backups that aren't tracked (helps with double backup problem)
20 //refactor db backup methods a bit. give full credit to wp-db-backup
21 //investigate $php_errormsg further
22 //pretty up return messages in admin area
23 //check s3/ftp download
24 //allow upload of backup files too. (specify 1-4 files to restore)
25 //Add back donate link in readme.txt header. Donate link: URL
26 //user permissions for WP users if ( function_exists('is_site_admin') && ! is_site_admin() ) around backups?
27
28 /* More TODO:
29 Are all directories in wp-content covered? No; only plugins, themes, content. We should check for others and allow the user the chance to choose which ones he wants
30 Add turn-off-foreign-key-checks stuff into mysql dump (does WP even use these?)
31 Use only one entry in WP options database
32 Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
33 More verbose debug reports, send debug report in the email
34 */
35
36 /* Portions copyright 2010 Paul Kehrer
37 Portions copyright 2011-12 David Anderson
38
39 This program is free software; you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation; either version 2 of the License, or
42 (at your option) any later version.
43
44 This program is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with this program; if not, write to the Free Software
51 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
52 */
53 // TODO: Note this might *lower* the limit - should check first.
54
55 @set_time_limit(900); //15 minutes max. i'm not sure how long a really big site could take to back up?
56
57 $updraft = new UpdraftPlus();
58
59 if(!$updraft->memory_check(192)) {
60 # TODO: Better solution is to split the backup set into manageable chunks based on this limit
61 @ini_set('memory_limit', '192M'); //up the memory limit for large backup files... should split the backup set into manageable chunks based on the limit
62 }
63
64 class UpdraftPlus {
65
66 var $version = '0.7.7';
67
68 var $dbhandle;
69 var $errors = array();
70 var $nonce;
71 var $logfile_name = "";
72 var $logfile_handle = false;
73 var $backup_time;
74
75 function __construct() {
76 // Initialisation actions
77 # Create admin page
78 add_action('admin_menu', array($this,'add_admin_pages'));
79 add_action('admin_init', array($this,'admin_init'));
80 add_action('updraft_backup', array($this,'backup'));
81 add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
82 add_filter('cron_schedules', array($this,'modify_cron_schedules'));
83 add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
84 }
85
86 # Adds the settings link under the plugin on the plugin screen.
87 function plugin_action_links($links, $file) {
88 if ($file == plugin_basename(__FILE__)){
89 $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraft-backuprestore.php">'.__("Settings", "wp-updates-notifier").'</a>';
90 array_unshift($links, $settings_link);
91 }
92 return $links;
93 }
94
95 function backup_time_nonce() {
96 $this->backup_time = time();
97 $this->nonce = substr(md5(time().rand()),20);
98 }
99
100 # Logs the given line, adding date stamp and newline
101 function log($line) {
102 if ($this->logfile_handle) {
103 fwrite($this->logfile_handle,date('r')." ".$line."\n");
104 }
105 }
106
107 //scheduled wp-cron events can have a race condition here if page loads are coming fast enough, but there's nothing we can do about it.
108 function backup() {
109 //generate backup information
110 $this->backup_time_nonce();
111
112 //set log file name
113 $updraft_dir = $this->backups_dir_location();
114 $this->logfile_name = $updraft_dir. "/log." . $this->nonce . ".txt";
115
116 # Use append mode in case it already exists
117 $this->logfile_handle = fopen($this->logfile_name, 'a');
118 // Some information that may be helpful
119 global $wp_version;
120 $this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version);
121 //backup directories and return a numerically indexed array of file paths to the backup files
122 $this->log("Beginning backup of directories");
123 $backup_array = $this->backup_dirs();
124 //backup DB and return string of file path
125 $this->log("Beginning backup of database");
126 $db_backup = $this->backup_db();
127 //add db path to rest of files
128 if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
129 //save this to our history so we can track backups for the retain feature
130 $this->log("Saving backup history");
131 $this->save_backup_history($backup_array);
132
133 //cloud operations (S3,FTP,email,nothing)
134 //this also calls the retain feature at the end (done in this method to reuse existing cloud connections)
135 if(is_array($backup_array) && count($backup_array) >0) {
136 $this->log("Beginning dispatch of backup to remote");
137 $this->cloud_backup($backup_array);
138 }
139 //delete local files if the pref is set
140 foreach($backup_array as $file) {
141 $this->log("Deleting local file: $file");
142 $this->delete_local($file);
143 }
144
145 //save the last backup info, including errors, if any
146 $this->log("Saving last backup information into WordPress db");
147 $this->save_last_backup($backup_array);
148
149 if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') {
150 $sendmail_to = get_option('updraft_email');
151 $this->log("Sending email report to: ".$sendmail_to);
152 $append_log = "";
153 if(get_option('updraft_debug_mode') && $this->logfile_name != "") {
154 $append_log .= "\r\nLog contents:\r\n".file_get_contents($this->logfile_name);
155 }
156 wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus) '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
157 }
158
159 // Close log file
160 close($this->logfile_handle);
161 if (!get_option('updraft_debug_mode')) { @unlink($this->logfile_name); }
162 }
163
164 function save_last_backup($backup_array) {
165 $success = (empty($this->errors))?1:0;
166 $last_backup = array('backup_time'=>$this->backup_time,'backup_array'=>$backup_array,'success'=>$success,'errors'=>$this->errors);
167 update_option('updraft_last_backup',$last_backup);
168 }
169
170 function cloud_backup($backup_array) {
171 switch(get_option('updraft_service')) {
172 case 's3':
173 @set_time_limit(900);
174 $this->log("Cloud backup: S3");
175 if (count($backup_array) >0) { $this->s3_backup($backup_array); }
176 break;
177 case 'ftp':
178 @set_time_limit(900);
179 $this->log("Cloud backup: FTP");
180 if (count($backup_array) >0) { $this->ftp_backup($backup_array); }
181 break;
182 case 'email':
183 @set_time_limit(900);
184 $this->log("Cloud backup: Email");
185 //files can easily get way too big for this...
186 foreach($backup_array as $type=>$file) {
187 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
188 wp_mail(get_option('updraft_email'),"WordPress Backup ".date('Y-m-d H:i',$this->backup_time),"Backup is of the $type. Be wary; email backups may fail because of file size limitations on mail servers.",null,array($fullpath));
189 }
190 //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
191 default:
192 /*retain behavior*/
193 $updraft_retain = get_option('updraft_retain');
194 $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
195 $backup_history = $this->get_backup_history();
196 while (count($backup_history) > $retain) {
197 $backup_to_delete = array_pop($backup_history);
198 foreach($backup_to_delete as $file) {
199 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
200 @unlink($fullpath); //delete it if it's locally available
201 }
202 }
203 update_option('updraft_backup_history',$backup_history);
204 /*retain behavior*/
205 break;
206 }
207 }
208
209 function s3_backup($backup_array) {
210 if(!class_exists('S3')) {
211 require_once(dirname(__FILE__).'/includes/S3.php');
212 }
213 $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
214 $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
215 if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
216 foreach($backup_array as $file) {
217 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
218 if (!$s3->putObjectFile($fullpath, $bucket_name, $file)) {
219 $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
220 }
221 }
222 } else {
223 $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
224 }
225 /*retain behavior*/
226 $updraft_retain = get_option('updraft_retain');
227 $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
228 $backup_history = $this->get_backup_history();
229 while (count($backup_history) > $retain) {
230 $backup_to_delete = array_pop($backup_history);
231 foreach($backup_to_delete as $file) {
232 //if for some reason one of the backup files is an empty string let's skip it.
233 if($file == '') {
234 continue;
235 }
236 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
237 @unlink($fullpath); //delete it if it's locally available
238 if (!$s3->deleteObject($bucket_name, $file)) {
239 $this->error("S3 Error: Failed to delete object $file. Error was ".$php_errormsg);
240 }
241 }
242 }
243 update_option('updraft_backup_history',$backup_history);
244 /*retain behavior*/
245 }
246
247 function ftp_backup($backup_array) {
248 if( !class_exists('ftp_wrapper')) {
249 require_once(dirname(__FILE__).'/includes/ftp.class.php');
250 }
251 //handle SSL and errors at some point TODO
252 $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
253 $ftp->passive = true;
254 $ftp->connect();
255 //$ftp->make_dir(); we may need to recursively create dirs? TODO
256
257 $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
258 foreach($backup_array as $file) {
259 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
260 $ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY);
261 }
262
263 /*retain behavior*/
264 $updraft_retain = get_option('updraft_retain');
265 $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
266 $backup_history = $this->get_backup_history();
267 while (count($backup_history) > $retain) {
268 $backup_to_delete = array_pop($backup_history);
269 foreach($backup_to_delete as $file) {
270 //if for some reason one of the backup files is an empty string let's skip it.
271 if($file == '') {
272 continue;
273 }
274 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
275 @unlink($fullpath); //delete it if it's locally available
276 @$ftp->delete($ftp_remote_path.$file);
277 }
278 }
279 update_option('updraft_backup_history',$backup_history);
280 /*retain behavior*/
281 }
282
283 function delete_local($file) {
284 if(get_option('updraft_delete_local')) {
285 //need error checking so we don't delete what isn't successfully uploaded?
286 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
287 return unlink($fullpath);
288 }
289 return true;
290 }
291
292 function backup_dirs() {
293 if(!$this->backup_time) {
294 $this->backup_time_nonce();
295 }
296 $wp_themes_dir = WP_CONTENT_DIR.'/themes';
297 $wp_upload_dir = wp_upload_dir();
298 $wp_upload_dir = $wp_upload_dir['basedir'];
299 $wp_plugins_dir = WP_PLUGIN_DIR;
300 if(!class_exists('PclZip')) {
301 if (file_exists(ABSPATH.'/wp-admin/includes/class-pclzip.php')) {
302 require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
303 }
304 }
305 $updraft_dir = $this->backups_dir_location();
306 if(!is_writable($updraft_dir)) {
307 $this->error('Backup directory is not writable.','fatal');
308 }
309 //get the blog name and rip out all non-alphanumeric chars other than _
310 $blog_name = str_replace(' ','_',get_bloginfo());
311 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
312 if(!$blog_name) {
313 $blog_name = 'non_alpha_name';
314 }
315
316 $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
317
318 $backup_array = array();
319
320 # Plugins
321 @set_time_limit(900);
322 if (get_option('updraft_include_plugins', true)) {
323 $plugins = new PclZip($backup_file_base.'-plugins.zip');
324 if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
325 $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
326 }
327 $backup_array['plugins'] = basename($backup_file_base.'-plugins.zip');
328 }
329
330 # Themes
331 @set_time_limit(900);
332 if (get_option('updraft_include_themes', true)) {
333 $themes = new PclZip($backup_file_base.'-themes.zip');
334 if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
335 $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
336 }
337 $backup_array['themes'] = basename($backup_file_base.'-themes.zip');
338 }
339
340 # Uploads
341 @set_time_limit(900);
342 if (get_option('updraft_include_uploads', true)) {
343 $uploads = new PclZip($backup_file_base.'-uploads.zip');
344 if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
345 $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
346 }
347 $backup_array['uploads'] = basename($backup_file_base.'-uploads.zip');
348 }
349
350 return $backup_array;
351 }
352
353 function save_backup_history($backup_array) {
354 //this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
355 $backup_history = get_option('updraft_backup_history');
356 $backup_history = (!is_array($backup_history))?array():$backup_history;
357 if(is_array($backup_array)) {
358 $backup_history[$this->backup_time] = $backup_array;
359 update_option('updraft_backup_history',$backup_history);
360 } else {
361 $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
362 }
363 }
364
365 function get_backup_history() {
366 //$backup_history = get_option('updraft_backup_history');
367 //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
368 global $wpdb;
369 $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
370 if(is_array($backup_history)) {
371 krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
372 } else {
373 $backup_history = array();
374 }
375 return $backup_history;
376 }
377
378
379 /*START OF WB-DB-BACKUP BLOCK*/
380
381 function backup_db() {
382 global $table_prefix, $wpdb;
383 if(!$this->backup_time) {
384 $this->backup_time_nonce();
385 }
386
387 $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
388 $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
389
390 $updraft_dir = $this->backups_dir_location();
391 //get the blog name and rip out all non-alphanumeric chars other than _
392 $blog_name = str_replace(' ','_',get_bloginfo());
393 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
394 if(!$blog_name) {
395 $blog_name = 'non_alpha_name';
396 }
397
398 $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
399 if (is_writable($updraft_dir)) {
400 if (function_exists('gzopen')) {
401 $this->dbhandle = @gzopen($backup_file_base.'-db.gz','w');
402 } else {
403 $this->dbhandle = @fopen($backup_file_base.'-db.gz', 'w');
404 }
405 if(!$this->dbhandle) {
406 //$this->error(__('Could not open the backup file for writing!','wp-db-backup'));
407 }
408 } else {
409 //$this->error(__('The backup directory is not writable!','wp-db-backup'));
410 }
411
412
413 //Begin new backup of MySql
414 $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
415 $this->stow("#\n");
416 $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
417 $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
418 $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
419 $this->stow("# --------------------------------------------------------\n");
420
421
422 if (defined("DB_CHARSET")) {
423 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
424 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
425 $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
426 $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
427 }
428
429 foreach ($all_tables as $table) {
430 // Increase script execution time-limit to 15 min for every table.
431 if ( !ini_get('safe_mode')) @set_time_limit(15*60);
432 if ( strpos($table, $table_prefix) == 0 ) {
433 // Create the SQL statements
434 $this->stow("# --------------------------------------------------------\n");
435 $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
436 $this->stow("# --------------------------------------------------------\n");
437 $this->backup_table($table);
438 } else {
439 $this->stow("# --------------------------------------------------------\n");
440 $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
441 $this->stow("# --------------------------------------------------------\n");
442 }
443 }
444
445 if (defined("DB_CHARSET")) {
446 $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
447 $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
448 $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
449 }
450
451 $this->close($this->dbhandle);
452
453 if (count($this->errors)) {
454 return false;
455 } else {
456 # Encrypt, if requested
457 $encryption = get_option('updraft_encryptionphrase');
458 if (strlen($encryption) > 0) {
459 $encryption_error = 0;
460 require_once(dirname(__FILE__).'/includes/Rijndael.php');
461 $rijndael = new Crypt_Rijndael();
462 $rijndael->setKey($encryption);
463 $in_handle = @fopen($backup_file_base.'-db.gz','r');
464 $buffer = "";
465 while (!feof ($in_handle)) {
466 $buffer .= fread($in_handle, 16384);
467 }
468 fclose ($in_handle);
469 $out_handle = @fopen($backup_file_base.'-db.gz.crypt','w');
470 if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
471 fclose ($out_handle);
472 if (0 == $encryption_error) {
473 # Delete unencrypted file
474 @unlink($backup_file_base.'-db.gz');
475 return basename($backup_file_base.'-db.gz.crypt');
476 } else {
477 $this->error("Encryption error occurred when encrypting database. Aborted.");
478 }
479 } else {
480 return basename($backup_file_base.'-db.gz');
481 }
482 }
483
484 } //wp_db_backup
485
486 /**
487 * Taken partially from phpMyAdmin and partially from
488 * Alain Wolf, Zurich - Switzerland
489 * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
490 * Modified by Scott Merrill (http://www.skippy.net/)
491 * to use the WordPress $wpdb object
492 * @param string $table
493 * @param string $segment
494 * @return void
495 */
496 function backup_table($table, $segment = 'none') {
497 global $wpdb;
498
499 $table_structure = $wpdb->get_results("DESCRIBE $table");
500 if (! $table_structure) {
501 //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
502 return false;
503 }
504
505 if(($segment == 'none') || ($segment == 0)) {
506 // Add SQL statement to drop existing table
507 $this->stow("\n\n");
508 $this->stow("#\n");
509 $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
510 $this->stow("#\n");
511 $this->stow("\n");
512 $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
513
514 // Table structure
515 // Comment in SQL-file
516 $this->stow("\n\n");
517 $this->stow("#\n");
518 $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
519 $this->stow("#\n");
520 $this->stow("\n");
521
522 $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
523 if (false === $create_table) {
524 $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
525 //$this->error($err_msg);
526 $this->stow("#\n# $err_msg\n#\n");
527 }
528 $this->stow($create_table[0][1] . ' ;');
529
530 if (false === $table_structure) {
531 $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
532 //$this->error($err_msg);
533 $this->stow("#\n# $err_msg\n#\n");
534 }
535
536 // Comment in SQL-file
537 $this->stow("\n\n");
538 $this->stow("#\n");
539 $this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
540 $this->stow("#\n");
541 }
542
543 if(($segment == 'none') || ($segment >= 0)) {
544 $defs = array();
545 $ints = array();
546 foreach ($table_structure as $struct) {
547 if ( (0 === strpos($struct->Type, 'tinyint')) ||
548 (0 === strpos(strtolower($struct->Type), 'smallint')) ||
549 (0 === strpos(strtolower($struct->Type), 'mediumint')) ||
550 (0 === strpos(strtolower($struct->Type), 'int')) ||
551 (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
552 $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
553 $ints[strtolower($struct->Field)] = "1";
554 }
555 }
556
557
558 // Batch by $row_inc
559 if ( ! defined('ROWS_PER_SEGMENT') ) {
560 define('ROWS_PER_SEGMENT', 100);
561 }
562
563 if($segment == 'none') {
564 $row_start = 0;
565 $row_inc = ROWS_PER_SEGMENT;
566 } else {
567 $row_start = $segment * ROWS_PER_SEGMENT;
568 $row_inc = ROWS_PER_SEGMENT;
569 }
570 do {
571 // don't include extra stuff, if so requested
572 $excs = array('revisions' => 0, 'spam' => 1); //TODO, FIX THIS
573 $where = '';
574 if ( is_array($excs['spam'] ) && in_array($table, $excs['spam']) ) {
575 $where = ' WHERE comment_approved != "spam"';
576 } elseif ( is_array($excs['revisions'] ) && in_array($table, $excs['revisions']) ) {
577 $where = ' WHERE post_type != "revision"';
578 }
579
580 if ( !ini_get('safe_mode')) @set_time_limit(15*60);
581 $table_data = $wpdb->get_results("SELECT * FROM $table $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
582 $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
583 // \x08\\x09, not required
584 $search = array("\x00", "\x0a", "\x0d", "\x1a");
585 $replace = array('\0', '\n', '\r', '\Z');
586 if($table_data) {
587 foreach ($table_data as $row) {
588 $values = array();
589 foreach ($row as $key => $value) {
590 if ($ints[strtolower($key)]) {
591 // make sure there are no blank spots in the insert syntax,
592 // yet try to avoid quotation marks around integers
593 $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
594 $values[] = ( '' === $value ) ? "''" : $value;
595 } else {
596 $values[] = "'" . str_replace($search, $replace, $this->sql_addslashes($value)) . "'";
597 }
598 }
599 $this->stow(" \n" . $entries . implode(', ', $values) . ');');
600 }
601 $row_start += $row_inc;
602 }
603 } while((count($table_data) > 0) and ($segment=='none'));
604 }
605
606 if(($segment == 'none') || ($segment < 0)) {
607 // Create footer/closing comment in SQL-file
608 $this->stow("\n");
609 $this->stow("#\n");
610 $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
611 $this->stow("# --------------------------------------------------------\n");
612 $this->stow("\n");
613 }
614 } // end backup_table()
615
616
617 function stow($query_line) {
618 if (function_exists('gzopen')) {
619 if(! @gzwrite($this->dbhandle, $query_line)) {
620 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
621 }
622 } else {
623 if(false === @fwrite($this->dbhandle, $query_line)) {
624 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
625 }
626 }
627 }
628
629
630 function close($handle) {
631 if (function_exists('gzopen')) {
632 gzclose($handle);
633 } else {
634 fclose($handle);
635 }
636 }
637
638 /**
639 * Logs any error messages
640 * @param array $args
641 * @return bool
642 */
643 function error($error,$severity='') {
644 $this->errors[] = array('error'=>$error,'severity'=>$severity);
645 if ($severity == 'fatal') {
646 //do something...
647 }
648 return true;
649 }
650
651
652
653 /**
654 * Add backquotes to tables and db-names in
655 * SQL queries. Taken from phpMyAdmin.
656 */
657 function backquote($a_name) {
658 if (!empty($a_name) && $a_name != '*') {
659 if (is_array($a_name)) {
660 $result = array();
661 reset($a_name);
662 while(list($key, $val) = each($a_name))
663 $result[$key] = '`' . $val . '`';
664 return $result;
665 } else {
666 return '`' . $a_name . '`';
667 }
668 } else {
669 return $a_name;
670 }
671 }
672
673 /**
674 * Better addslashes for SQL queries.
675 * Taken from phpMyAdmin.
676 */
677 function sql_addslashes($a_string = '', $is_like = false) {
678 if ($is_like) $a_string = str_replace('\\', '\\\\\\\\', $a_string);
679 else $a_string = str_replace('\\', '\\\\', $a_string);
680 return str_replace('\'', '\\\'', $a_string);
681 }
682
683 /*END OF WP-DB-BACKUP BLOCK */
684 /*END OF WP-DB-BACKUP BLOCK */
685 /*END OF WP-DB-BACKUP BLOCK */
686 /*END OF WP-DB-BACKUP BLOCK */
687 /*END OF WP-DB-BACKUP BLOCK */
688
689 /*
690 this function is both the backup scheduler and ostensibly a filter callback for saving the option.
691 it is called in the register_setting for the updraft_interval, which means when the admin settings
692 are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
693 so the option can be properly saved.
694 */
695 function schedule_backup($interval) {
696 //clear schedule and add new so we don't stack up scheduled backups
697 wp_clear_scheduled_hook('updraft_backup');
698 switch($interval) {
699 case 'daily':
700 case 'weekly':
701 case 'monthly':
702 wp_schedule_event(time()+30, $interval, 'updraft_backup');
703 break;
704 }
705 return wp_filter_nohtml_kses($interval);
706 }
707
708 //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
709 function modify_cron_schedules($schedules) {
710 $schedules['weekly'] = array(
711 'interval' => 604800,
712 'display' => 'Once Weekly'
713 );
714 $schedules['monthly'] = array(
715 'interval' => 2592000,
716 'display' => 'Once Monthly'
717 );
718 return $schedules;
719 }
720
721 function backups_dir_location() {
722 $updraft_dir = untrailingslashit(get_option('updraft_dir'));
723 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
724 //if the option isn't set, default it to /backups inside the upload dir
725 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
726 //check for the existence of the dir and an enumeration preventer.
727 if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
728 @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
729 @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
730 @file_put_contents($updraft_dir.'/.htaccess','deny from all');
731 }
732 return $updraft_dir;
733 }
734
735 function updraft_download_backup() {
736 $type = $_POST['type'];
737 $timestamp = (int)$_POST['timestamp'];
738 $backup_history = $this->get_backup_history();
739 $file = $backup_history[$timestamp][$type];
740 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
741 if(!is_readable($fullpath)) {
742 //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
743 $this->download_backup($file);
744 }
745 if(@is_readable($fullpath) && is_file($fullpath)) {
746 $len = filesize($fullpath);
747
748 $filearr = explode('.',$file);
749 //we've only got zip and gz...for now
750 $file_ext = array_pop($filearr);
751 if($file_ext == 'zip') {
752 header('Content-type: application/zip');
753 } else {
754 // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
755 header('Content-type: application/x-gzip');
756 }
757 header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
758 header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
759 header("Content-Length: $len;");
760 if ($file_ext == 'crypt') {
761 header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
762 } else {
763 header("Content-Disposition: attachment; filename=\"$file\";");
764 }
765 ob_end_flush();
766 if ($file_ext == 'crypt') {
767 $encryption = get_option('updraft_encryptionphrase');
768 if ($encryption == "") {
769 $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
770 } else {
771 require_once(dirname(__FILE__).'/includes/Rijndael.php');
772 $rijndael = new Crypt_Rijndael();
773 $rijndael->setKey($encryption);
774 $in_handle = fopen($fullpath,'r');
775 $ciphertext = "";
776 while (!feof ($in_handle)) {
777 $ciphertext .= fread($in_handle, 16384);
778 }
779 fclose ($in_handle);
780 print $rijndael->decrypt($ciphertext);
781 }
782 } else {
783 readfile($fullpath);
784 }
785 $this->delete_local($file);
786 exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end for some reason I don't understand. seriously, why die('0')?
787 } else {
788 echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or FTP retrieval may have failed.';
789 }
790 }
791
792 function download_backup($file) {
793 switch(get_option('updraft_service')) {
794 case 's3':
795 $this->download_s3_backup($file);
796 break;
797 case 'ftp':
798 $this->download_ftp_backup($file);
799 break;
800 default:
801 $this->error('Automatic backup restoration is only available via S3, FTP, and local. Email and downloaded backup restoration must be performed manually.');
802 }
803 }
804
805 function download_s3_backup($file) {
806 if(!class_exists('S3')) {
807 require_once(dirname(__FILE__).'/includes/S3.php');
808 }
809 $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
810 $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
811 if (@$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
812 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
813 if (!$s3->getObject($bucket_name, $file, $fullpath)) {
814 $this->error("S3 Error: Failed to download $fullpath. Error was ".$php_errormsg);
815 }
816 } else {
817 $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
818 }
819 }
820
821 function download_ftp_backup($file) {
822 if( !class_exists('ftp_wrapper')) {
823 require_once(dirname(__FILE__).'/includes/ftp.class.php');
824 }
825 //handle SSL and errors at some point TODO
826 $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
827 $ftp->passive = true;
828 $ftp->connect();
829 //$ftp->make_dir(); we may need to recursively create dirs? TODO
830
831 $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
832 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
833 $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
834 }
835
836 function restore_backup($timestamp) {
837 global $wp_filesystem;
838 $backup_history = get_option('updraft_backup_history');
839 if(!is_array($backup_history[$timestamp])) {
840 echo '<p>This backup does not exist in the backup history -- restoration aborted! timestamp: '.$timestamp.'</p><br/>';
841 return false;
842 }
843
844 $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_restore&backup_timestamp=$timestamp");
845 WP_Filesystem($credentials);
846 if ( $wp_filesystem->errors->get_error_code() ) {
847 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
848 show_message($message);
849 exit;
850 }
851
852 //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?)
853 echo '<span style="font-weight:bold">Restoration Progress </span><div id="updraft-restore-progress">';
854
855 $updraft_dir = trailingslashit(get_option('updraft_dir'));
856 foreach($backup_history[$timestamp] as $type=>$file) {
857 $fullpath = $updraft_dir.$file;
858 if(!is_readable($fullpath) && $type != 'db') {
859 $this->download_backup($file);
860 }
861 if(is_readable($fullpath) && $type != 'db') {
862 if(!class_exists('WP_Upgrader')) {
863 require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
864 }
865 require_once('includes/updraft-restorer.php');
866 $restorer = new Updraft_Restorer();
867 $val = $restorer->restore_backup($fullpath,$type);
868 if(is_wp_error($val)) {
869 print_r($val);
870 echo '</div>'; //close the updraft_restore_progress div even if we error
871 return false;
872 }
873 }
874 }
875 echo '</div>'; //close the updraft_restore_progress div
876 if(ini_get('safe_mode')) {
877 echo "<p>DB could not be restored because safe_mode is active on your server. You will need to manually restore the file via phpMyAdmin or another method.</p><br/>";
878 return false;
879 }
880 return true;
881 }
882
883
884 //deletes the -old directories that are created when a backup is restored.
885 function delete_old_dirs() {
886 global $wp_filesystem;
887 $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_delete_old_dirs");
888 WP_Filesystem($credentials);
889 if ( $wp_filesystem->errors->get_error_code() ) {
890 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
891 show_message($message);
892 exit;
893 }
894
895 $to_delete = array('themes-old','plugins-old','uploads-old');
896
897 foreach($to_delete as $name) {
898 //recursively delete
899 if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
900 return false;
901 }
902 }
903 return true;
904 }
905
906 //scans the content dir to see if any -old dirs are present
907 function scan_old_dirs() {
908 $dirArr = scandir(WP_CONTENT_DIR);
909 foreach($dirArr as $dir) {
910 if(strpos($dir,'-old') !== false) {
911 return true;
912 }
913 }
914 return false;
915 }
916
917
918 function retain_range($input) {
919 $input = (int)$input;
920 if($input > 0 && $input < 3650) {
921 return $input;
922 } else {
923 return 1;
924 }
925 }
926
927 function create_backup_dir() {
928 global $wp_filesystem;
929 $credentials = request_filesystem_credentials("options-general.php?page=updraft-backuprestore.php&action=updraft_create_backup_dir");
930 WP_Filesystem($credentials);
931 if ( $wp_filesystem->errors->get_error_code() ) {
932 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
933 show_message($message);
934 exit;
935 }
936
937 $updraft_dir = untrailingslashit(get_option('updraft_dir'));
938 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
939 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
940
941 //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...)
942 if(!$wp_filesystem->mkdir($updraft_dir, 0777)) {
943 return false;
944 }
945 return true;
946 }
947
948
949 function memory_check_current() {
950 # Returns in megabytes
951 $memory_limit = ini_get('memory_limit');
952 $memory_unit = $memory_limit[strlen($memory_limit)-1];
953 $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
954 switch($memory_unit) {
955 case 'K':
956 $memory_limit = $memory_limit/1024;
957 break;
958 case 'G':
959 $memory_limit = $memory_limit*1024;
960 break;
961 case 'M':
962 //assumed size, no change needed
963 break;
964 }
965 return $memory_limit;
966 }
967
968 function memory_check($memory) {
969 $memory_limit = $this->memory_check_current();
970 return ($memory_limit >= $memory)?true:false;
971 }
972
973 function execution_time_check($time) {
974 return (ini_get('max_execution_time') >= $time)?true:false;
975 }
976
977 function admin_init() {
978 if(get_option('updraft_debug_mode')) {
979 ini_set('display_errors',1);
980 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
981 ini_set('track_errors',1);
982 }
983 wp_enqueue_script('jquery');
984 register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
985 register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
986 register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
987 register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
988 register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
989 register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
990 register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
991 register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
992 register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
993 register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
994 register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
995 register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
996 register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
997 register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
998 register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
999 register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
1000 register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
1001 register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1002
1003 if (current_user_can('manage_options')) {
1004 $updraft_dir = $this->backups_dir_location();
1005 if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
1006 $relative_dir = str_replace(WP_CONTENT_DIR,'',$updraft_dir);
1007 $possible_updraft_url = WP_CONTENT_URL.$relative_dir;
1008 $resp = wp_remote_request($possible_updraft_url, array('timeout' => 15));
1009 if ( is_wp_error($resp) ) {
1010 add_action('admin_notices', array($this,'show_admin_warning_accessible_unknownresult') );
1011 } else {
1012 if(strpos($resp['response']['code'],'403') === false) {
1013 add_action('admin_notices', array($this,'show_admin_warning_accessible') );
1014 }
1015 }
1016 if (isset($dir_protection_info)) {
1017 }
1018 }
1019 }
1020 }
1021
1022
1023 function add_admin_pages() {
1024 add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraft-backuprestore.php",
1025 array($this,"settings_output"));
1026 }
1027
1028 function wordshell_random_advert($urls) {
1029 $url_start = ($urls) ? '<a href="http://wordshell.net">' : "";
1030 $url_end = ($urls) ? '</a>' : " (www.wordshell.net)";
1031 if (rand(0,1) == 0) {
1032 return "Like automating WordPress operations? Use the CLI? ${url_start}You will love WordShell${url_end} - saves time and money fast.";
1033 } else {
1034 return "${url_start}Check out WordShell${url_end} - manage WordPress from the command line - huge time-saver";
1035 }
1036 }
1037
1038 function settings_output() {
1039
1040 $ws_advert = $this->wordshell_random_advert(1);
1041 echo <<<ENDHERE
1042 <div class="updated fade" style="font-size:140%; padding:14px;">${ws_advert}</div>
1043 ENDHERE;
1044
1045 /*
1046 we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1047 for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
1048 passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
1049 */
1050 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
1051 $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1052 if(empty($this->errors) && $backup_success == true) {
1053 echo '<p>Restore successful!</p><br/>';
1054 echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1055 return;
1056 } else {
1057 echo '<p>Restore failed...</p><br/>';
1058 echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1059 return;
1060 }
1061 //uncomment the below once i figure out how i want the flow of a restoration to work.
1062 //echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1063 }
1064 $deleted_old_dirs = false;
1065 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1066 if($this->delete_old_dirs()) {
1067 $deleted_old_dirs = true;
1068 } else {
1069 echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1070 }
1071 echo '<p>Old directories successfully removed.</p><br/>';
1072 echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1073 return;
1074 }
1075
1076 if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1077 if(!$this->create_backup_dir()) {
1078 echo '<p>Backup directory could not be created...</p><br/>';
1079 }
1080 echo '<p>Backup directory successfully created.</p><br/>';
1081 echo '<b>Actions:</b> <a href="options-general.php?page=updraft-backuprestore.php">Return to Updraft Configuration</a>.';
1082 return;
1083 }
1084
1085 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1086 wp_schedule_single_event(time()+3, 'updraft_backup');
1087 }
1088 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1089 $this->backup();
1090 }
1091 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1092 $this->backup_db();
1093 }
1094
1095 ?>
1096 <div class="wrap">
1097 <h2>UpdraftPlus - Backup/Restore</h2>
1098
1099 Version: <b><?php echo $this->version; ?></b><br />
1100 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> )
1101 <br />
1102 Based on 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> )
1103 <br />
1104 <?php
1105 if(isset($_GET['updraft_restore_success'])) {
1106 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>";
1107 }
1108 if($deleted_old_dirs) {
1109 echo "<div style=\"color:blue\">Old directories successfully deleted.</div>";
1110 }
1111 if(!$this->memory_check(96)) {?>
1112 <div style="color:orange">Your PHP memory limit is too low. Updraft attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb. Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
1113 <?php
1114 }
1115 if(!$this->execution_time_check(300)) {?>
1116 <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>
1117 <?php
1118 }
1119
1120 if($this->scan_old_dirs()) {?>
1121 <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>
1122 <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
1123 <input type="hidden" name="action" value="updraft_delete_old_dirs" />
1124 <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.'))" />
1125 </form>
1126 <?php
1127 }
1128 if(!empty($this->errors)) {
1129 foreach($this->errors as $error) {
1130 //ignoring severity here right now
1131 echo '<div style="color:red">'.$error['error'].'</div>';
1132 }
1133 }
1134 ?>
1135 <table class="form-table" style="float:left;width:475px">
1136 <tr>
1137 <?php
1138 $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1139 if($next_scheduled_backup) {
1140 $next_scheduled_backup = date('D, F j, Y H:i T',$next_scheduled_backup);
1141 } else {
1142 $next_scheduled_backup = 'No backups are scheduled at this time.';
1143 }
1144 $current_time = date('D, F j, Y H:i T',time());
1145 $updraft_last_backup = get_option('updraft_last_backup');
1146 if($updraft_last_backup) {
1147 if($updraft_last_backup['success']) {
1148 $last_backup = date('D, F j, Y H:i T',$updraft_last_backup['backup_time']);
1149 $last_backup_color = 'green';
1150 } else {
1151 $last_backup = print_r($updraft_last_backup['errors'],true);
1152 $last_backup_color = 'red';
1153 }
1154 } else {
1155 $last_backup = 'No backup has been completed.';
1156 $last_backup_color = 'blue';
1157 }
1158
1159 $updraft_dir = $this->backups_dir_location();
1160 if(is_writable($updraft_dir)) {
1161 $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
1162 $backup_disabled = "";
1163 } else {
1164 $backup_disabled = 'disabled="disabled"';
1165 $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraft-backuprestore.php&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>';
1166 }
1167 ?>
1168 <th>Current Time:</th>
1169 <td style="color:blue"><?php echo $current_time?></td>
1170 </tr>
1171 <tr>
1172 <th>Next Scheduled Backup:</th>
1173 <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1174 </tr>
1175 <tr>
1176 <th>Last Backup:</th>
1177 <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1178 </tr>
1179 </table>
1180 <div style="float:left;width:200px">
1181 <form method="post" action="">
1182 <input type="hidden" name="action" value="updraft_backup" />
1183 <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 immediately you may need to load a page on your site.'))" /></p>
1184 </form>
1185 <div style="position:relative">
1186 <div style="position:absolute;top:0;left:0">
1187 <?php
1188 $backup_history = get_option('updraft_backup_history');
1189 $backup_history = (is_array($backup_history))?$backup_history:array();
1190 $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
1191 ?>
1192 <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')" />
1193 </div>
1194 <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
1195 <form method="post" action="">
1196 <b>Choose: </b>
1197 <select name="backup_timestamp" style="display:inline">
1198 <?php
1199 foreach($backup_history as $key=>$value) {
1200 echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
1201 }
1202 ?>
1203 </select>
1204
1205 <input type="hidden" name="action" value="updraft_restore" />
1206 <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, and uploads directories. DB restoration must be done separately at this time. Continue with the restoration process?'))" />
1207 </form>
1208 </div>
1209 </div>
1210 </div>
1211 <br style="clear:both" />
1212 <table class="form-table">
1213 <tr>
1214 <th>Download Backups</th>
1215 <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
1216 </tr>
1217 <tr>
1218 <td></td><td class="download-backups" style="display:none">
1219 <em>Click on a button to download the corresponding file to your computer. If you are using Opera, you should turn Turbo mode off.</em>
1220 <table>
1221 <?php
1222 foreach($backup_history as $key=>$value) {
1223 ?>
1224 <tr>
1225 <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
1226 <td>
1227 <?php if (isset($value['db'])) { ?>
1228 <form action="admin-ajax.php" method="post">
1229 <input type="hidden" name="action" value="updraft_download_backup" />
1230 <input type="hidden" name="type" value="db" />
1231 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1232 <input type="submit" value="Database" />
1233 </form>
1234 <?php } else { echo "(No database in backup)"; } ?>
1235 </td>
1236 <td>
1237 <?php if (isset($value['plugins'])) { ?>
1238 <form action="admin-ajax.php" method="post">
1239 <input type="hidden" name="action" value="updraft_download_backup" />
1240 <input type="hidden" name="type" value="plugins" />
1241 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1242 <input type="submit" value="Plugins" />
1243 </form>
1244 <?php } else { echo "(No plugins in backup)"; } ?>
1245 </td>
1246 <td>
1247 <?php if (isset($value['themes'])) { ?>
1248 <form action="admin-ajax.php" method="post">
1249 <input type="hidden" name="action" value="updraft_download_backup" />
1250 <input type="hidden" name="type" value="themes" />
1251 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1252 <input type="submit" value="Themes" />
1253 </form>
1254 <?php } else { echo "(No themes in backup)"; } ?>
1255 </td>
1256 <td>
1257 <?php if (isset($value['uploads'])) { ?>
1258 <form action="admin-ajax.php" method="post">
1259 <input type="hidden" name="action" value="updraft_download_backup" />
1260 <input type="hidden" name="type" value="uploads" />
1261 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
1262 <input type="submit" value="Uploads" />
1263 </form>
1264 <?php } else { echo "(No uploads in backup)"; } ?>
1265 </td>
1266 </tr>
1267 <?php }?>
1268 </table>
1269 </td>
1270 </tr>
1271 </table>
1272 <form method="post" action="options.php">
1273 <?php settings_fields('updraft-options-group'); ?>
1274 <table class="form-table">
1275 <tr>
1276 <th>Backup Directory:</th>
1277 <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
1278 </tr>
1279 <tr>
1280 <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>
1281 </tr>
1282 <tr>
1283 <th>Backup Intervals:</th>
1284 <td><select name="updraft_interval">
1285 <?php
1286 $intervals = array ("manual", "daily", "weekly", "monthly");
1287 foreach ($intervals as $ival) {
1288 echo "<option value=\"$ival\" ";
1289 if ($ival == get_option('updraft_interval')) { echo 'selected="selected"';}
1290 echo ">".ucfirst($ival)."</option>\n";
1291 }
1292 ?>
1293 </select></td>
1294 </tr>
1295 <tr class="backup-interval-description">
1296 <td></td><td>If you would like to automatically schedule backups, choose a schedule from the dropdown above. Backups will occur at the interval specified starting five minutes after the current time. If you choose manual you must click the &quot;Backup Now!&quot; button to cause a backup to occur.</td>
1297 </tr>
1298 <?php
1299 # The true (default value if non-existent) here has the effect of forcing a default of on.
1300 $include_themes = (get_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
1301 $include_plugins = (get_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
1302 $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
1303 ?>
1304 <tr>
1305 <th>Include in Backup:</th>
1306 <td>
1307 <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br />
1308 <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br />
1309 <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br />
1310 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. The database is always included.<br />(<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br /></td>
1311 </td>
1312 </tr>
1313 <tr>
1314 <th>Retain Backups:</th>
1315 <?php
1316 $updraft_retain = get_option('updraft_retain');
1317 $retain = ((int)$updraft_retain > 0)?get_option('updraft_retain'):1;
1318 ?>
1319 <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
1320 </tr>
1321 <tr class="backup-retain-description">
1322 <td></td><td>By default only the most recent backup is retained. If you'd like to preserve more, specify the number here.</td>
1323 </tr>
1324 <tr>
1325 <th>Encryption phrase:</th>
1326 <?php
1327 $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
1328 ?>
1329 <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
1330 </tr>
1331 <tr class="backup-crypt-description">
1332 <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>
1333 </tr>
1334
1335 <tr>
1336 <th>Remote backup:</th>
1337 <td><select name="updraft_service" id="updraft-service">
1338 <?php
1339 $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
1340 $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
1341
1342 $display_none = 'style="display:none"';
1343 $s3 = ""; $ftp = ""; $email = "";
1344 $email_display="";
1345 $display_email_complete = "";
1346 $set = 'selected="selected"';
1347 switch(get_option('updraft_service')) {
1348 case 's3':
1349 $s3 = $set;
1350 $ftp_display = $display_none;
1351 break;
1352 case 'ftp':
1353 $ftp = $set;
1354 $s3_display = $display_none;
1355 break;
1356 case 'email':
1357 $email = $set;
1358 $ftp_display = $display_none;
1359 $s3_display = $display_none;
1360 $display_email_complete = $display_none;
1361 break;
1362 default:
1363 $none = $set;
1364 $ftp_display = $display_none;
1365 $s3_display = $display_none;
1366 $display_delete_local = $display_none;
1367 break;
1368 }
1369 ?>
1370 <option value="none" <?php echo $none?>>None</option>
1371 <option value="s3" <?php echo $s3?>>Amazon S3</option>
1372 <option value="ftp" <?php echo $ftp?>>FTP</option>
1373 <option value="email" <?php echo $email?>>E-mail</option>
1374 </select></td>
1375 </tr>
1376 <tr class="backup-service-description">
1377 <td></td><td>Choose which backup method you would like to employ. Be aware that email servers tend to have strict file size limitations and it is possible you will not receive your backup emails (>10MB is a typical threshold). Select none if you do not wish to send your backups anywhere. <b>Not recommended.</b></td>
1378
1379 </tr>
1380 <tr class="s3" <?php echo $s3_display?>>
1381 <th>S3 access key:</th>
1382 <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
1383 </tr>
1384 <tr class="s3" <?php echo $s3_display?>>
1385 <th>S3 secret key:</th>
1386 <td><input type="password" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
1387 </tr>
1388 <tr class="s3" <?php echo $s3_display?>>
1389 <th>S3 bucket:</th>
1390 <td><input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
1391 </tr>
1392 <tr class="s3" <?php echo $s3_display?>>
1393 <th></th>
1394 <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) to use for storage. (Do not enter the s3:// prefix).</p></td>
1395 </tr>
1396 <tr class="ftp" <?php echo $ftp_display?>>
1397 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
1398 <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
1399 </tr>
1400 <tr class="ftp" <?php echo $ftp_display?>>
1401 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
1402 <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
1403 </tr>
1404 <tr class="ftp" <?php echo $ftp_display?>>
1405 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
1406 <td><input type="password" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
1407 </tr>
1408 <tr class="ftp" <?php echo $ftp_display?>>
1409 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
1410 <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
1411 </tr>
1412 <tr class="ftp-description" style="display:none">
1413 <td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
1414 </tr>
1415 <tr class="email" <?php echo $email_display?>>
1416 <th>Email:</th>
1417 <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>
1418 </tr>
1419 <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
1420 <th>Delete local backup:</th>
1421 <td><input type="checkbox" name="updraft_delete_local" value="1" <?php echo $delete_local; ?> /> <br />Check this to delete the local backup file after it has been sent off the server.</td>
1422 </tr>
1423 <tr>
1424 <th>Debug mode:</th>
1425 <td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br />Check this for more information, if something is going wrong. Will also drop a log file in your backup directory which you can examine.</td>
1426 </tr>
1427 <tr>
1428 <td>
1429 <input type="hidden" name="action" value="update" />
1430 <input type="submit" class="button-primary" value="Save Changes" />
1431 </td>
1432 </tr>
1433 </table>
1434 </form>
1435 <?php
1436 if(get_option('updraft_debug_mode')) {
1437 ?>
1438 <div>
1439 <h3>Debug Information</h3>
1440 <?php
1441 $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
1442 $memory_usage = memory_get_usage(true)/1024/1024;
1443 echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
1444 echo 'Current memory usage: '.$memory_usage.' MB<br/>';
1445 echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
1446 ?>
1447 <form method="post" action="">
1448 <input type="hidden" name="action" value="updraft_backup_debug_all" />
1449 <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>
1450 </form>
1451 <form method="post" action="">
1452 <input type="hidden" name="action" value="updraft_backup_debug_db" />
1453 <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>
1454 </form>
1455 </div>
1456 <?php } ?>
1457 <script type="text/javascript">
1458 jQuery(document).ready(function() {
1459 jQuery('#updraft-service').change(function() {
1460 switch(jQuery(this).val()) {
1461 case 'none':
1462 jQuery('.deletelocal,.s3,.ftp,.s3-description,.ftp-description').hide()
1463 jQuery('.email,.email-complete').show()
1464 break;
1465 case 's3':
1466 jQuery('.ftp,.ftp-description').hide()
1467 jQuery('.s3,.deletelocal,.email,.email-complete').show()
1468 break;
1469 case 'ftp':
1470 jQuery('.s3,.s3-description').hide()
1471 jQuery('.ftp,.deletelocal,.email,.email-complete').show()
1472 break;
1473 case 'email':
1474 jQuery('.s3,.ftp,.s3-description,.ftp-description,.email-complete').hide()
1475 jQuery('.email,.deletelocal').show()
1476 break;
1477 }
1478 })
1479 })
1480 jQuery(window).load(function() {
1481 //this is for hiding the restore progress at the top after it is done
1482 setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
1483 jQuery('#updraft-restore-progress-toggle').click(function() {
1484 jQuery('#updraft-restore-progress').toggle(500)
1485 })
1486 })
1487 </script>
1488 <?php
1489 }
1490
1491 /*array2json provided by bin-co.com under BSD license*/
1492 function array2json($arr) {
1493 if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
1494 $parts = array();
1495 $is_list = false;
1496
1497 //Find out if the given array is a numerical array
1498 $keys = array_keys($arr);
1499 $max_length = count($arr)-1;
1500 if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {//See if the first key is 0 and last key is length - 1
1501 $is_list = true;
1502 for($i=0; $i<count($keys); $i++) { //See if each key correspondes to its position
1503 if($i != $keys[$i]) { //A key fails at position check.
1504 $is_list = false; //It is an associative array.
1505 break;
1506 }
1507 }
1508 }
1509
1510 foreach($arr as $key=>$value) {
1511 if(is_array($value)) { //Custom handling for arrays
1512 if($is_list) $parts[] = $this->array2json($value); /* :RECURSION: */
1513 else $parts[] = '"' . $key . '":' . $this->array2json($value); /* :RECURSION: */
1514 } else {
1515 $str = '';
1516 if(!$is_list) $str = '"' . $key . '":';
1517
1518 //Custom handling for multiple data types
1519 if(is_numeric($value)) $str .= $value; //Numbers
1520 elseif($value === false) $str .= 'false'; //The booleans
1521 elseif($value === true) $str .= 'true';
1522 else $str .= '"' . addslashes($value) . '"'; //All other things
1523 // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
1524
1525 $parts[] = $str;
1526 }
1527 }
1528 $json = implode(',',$parts);
1529
1530 if($is_list) return '[' . $json . ']';//Return numerical JSON
1531 return '{' . $json . '}';//Return associative JSON
1532 }
1533
1534 function show_admin_warning($message) {
1535 echo '<div id="updraftmessage" class="updated fade">';
1536 echo "<p>$message</p></div>";
1537 }
1538 function show_admin_warning_accessible() {
1539 $this->show_admin_warning("UpdraftPlus backup directory specified is accessible via the web. This is a potential security problem (people may be able to download your backups - which is undesirable if your database is not encrypted and if you have non-public assets amongst the files). If using Apache, enable .htaccess support to allow web access to be denied; otherwise, you should deny access manually.");
1540 }
1541 function show_admin_warning_accessible_unknownresult() {
1542 $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
1543 }
1544
1545
1546 }
1547
1548 ?>
1549