PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.0.9
UpdraftPlus: WP Backup & Migration Plugin v1.0.9
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / updraftplus.php

updraftplus.php in UpdraftPlus: WP Backup & Migration Plugin 1.0.9, at updraftplus.php

2,420 lines 104.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: UpdraftPlus - Backup/Restore
4 Plugin URI: http://wordpress.org/extend/plugins/updraftplus
5 Description: 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.0.9
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 (some of these items mine, some from original Updraft awaiting review):
15 //Add DropBox and Microsoft Skydrive support
16 //Backup record should include *where* the backup was placed, so that we don't attempt to delete old ones from the wrong place? (Or would that be unexpected to users to have things from elsewhere deleted?)
17 //improve error reporting. s3 and dir backup have decent reporting now, but not sure i know what to do from here
18 //list backups that aren't tracked (helps with double backup problem)
19 //investigate $php_errormsg further
20 //pretty up return messages in admin area
21 //check s3/ftp download
22
23 //Rip out the "last backup" bit, and/or put in a display of the last log
24
25 Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
26 // Does not delete old custom directories upon a restore?
27 */
28
29 /* Portions copyright 2010 Paul Kehrer
30 Portions copyright 2011-12 David Anderson
31 Other portions copyright as indicated authors in the relevant files
32 Particular thanks to Sorin Iclanzan, author of the "Backup" plugin, from which much Google Drive code was taken under the GPLv3+
33
34 This program is free software; you can redistribute it and/or modify
35 it under the terms of the GNU General Public License as published by
36 the Free Software Foundation; either version 3 of the License, or
37 (at your option) any later version.
38
39 This program is distributed in the hope that it will be useful,
40 but WITHOUT ANY WARRANTY; without even the implied warranty of
41 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42 GNU General Public License for more details.
43
44 You should have received a copy of the GNU General Public License
45 along with this program; if not, write to the Free Software
46 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
47 */
48 // TODO: Note this might *lower* the limit - should check first.
49
50 @set_time_limit(900); //15 minutes max. i'm not sure how long a really big site could take to back up?
51
52 $updraft = new UpdraftPlus();
53
54 if(!$updraft->memory_check(192)) {
55 # TODO: Better solution is to split the backup set into manageable chunks based on this limit
56 @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
57 }
58
59 define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php');
60
61 class UpdraftPlus {
62
63 var $version = '1.0.9';
64
65 var $dbhandle;
66 var $errors = array();
67 var $nonce;
68 var $logfile_name = "";
69 var $logfile_handle = false;
70 var $backup_time;
71 var $gdocs;
72 var $gdocs_access_token;
73
74 function __construct() {
75 // Initialisation actions
76 # Create admin page
77 add_action('admin_menu', array($this,'add_admin_pages'));
78 add_action('admin_init', array($this,'admin_init'));
79 add_action('updraft_backup', array($this,'backup_files'));
80 add_action('updraft_backup_database', array($this,'backup_database'));
81 # backup_all is used by the manual "Backup Now" button
82 add_action('updraft_backup_all', array($this,'backup_all'));
83 # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
84 add_action('updraft_backup_resume', array($this,'backup_resume'));
85 add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
86 # http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules
87 add_filter('cron_schedules', array($this,'modify_cron_schedules'));
88 add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
89 add_action('init', array($this, 'googledrive_backup_auth'));
90 }
91
92 // Handle Google OAuth 2.0 - ?page=updraftplus&action=auth
93 function googledrive_backup_auth() {
94 if ( is_admin() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset( $_GET['action'] ) && $_GET['action'] == 'auth' ) {
95 if ( isset( $_GET['state'] ) ) {
96 if ( $_GET['state'] == 'token' )
97 $this->gdrive_auth_token();
98 elseif ( $_GET['state'] == 'revoke' )
99 $this->gdrive_auth_revoke();
100 } elseif (isset($_GET['updraftplus_googleauth'])) {
101 $this->gdrive_auth_request();
102 }
103 }
104 }
105
106 /**
107 * Acquire single-use authorization code from Google OAuth 2.0
108 */
109 function gdrive_auth_request() {
110 $params = array(
111 'response_type' => 'code',
112 'client_id' => get_option('updraft_googledrive_clientid'),
113 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
114 'scope' => 'https://www.googleapis.com/auth/drive.file https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/',
115 'state' => 'token',
116 'access_type' => 'offline',
117 'approval_prompt' => 'auto'
118 );
119 header('Location: https://accounts.google.com/o/oauth2/auth?'.http_build_query($params));
120 }
121
122 /**
123 * Get a Google account access token using the refresh token
124 */
125 function access_token( $token, $client_id, $client_secret ) {
126 $context = array(
127 'http' => array(
128 'method' => 'POST',
129 'header' => 'Content-type: application/x-www-form-urlencoded',
130 'content' => http_build_query( array(
131 'refresh_token' => $token,
132 'client_id' => $client_id,
133 'client_secret' => $client_secret,
134 'grant_type' => 'refresh_token'
135 ) )
136 )
137 );
138 $this->log("Google Drive: requesting access token: client_id=$client_id");
139 $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
140 if($result) {
141 $result = json_decode( $result, true );
142 if ( isset( $result['access_token'] ) ) {
143 $this->log("Google Drive: successfully obtained access token");
144 return $result['access_token'];
145 } else {
146 $this->log("Google Drive error when requesting access token: response does not contain access_token");
147 return false;
148 }
149 } else {
150 $this->log("Google Drive error when requesting access token: no response");
151 return false;
152 }
153 }
154
155 function googledrive_delete_file( $file, $token) {
156 $ids = get_option('updraft_file_ids', array());
157 if (!isset($ids[$file])) {
158 $this->log("Could not delete: could not find a record of the Google Drive file ID for this file");
159 return;
160 } else {
161 $del == $this->gdocs->delete_resource($ids[$file]);
162 if (is_wp_error($del)) {
163 foreach ($del->get_error_messages() as $msg) {
164 $this->log("Deletion failed: $msg");
165 }
166 } else {
167 $this->log("Deletion successful");
168 unset($ids[$file]);
169 update_option('updraft_file_ids', $ids);
170 }
171 }
172 return;
173 }
174
175 /**
176 * Get a Google account refresh token using the code received from gdrive_auth_request
177 */
178 function gdrive_auth_token() {
179 if( isset( $_GET['code'] ) ) {
180 $context = array(
181 'http' => array(
182 'timeout' => 30,
183 'method' => 'POST',
184 'header' => 'Content-type: application/x-www-form-urlencoded',
185 'content' => http_build_query( array(
186 'code' => $_GET['code'],
187 'client_id' => get_option('updraft_googledrive_clientid'),
188 'client_secret' => get_option('updraft_googledrive_secret'),
189 'redirect_uri' => admin_url('options-general.php?page=updraftplus&action=auth'),
190 'grant_type' => 'authorization_code'
191 ) )
192 )
193 );
194 // TODO Use curl??
195 $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));
196 # Oddly, sometimes fails and then trying again works...
197 /*
198 if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
199 if (!$result) { sleep(1); $result = @file_get_contents('https://accounts.google.com/o/oauth2/token', false, stream_context_create($context));}
200 */
201 if($result) {
202 $result = json_decode( $result, true );
203 if ( isset( $result['refresh_token'] ) ) {
204 update_option('updraft_googledrive_token',$result['refresh_token']); // Save token
205 header('Location: '.admin_url('options-general.php?page=updraftplus&message=' . __( 'Google Drive authorization was successful.', 'updraftplus' ) ) );
206 }
207 else {
208 header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'No refresh token was received!', 'updraftplus' ) ) );
209 }
210 } else {
211 header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Bad response!', 'backup' ) ) );
212 }
213 }
214 else {
215 header('Location: '.admin_url('options-general.php?page=updraftplus&error=' . __( 'Authorisation failed!', 'backup' ) ) );
216 }
217 }
218
219 /**
220 * Revoke a Google account refresh token
221 */
222 function gdrive_auth_revoke() {
223 @file_get_contents( 'https://accounts.google.com/o/oauth2/revoke?token=' . get_option('updraft_googledrive_token') );
224 update_option('updraft_googledrive_token','');
225 header( 'Location: '.admin_url( 'options-general.php?page=updraftplus&message=' . __( 'Authorization revoked.', 'backup' ) ) );
226 }
227
228 # Adds the settings link under the plugin on the plugin screen.
229 function plugin_action_links($links, $file) {
230 if ($file == plugin_basename(__FILE__)){
231 $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
232 array_unshift($links, $settings_link);
233 $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
234 array_unshift($links, $settings_link);
235 }
236 return $links;
237 }
238
239 function backup_time_nonce() {
240 $this->backup_time = time();
241 $this->nonce = substr(md5(time().rand()),20);
242 }
243
244 # Logs the given line, adding date stamp and newline
245 function log($line) {
246 if ($this->logfile_handle) fwrite($this->logfile_handle,date('r')." ".$line."\n");
247 }
248
249 function backup_resume($resumption_no) {
250 @ignore_user_abort(true);
251 // This is scheduled for 5 minutes after a backup job starts
252 $bnonce = get_transient('updraftplus_backup_job_nonce');
253 if (!$bnonce) return;
254 $this->nonce = $bnonce;
255 $this->logfile_open($bnonce);
256 $this->log("Resume backup ($resumption_no): begin run (will check for any remaining jobs)");
257 $btime = get_transient('updraftplus_backup_job_time');
258 if (!$btime) {
259 $this->log("Did not find stored time setting - aborting");
260 return;
261 }
262 $this->log("Resuming backup: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
263 // Schedule again, to run in 5 minutes again, in case we again fail
264 $resume_delay = 300;
265 // A different argument than before is needed otherwise the event is ignored
266 $next_resumption = $resumption_no+1;
267 if ($next_resumption < 10) {
268 wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume' ,array($next_resumption));
269 } else {
270 $this->log("This is our tenth attempt - will not try again");
271 }
272 $this->backup_time = $btime;
273
274 // Returns an array, most recent first, of backup sets
275 $backup_history = $this->get_backup_history();
276 if (!isset($backup_history[$btime])) $this->log("Error: Could not find a record in the database of a backup with this timestamp");
277
278 $our_files=$backup_history[$btime];
279 $undone_files = array();
280 foreach ($our_files as $key => $file) {
281 $hash = md5($file);
282 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
283 if (get_transient('updraft_'.$hash) === "yes") {
284 $this->log("$file: $key: This file has been successfully uploaded in the last 3 hours");
285 } elseif (is_file($fullpath)) {
286 $this->log("$file: $key: This file has NOT been successfully uploaded in the last 3 hours: will retry");
287 $undone_files[$key] = $file;
288 } else {
289 $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
290 $this->uploaded_file($file);
291 }
292 }
293
294 if (count($undone_files) == 0) {
295 $this->log("There were no files that needed uploading; backup job is finished");
296 return;
297 }
298
299 $this->log("Requesting backup of the files that were not successfully uploaded");
300 $this->cloud_backup($undone_files);
301 $this->cloud_backup_finish($undone_files);
302
303 $this->log("Resume backup ($resumption_no): finish run");
304
305 $this->backup_finish($next_resumption);
306
307 }
308
309 function backup_all() {
310 $this->backup(true,true);
311 }
312
313 function backup_files() {
314 # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
315 $this->backup(true,false);
316 }
317
318 function backup_database() {
319 # Note that nothing will happen if the file backup had the same schedule
320 $this->backup(false,true);
321 }
322
323 function logfile_open($nonce) {
324 //set log file name and open log file
325 $updraft_dir = $this->backups_dir_location();
326 $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
327 // Use append mode in case it already exists
328 $this->logfile_handle = fopen($this->logfile_name, 'a');
329 }
330
331 //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. TODO: I reckon there is. Store a transient based on the backup schedule. Then as the backup proceeds, check for its existence; if it has changed, then another task has begun, so abort.
332 function backup($backup_files, $backup_database) {
333
334 @ignore_user_abort(true);
335 //generate backup information
336 $this->backup_time_nonce();
337 // If we don't finish in 3 hours, then we won't finish
338 // This transient indicates the identity of the current backup job (which can be used to find the files and logfile)
339 set_transient("updraftplus_backup_job_nonce",$this->nonce,3600*3);
340 set_transient("updraftplus_backup_job_time",$this->backup_time,3600*3);
341 $this->logfile_open($this->nonce);
342
343 // Schedule the even to run later, which checks on success and can resume the backup
344 // We save the time to a variable because it is needed for un-scheduling
345 // $resume_delay = (get_option('updraft_debug_mode')) ? 60 : 300;
346 $resume_delay = 300;
347 wp_schedule_single_event(time()+$resume_delay, 'updraft_backup_resume', array(1));
348 $this->log("In case we run out of time, scheduled a resumption at: $resume_delay seconds from now");
349
350 // Log some information that may be helpful
351 global $wp_version;
352 $this->log("PHP version: ".phpversion()." WordPress version: ".$wp_version." Updraft version: ".$this->version." Backup files: $backup_files (schedule: ".get_option('updraft_interval','unset').") Backup DB: $backup_database (schedule: ".get_option('updraft_interval_database','unset').")");
353
354 # If the files and database schedules are the same, and if this the file one, then we rope in database too.
355 # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
356 if (get_option('updraft_interval') == get_option('updraft_interval_database') || get_option('updraft_interval_database','xyz') == 'xyz' ) {
357 $backup_database = ($backup_files == true) ? true : false;
358 }
359
360 $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
361
362 # Possibly now nothing is to be done, except to close the log file
363 if ($backup_files || $backup_database) {
364
365 $backup_contains = "";
366
367 $backup_array = array();
368
369 //backup directories and return a numerically indexed array of file paths to the backup files
370 if ($backup_files) {
371 $this->log("Beginning backup of directories");
372 $backup_array = $this->backup_dirs();
373 $backup_contains = "Files only (no database)";
374 }
375
376 //backup DB and return string of file path
377 if ($backup_database) {
378 $this->log("Beginning backup of database");
379 $db_backup = $this->backup_db();
380 //add db path to rest of files
381 if(is_array($backup_array)) { $backup_array['db'] = $db_backup; }
382 $backup_contains = ($backup_files) ? "Files and database" : "Database only (no files)";
383 }
384
385 set_transient("updraftplus_backupcontains", $backup_contains, 3600*3);
386
387 //save this to our history so we can track backups for the retain feature
388 $this->log("Saving backup history");
389 // 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.
390 $this->save_backup_history($backup_array);
391
392 //cloud operations (S3,Google Drive,FTP,email,nothing)
393 //this also calls the retain (prune) feature at the end (done in this method to reuse existing cloud connections)
394 if(is_array($backup_array) && count($backup_array) >0) {
395 $this->log("Beginning dispatch of backup to remote");
396 $this->cloud_backup($backup_array);
397 }
398
399 //save the last backup info, including errors, if any
400 $this->log("Saving last backup information into WordPress db");
401 $this->save_last_backup($backup_array);
402
403 // Send the email
404 $this->cloud_backup_finish($backup_array);
405
406 }
407
408 // Close log file; delete and also delete transients if not in debug mode
409 $this->backup_finish(1);
410
411 }
412
413 function backup_finish($cancel_event) {
414
415 // 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.
416 if (empty($this->errors)) {
417 $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
418 wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event));
419 delete_transient("updraftplus_backup_job_nonce");
420 delete_transient("updraftplus_backup_job_time");
421 } else {
422 $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
423 }
424
425 @fclose($this->logfile_handle);
426
427 if (!get_option('updraft_debug_mode')) @unlink($this->logfile_name);
428
429 }
430
431 function cloud_backup_finish($backup_array) {
432
433 // Send the results email if requested
434 if(get_option('updraft_email') != "" && get_option('updraft_service') != 'email') $this->send_results_email();
435
436 }
437
438 function send_results_email() {
439
440 $sendmail_to = get_option('updraft_email');
441
442 $this->log("Sending email report to: ".$sendmail_to);
443
444 $append_log = (get_option('updraft_debug_mode') && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
445
446 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);
447
448 }
449
450 function save_last_backup($backup_array) {
451 $success = (empty($this->errors))?1:0;
452
453 $last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors);
454
455 update_option('updraft_last_backup', $last_backup);
456 }
457
458 // This should be called whenever a file is successfully uploaded
459 function uploaded_file($file, $id = false) {
460 # We take an MD5 hash because set_transient wants a name of 45 characters or less
461 $hash = md5($file);
462 $this->log("$file: $hash: recording as successfully uploaded");
463 set_transient("updraft_".$hash, "yes", 3600*4);
464 if ($id) {
465 $ids = get_option('updraft_file_ids', array() );
466 $ids[$file] = $id;
467 update_option('updraft_file_ids',$ids);
468 $this->log("Stored file<->id correlation in database ($file <-> $id)");
469 }
470 // Delete local files if the option is set
471 $this->delete_local($file);
472
473 }
474
475 function cloud_backup($backup_array) {
476 switch(get_option('updraft_service')) {
477 case 's3':
478 @set_time_limit(900);
479 $this->log("Cloud backup: S3");
480 if (count($backup_array) >0) $this->s3_backup($backup_array);
481 break;
482 case 'googledrive':
483 @set_time_limit(900);
484 $this->log("Cloud backup: Google Drive");
485 if (count($backup_array) >0) $this->googledrive_backup($backup_array);
486 break;
487 case 'ftp':
488 @set_time_limit(900);
489 $this->log("Cloud backup: FTP");
490 if (count($backup_array) >0) $this->ftp_backup($backup_array);
491 break;
492 case 'email':
493 @set_time_limit(900);
494 $this->log("Cloud backup: Email");
495 //files can easily get way too big for this...
496 foreach($backup_array as $type=>$file) {
497 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
498 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));
499 $this->uploaded_file($file);
500 }
501 //we don't break here so it goes and executes all the default behavior below as well. this gives us retain behavior for email
502 default:
503 $this->prune_retained_backups("local");
504 break;
505 }
506 }
507
508 // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
509 function prune_retained_backups($updraft_service,$remote_object,$remote_path) {
510 $this->log("Retain: beginning examination of existing backup sets");
511 $updraft_retain = get_option('updraft_retain');
512 // Number of backups to retain
513 $retain = (isset($updraft_retain))?get_option('updraft_retain'):1;
514 $this->log("Retain: user setting: number to retain = $retain");
515 // Returns an array, most recent first, of backup sets
516 $backup_history = $this->get_backup_history();
517 $db_backups_found = 0;
518 $file_backups_found = 0;
519 $this->log("Number of backup sets in history: ".count($backup_history));
520 foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
521 // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
522 // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
523 $this->log("Examining backup set with datestamp: $backup_datestamp");
524 if (isset($backup_to_examine['db'])) {
525 $db_backups_found++;
526 $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
527 if ($db_backups_found > $retain) {
528 $this->log("$backup_datestamp: over retain limit; will delete this database");
529 $file = $backup_to_examine['db'];
530 $this->log("$backup_datestamp: Delete this file: $file");
531 if ($file != '') {
532 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
533 @unlink($fullpath); //delete it if it's locally available
534 if ($updraft_service == "s3") {
535 if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
536 $s3_bucket=$bmatches[1];
537 $s3_uri = $bmatches[2]."/".$file;
538 } else {
539 $s3_bucket = $remote_path;
540 $s3_uri = $file;
541 }
542 $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
543 # Here we brought in the function deleteObject in order to get more direct access to any error
544 $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
545 $rest = $rest->getResponse();
546 if ($rest->error === false && $rest->code !== 204) {
547 $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
548 $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
549 } elseif ($rest->error !== false) {
550 $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
551 $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
552 }
553 } elseif ($updraft_service == "ftp") {
554 $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$file");
555 @$remote_object->delete($remote_path.$file);
556 } elseif ($updraft_service == "googledrive") {
557 $this->log("$backup_datestamp: Delete remote file from Google Drive: $file");
558 $this->googledrive_delete_file($file,$remote_object);
559 }
560 }
561 unset($backup_to_examine['db']);
562 }
563 }
564 if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads']) || isset($backup_to_examine['others'])) {
565 $file_backups_found++;
566 $this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
567 if ($file_backups_found > $retain) {
568 $this->log("$backup_datestamp: over retain limit; will delete this file set");
569 $file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
570 $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
571 $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
572 $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
573 foreach (array($file,$file2,$file3,$file4) as $dofile) {
574 if ($dofile) {
575 $this->log("$backup_datestamp: Delete this file: $dofile");
576 $fullpath = trailingslashit(get_option('updraft_dir')).$dofile;
577 @unlink($fullpath); //delete it if it's locally available
578 if ($updraft_service == "s3") {
579 if (preg_match("#^([^/]+)/(.*)$#",$remote_path,$bmatches)) {
580 $s3_bucket=$bmatches[1];
581 $s3_uri = $bmatches[2]."/".$dofile;
582 } else {
583 $s3_bucket = $remote_path;
584 $s3_uri = $dofile;
585 }
586 $this->log("$backup_datestamp: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
587 # Here we brought in the function deleteObject in order to get more direct access to any error
588 $rest = new S3Request('DELETE', $s3_bucket, $s3_uri);
589 $rest = $rest->getResponse();
590 if ($rest->error === false && $rest->code !== 204) {
591 $this->log("S3 Error: Expected HTTP response 204; got: ".$rest->code);
592 $this->error("S3 Error: Unexpected HTTP response code ".$rest->code." (expected 204)");
593 } elseif ($rest->error !== false) {
594 $this->log("S3 Error: ".$rest->error['code'].": ".$rest->error['message']);
595 $this->error("S3 delete error: ".$rest->error['code'].": ".$rest->error['message']);
596 }
597 } elseif ($updraft_service == "ftp") {
598 $this->log("$backup_datestamp: Delete remote ftp: $remote_path/$dofile");
599 @$remote_object->delete($remote_path.$dofile);
600 } elseif ($updraft_service == "googledrive") {
601 $this->log("$backup_datestamp: Delete remote file from Google Drive: $dofile");
602 $this->googledrive_delete_file($dofile,$remote_object);
603 }
604 }
605 }
606 unset($backup_to_examine['plugins']);
607 unset($backup_to_examine['themes']);
608 unset($backup_to_examine['uploads']);
609 unset($backup_to_examine['others']);
610 }
611 }
612 // Delete backup set completely if empty, o/w just remove DB
613 if (count($backup_to_examine)==0) {
614 $this->log("$backup_datestamp: this backup set is now empty; will remove from history");
615 unset($backup_history[$backup_datestamp]);
616 } else {
617 $this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
618 $backup_history[$backup_datestamp] = $backup_to_examine;
619 }
620 }
621 $this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
622 update_option('updraft_backup_history',$backup_history);
623 }
624
625 function s3_backup($backup_array) {
626
627 if(!class_exists('S3')) require_once(dirname(__FILE__).'/includes/S3.php');
628
629 $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
630
631 $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
632 $bucket_path = "";
633 $orig_bucket_name = $bucket_name;
634
635 if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
636 $bucket_name = $bmatches[1];
637 $bucket_path = $bmatches[2]."/";
638 }
639
640 // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
641 if (@$s3->getBucketLocation($bucket_name) || @$s3->putBucket($bucket_name, S3::ACL_PRIVATE)) {
642
643 foreach($backup_array as $file) {
644
645 // We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
646 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
647 $chunks = floor(filesize($fullpath) / 5242880)+1;
648 $hash = md5($file);
649
650 $this->log("S3 upload: $fullpath (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
651
652 $filepath = $bucket_path.$file;
653
654 // This is extra code for the 1-chunk case, but less overhead (no bothering with transients)
655 if ($chunks < 2) {
656 if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath)) {
657 $this->log("S3 regular upload: failed");
658 $this->error("S3 Error: Failed to upload $fullpath. Error was ".$php_errormsg);
659 } else {
660 $this->log("S3 regular upload: success");
661 $this->uploaded_file($file);
662 }
663 } else {
664
665 // Retrieve the upload ID
666 $uploadId = get_transient("updraft_${hash}_uid");
667 if (empty($uploadId)) {
668 $uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath);
669 if (empty($uploadId)) {
670 $this->log("S3 upload: failed: could not get uploadId for multipart upload");
671 continue;
672 } else {
673 $this->log("S3 chunked upload: got multipart ID: $uploadId");
674 set_transient("updraft_${hash}_uid", $uploadId, 3600*3);
675 }
676 } else {
677 $this->log("S3 chunked upload: retrieved previously obtained multipart ID: $uploadId");
678 }
679
680 $successes = 0;
681 $etags = array();
682 for ($i = 1 ; $i <= $chunks; $i++) {
683 # Shorted to upd here to avoid hitting the 45-character limit
684 $etag = get_transient("upd_${hash}_e$i");
685 if (strlen($etag) > 0) {
686 $this->log("S3 chunk $i: was already completed (etag: $etag)");
687 $successes++;
688 array_push($etags, $etag);
689 } else {
690 $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
691 if (is_string($etag)) {
692 $this->log("S3 chunk $i: uploaded (etag: $etag)");
693 array_push($etags, $etag);
694 set_transient("upd_${hash}_e$i", $etag, 3600*3);
695 $successes++;
696 } else {
697 $this->error("S3 chunk $i: upload failed");
698 $this->log("S3 chunk $i: upload failed");
699 }
700 }
701 }
702 if ($successes >= $chunks) {
703 $this->log("S3 upload: all chunks uploaded; will now instruct S3 to re-assemble");
704 if ($s3->completeMultipartUpload ($bucket_name, $filepath, $uploadId, $etags)) {
705 $this->log("S3 upload: re-assembly succeeded");
706 $this->uploaded_file($file);
707 } else {
708 $this->log("S3 upload: re-assembly failed");
709 $this->error("S3 upload: re-assembly failed");
710 }
711 } else {
712 $this->log("S3 upload: upload was not completely successful on this run");
713 }
714 }
715 }
716 $this->prune_retained_backups('s3',$s3,$orig_bucket_name);
717 } else {
718 $this->log("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
719 $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
720 }
721 }
722
723 // This function taken from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
724 function is_gdocs( $thing ) {
725 if ( is_object( $thing ) && is_a( $thing, 'UpdraftPlus_GDocs' ) )
726 return true;
727 return false;
728 }
729
730 // This function modified from wordpress.org/extend/plugins/backup, by Sorin Iclanzan, under the GPLv3 or later at your choice
731 function need_gdocs() {
732
733 if ( ! $this->is_gdocs( $this->gdocs ) ) {
734 if ( get_option('updraft_googledrive_token') == "" || get_option('updraft_googledrive_clientid') == "" || get_option('updraft_googledrive_secret') == "" ) {
735 $this->log("GoogleDrive: this account is not authorised");
736 return new WP_Error( "not_authorized", "Account is not authorized." );
737 }
738
739 if ( is_wp_error( $this->gdocs_access_token ) ) return $access_token;
740
741 $this->gdocs = new UpdraftPlus_GDocs( $this->gdocs_access_token );
742 $this->gdocs->set_option( 'chunk_size', 1 ); # 1Mb; change from default of 512Kb
743 $this->gdocs->set_option( 'request_timeout', 10 ); # Change from default of 10s
744 $this->gdocs->set_option( 'max_resume_attempts', 36 ); # Doesn't look like GDocs class actually uses this anyway
745 }
746 return true;
747 }
748
749 // Returns:
750 // true = already uploaded
751 // false = failure
752 // otherwise, the file ID
753 function googledrive_upload_file( $file, $title, $parent = '') {
754
755 // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
756 if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
757
758 $hash = md5($file);
759 $transkey = 'upd_'.$hash.'_gloc';
760 // This is unset upon completion, so if it is set then we are resuming
761 $possible_location = get_transient($transkey);
762
763 if ( empty( $possible_location ) ) {
764 $this->log("$file: Attempting to upload file to Google Drive.");
765 $location = $this->gdocs->prepare_upload( $file, $title, $parent );
766 } else {
767 $this->log("$file: Attempting to resume upload.");
768 $location = $this->gdocs->resume_upload( $file, $possible_location );
769 }
770
771 if ( is_wp_error( $location ) ) {
772 $this->log("GoogleDrive upload: an error occurred");
773 foreach ($location->get_error_messages() as $msg) {
774 $this->error($msg);
775 $this->log("Error details: ".$msg);
776 }
777 return false;
778 }
779
780 if (!is_string($location) && true == $location) {
781 $this->log("$file: this file is already uploaded");
782 return true;
783 }
784
785 if ( is_string( $location ) ) {
786 $res = $location;
787 $this->log("Uploading file with title ".$title);
788 $d = 0;
789 do {
790 $this->log("Google Drive upload: chunk d: $d, loc: $res");
791 $res = $this->gdocs->upload_chunk();
792 if (is_string($res)) set_transient($transkey, $res, 3600*3);
793 $p = $this->gdocs->get_upload_percentage();
794 if ( $p - $d >= 1 ) {
795 $b = intval( $p - $d );
796 // echo '<span style="width:' . $b . '%"></span>';
797 $d += $b;
798 }
799 // $this->options['backup_list'][$id]['speed'] = $this->gdocs->get_upload_speed();
800 } while ( is_string( $res ) );
801 // echo '</div>';
802
803 if ( is_wp_error( $res ) || $res !== true) {
804 $this->log( "An error occurred during GoogleDrive upload (2)" );
805 $this->error( "An error occurred during GoogleDrive upload (2)" );
806 if (is_wp_error( $res )) {
807 foreach ($res->get_error_messages() as $msg) { $this->log($msg); }
808 }
809 return false;
810 }
811
812 $this->log("The file was successfully uploaded to Google Drive in ".number_format_i18n( $this->gdocs->time_taken(), 3)." seconds at an upload speed of ".size_format( $this->gdocs->get_upload_speed() )."/s.");
813
814 delete_transient($transkey);
815 // unset( $this->options['backup_list'][$id]['location'], $this->options['backup_list'][$id]['attempt'] );
816 }
817
818 return $this->gdocs->get_file_id();
819
820 // $this->update_quota();
821 // Google's "user info" service
822 // if ( empty( $this->options['user_info'] ) ) $this->set_user_info();
823
824 }
825
826 // This function just does the formalities, and off-loads the main work to googledrive_upload_file
827 function googledrive_backup($backup_array) {
828
829 require_once(dirname(__FILE__).'/includes/class-gdocs.php');
830
831 // Do we have an access token?
832 if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
833 $this->log('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
834 return new WP_Error( "no_access_token", "Have not yet obtained an access token from Google (has the user authorised?");
835 }
836
837 $this->gdocs_access_token = $access_token;
838
839 foreach ($backup_array as $file) {
840 $file_path = trailingslashit(get_option('updraft_dir')).$file;
841 $file_name = basename($file_path);
842 $this->log("$file_name: Attempting to upload to Google Drive");
843 $timer_start = microtime(true);
844 if ( $id = $this->googledrive_upload_file( $file_path, $file_name, get_option('updraft_googledrive_remotepath')) ) {
845 $this->log('OK: Archive ' . $file_name . ' uploaded to Google Drive in ' . ( round(microtime( true ) - $timer_start,2) ) . ' seconds (id: '.$id.')' );
846 $this->uploaded_file($file, $id);
847 } else {
848 $this->error("$file_name: Failed to upload to Google Drive" );
849 $this->log("ERROR: $file_name: Failed to upload to Google Drive" );
850 }
851 }
852 $this->prune_retained_backups("googledrive",$access_token,get_option('updraft_googledrive_remotepath'));
853 }
854
855 function ftp_backup($backup_array) {
856 if( !class_exists('ftp_wrapper')) {
857 require_once(dirname(__FILE__).'/includes/ftp.class.php');
858 }
859 //handle SSL and errors at some point TODO
860 $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
861 $ftp->passive = true;
862 $ftp->connect();
863 //$ftp->make_dir(); we may need to recursively create dirs? TODO
864
865 $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
866 foreach($backup_array as $file) {
867 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
868 if ($ftp->put($fullpath,$ftp_remote_path.$file,FTP_BINARY)) {
869 $this->log("ERROR: $file_name: Successfully uploaded via FTP");
870 $this->uploaded_file($file);
871 } else {
872 $this->error("$file_name: Failed to upload to FTP" );
873 $this->log("ERROR: $file_name: Failed to upload to FTP" );
874 }
875 }
876 $this->prune_retained_backups("ftp",$ftp,$ftp_remote_path);
877 }
878
879 function delete_local($file) {
880 if(get_option('updraft_delete_local')) {
881 $this->log("Deleting local file: $file");
882 //need error checking so we don't delete what isn't successfully uploaded?
883 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
884 return unlink($fullpath);
885 }
886 return true;
887 }
888
889 function backup_dirs() {
890 if(!$this->backup_time) $this->backup_time_nonce();
891 $wp_themes_dir = WP_CONTENT_DIR.'/themes';
892 $wp_upload_dir = wp_upload_dir();
893 $wp_upload_dir = $wp_upload_dir['basedir'];
894 $wp_plugins_dir = WP_PLUGIN_DIR;
895
896 if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
897
898 $updraft_dir = $this->backups_dir_location();
899 if(!is_writable($updraft_dir)) $this->error('Backup directory is not writable, or does not exist.','fatal');
900
901 //get the blog name and rip out all non-alphanumeric chars other than _
902 $blog_name = str_replace(' ','_',get_bloginfo());
903 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
904 if(!$blog_name) $blog_name = 'non_alpha_name';
905
906 $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
907
908 $backup_array = array();
909
910 # Plugins
911 @set_time_limit(900);
912 if (get_option('updraft_include_plugins', true)) {
913 $this->log("Beginning backup of plugins");
914 $full_path = $backup_file_base.'-plugins.zip';
915 $plugins = new PclZip($full_path);
916 # The paths in the zip should then begin with 'plugins', having removed WP_CONTENT_DIR from the front
917 if (!$plugins->create($wp_plugins_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
918 $this->error('Could not create plugins zip. Error was '.$php_errmsg,'fatal');
919 $this->log('ERROR: PclZip failure: Could not create plugins zip');
920 } else {
921 $this->log("Created plugins zip - file size is ".filesize($full_path)." bytes");
922 }
923 $backup_array['plugins'] = basename($full_path);
924 } else {
925 $this->log("No backup of plugins: excluded by user's options");
926 }
927
928 # Themes
929 @set_time_limit(900);
930 if (get_option('updraft_include_themes', true)) {
931 $this->log("Beginning backup of themes");
932 $full_path = $backup_file_base.'-themes.zip';
933 $themes = new PclZip($full_path);
934 if (!$themes->create($wp_themes_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
935 $this->error('Could not create themes zip. Error was '.$php_errmsg,'fatal');
936 $this->log('ERROR: PclZip failure: Could not create themes zip');
937 } else {
938 $this->log("Created themes zip - file size is ".filesize($full_path)." bytes");
939 }
940 $backup_array['themes'] = basename($full_path);
941 } else {
942 $this->log("No backup of themes: excluded by user's options");
943 }
944
945 # Uploads
946 @set_time_limit(900);
947 if (get_option('updraft_include_uploads', true)) {
948 $this->log("Beginning backup of uploads");
949 $full_path = $backup_file_base.'-uploads.zip';
950 $uploads = new PclZip($full_path);
951 if (!$uploads->create($wp_upload_dir,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
952 $this->error('Could not create uploads zip. Error was '.$php_errmsg,'fatal');
953 $this->log('ERROR: PclZip failure: Could not create uploads zip');
954 } else {
955 $this->log("Created uploads zip - file size is ".filesize($full_path)." bytes");
956 }
957 $backup_array['uploads'] = basename($full_path);
958 } else {
959 $this->log("No backup of uploads: excluded by user's options");
960 }
961
962 # Others
963 @set_time_limit(900);
964 if (get_option('updraft_include_others', true)) {
965 $this->log("Beginning backup of other directories found in the content directory");
966 $full_path=$backup_file_base.'-others.zip';
967 $others = new PclZip($full_path);
968 // http://www.phpconcept.net/pclzip/user-guide/53
969 /* First parameter to create is:
970 An array of filenames or dirnames,
971 or
972 A string containing the filename or a dirname,
973 or
974 A string containing a list of filename or dirname separated by a comma.
975 */
976 // First, see what we can find. We always want to exclude these:
977 $wp_themes_dir = WP_CONTENT_DIR.'/themes';
978 $wp_upload_dir = wp_upload_dir();
979 $wp_upload_dir = $wp_upload_dir['basedir'];
980 $wp_plugins_dir = WP_PLUGIN_DIR;
981 $updraft_dir = untrailingslashit(get_option('updraft_dir'));
982
983 # Initialise
984 $other_dirlist = array();
985
986 $others_skip = preg_split("/,/",get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
987 # Make the values into the keys
988 $others_skip = array_flip($others_skip);
989
990 $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
991 if ($handle = opendir(WP_CONTENT_DIR)) {
992 while (false !== ($entry = readdir($handle))) {
993 $candidate = WP_CONTENT_DIR.'/'.$entry;
994 if ($entry == "." || $entry == "..") { ; }
995 elseif ($candidate == $updraft_dir) { $this->log("$entry: skipping: this is the updraft directory"); }
996 elseif ($candidate == $wp_themes_dir) { $this->log("$entry: skipping: this is the themes directory"); }
997 elseif ($candidate == $wp_upload_dir) { $this->log("$entry: skipping: this is the uploads directory"); }
998 elseif ($candidate == $wp_plugins_dir) { $this->log("$entry: skipping: this is the plugins directory"); }
999 elseif (isset($others_skip[$entry])) { $this->log("$entry: skipping: excluded by options"); }
1000 else { $this->log("$entry: adding to list"); array_push($other_dirlist,$candidate); }
1001 }
1002 } else {
1003 $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
1004 }
1005
1006 if (count($other_dirlist)>0) {
1007 if (!$others->create($other_dirlist,PCLZIP_OPT_REMOVE_PATH,WP_CONTENT_DIR)) {
1008 $this->error('Could not create other zip. Error was '.$php_errmsg,'fatal');
1009 $this->log('ERROR: PclZip failure: Could not create other zip');
1010 } else {
1011 $this->log("Created other directories zip - file size is ".filesize($full_path)." bytes");
1012 }
1013 $backup_array['others'] = basename($full_path);
1014 } else {
1015 $this->log("No backup of other directories: there was nothing found to back up");
1016 }
1017 } else {
1018 $this->log("No backup of other directories: excluded by user's options");
1019 }
1020 return $backup_array;
1021 }
1022
1023 function save_backup_history($backup_array) {
1024 //TODO: this stores full paths right now. should probably concatenate with ABSPATH to make it easier to move sites
1025 if(is_array($backup_array)) {
1026 $backup_history = get_option('updraft_backup_history');
1027 $backup_history = (is_array($backup_history)) ? $backup_history : array();
1028 $backup_history[$this->backup_time] = $backup_array;
1029 update_option('updraft_backup_history',$backup_history);
1030 } else {
1031 $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
1032 }
1033 }
1034
1035 function get_backup_history() {
1036 //$backup_history = get_option('updraft_backup_history');
1037 //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
1038 global $wpdb;
1039 $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
1040 if(is_array($backup_history)) {
1041 krsort($backup_history); //reverse sort so earliest backup is last on the array. this way we can array_pop
1042 } else {
1043 $backup_history = array();
1044 }
1045 return $backup_history;
1046 }
1047
1048
1049 /*START OF WB-DB-BACKUP BLOCK*/
1050
1051 function backup_db() {
1052
1053 $total_tables = 0;
1054
1055 global $table_prefix, $wpdb;
1056 if(!$this->backup_time) {
1057 $this->backup_time_nonce();
1058 }
1059
1060 $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
1061 $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
1062
1063 $updraft_dir = $this->backups_dir_location();
1064 //get the blog name and rip out all non-alphanumeric chars other than _
1065 $blog_name = str_replace(' ','_',get_bloginfo());
1066 $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
1067 if(!$blog_name) {
1068 $blog_name = 'non_alpha_name';
1069 }
1070
1071 $backup_file_base = $updraft_dir.'/backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
1072 if (is_writable($updraft_dir)) {
1073 if (function_exists('gzopen')) {
1074 $this->dbhandle = @gzopen($backup_file_base.'-db.gz','w');
1075 } else {
1076 $this->dbhandle = @fopen($backup_file_base.'-db.gz', 'w');
1077 }
1078 if(!$this->dbhandle) {
1079 //$this->error(__('Could not open the backup file for writing!','wp-db-backup'));
1080 }
1081 } else {
1082 //$this->error(__('The backup directory is not writable!','wp-db-backup'));
1083 }
1084
1085 //Begin new backup of MySql
1086 $this->stow("# " . __('WordPress MySQL database backup','wp-db-backup') . "\n");
1087 $this->stow("#\n");
1088 $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
1089 $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
1090 $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
1091 $this->stow("# --------------------------------------------------------\n");
1092
1093
1094 if (defined("DB_CHARSET")) {
1095 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
1096 $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
1097 $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
1098 $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
1099 }
1100 $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n");
1101
1102 foreach ($all_tables as $table) {
1103 $total_tables++;
1104 // Increase script execution time-limit to 15 min for every table.
1105 if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1106 # === is needed, otherwise 'false' matches (i.e. prefix does not match)
1107 if ( strpos($table, $table_prefix) === 0 ) {
1108 // Create the SQL statements
1109 $this->stow("# --------------------------------------------------------\n");
1110 $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
1111 $this->stow("# --------------------------------------------------------\n");
1112 $this->backup_table($table);
1113 } else {
1114 $this->stow("# --------------------------------------------------------\n");
1115 $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
1116 $this->stow("# --------------------------------------------------------\n");
1117 }
1118 }
1119
1120 if (defined("DB_CHARSET")) {
1121 $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
1122 $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
1123 $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
1124 }
1125
1126 $this->close($this->dbhandle);
1127
1128 if (count($this->errors)) {
1129 return false;
1130 } else {
1131 # Encrypt, if requested
1132 $encryption = get_option('updraft_encryptionphrase');
1133 if (strlen($encryption) > 0) {
1134 $this->log("Database: applying encryption");
1135 $encryption_error = 0;
1136 require_once(dirname(__FILE__).'/includes/Rijndael.php');
1137 $rijndael = new Crypt_Rijndael();
1138 $rijndael->setKey($encryption);
1139 $in_handle = @fopen($backup_file_base.'-db.gz','r');
1140 $buffer = "";
1141 while (!feof ($in_handle)) {
1142 $buffer .= fread($in_handle, 16384);
1143 }
1144 fclose ($in_handle);
1145 $out_handle = @fopen($backup_file_base.'-db.gz.crypt','w');
1146 if (!fwrite($out_handle, $rijndael->encrypt($buffer))) {$encryption_error = 1;}
1147 fclose ($out_handle);
1148 if (0 == $encryption_error) {
1149 # Delete unencrypted file
1150 @unlink($backup_file_base.'-db.gz');
1151 return basename($backup_file_base.'-db.gz.crypt');
1152 } else {
1153 $this->error("Encryption error occurred when encrypting database. Aborted.");
1154 }
1155 } else {
1156 return basename($backup_file_base.'-db.gz');
1157 }
1158 }
1159 $this->log("Total database tables backed up: $total_tables");
1160
1161 } //wp_db_backup
1162
1163 /**
1164 * Taken partially from phpMyAdmin and partially from
1165 * Alain Wolf, Zurich - Switzerland
1166 * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
1167 * Modified by Scott Merrill (http://www.skippy.net/)
1168 * to use the WordPress $wpdb object
1169 * @param string $table
1170 * @param string $segment
1171 * @return void
1172 */
1173 function backup_table($table, $segment = 'none') {
1174 global $wpdb;
1175
1176 $total_rows = 0;
1177
1178 $table_structure = $wpdb->get_results("DESCRIBE $table");
1179 if (! $table_structure) {
1180 //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
1181 return false;
1182 }
1183
1184 if(($segment == 'none') || ($segment == 0)) {
1185 // Add SQL statement to drop existing table
1186 $this->stow("\n\n");
1187 $this->stow("#\n");
1188 $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1189 $this->stow("#\n");
1190 $this->stow("\n");
1191 $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
1192
1193 // Table structure
1194 // Comment in SQL-file
1195 $this->stow("\n\n");
1196 $this->stow("#\n");
1197 $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1198 $this->stow("#\n");
1199 $this->stow("\n");
1200
1201 $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
1202 if (false === $create_table) {
1203 $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
1204 //$this->error($err_msg);
1205 $this->stow("#\n# $err_msg\n#\n");
1206 }
1207 $this->stow($create_table[0][1] . ' ;');
1208
1209 if (false === $table_structure) {
1210 $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
1211 //$this->error($err_msg);
1212 $this->stow("#\n# $err_msg\n#\n");
1213 }
1214
1215 // Comment in SQL-file
1216 $this->stow("\n\n");
1217 $this->stow("#\n");
1218 $this->stow('# ' . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1219 $this->stow("#\n");
1220 }
1221
1222 if(($segment == 'none') || ($segment >= 0)) {
1223 $defs = array();
1224 $ints = array();
1225 foreach ($table_structure as $struct) {
1226 if ( (0 === strpos($struct->Type, 'tinyint')) ||
1227 (0 === strpos(strtolower($struct->Type), 'smallint')) ||
1228 (0 === strpos(strtolower($struct->Type), 'mediumint')) ||
1229 (0 === strpos(strtolower($struct->Type), 'int')) ||
1230 (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
1231 $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
1232 $ints[strtolower($struct->Field)] = "1";
1233 }
1234 }
1235
1236
1237 // Batch by $row_inc
1238 if ( ! defined('ROWS_PER_SEGMENT') ) {
1239 define('ROWS_PER_SEGMENT', 100);
1240 }
1241
1242 if($segment == 'none') {
1243 $row_start = 0;
1244 $row_inc = ROWS_PER_SEGMENT;
1245 } else {
1246 $row_start = $segment * ROWS_PER_SEGMENT;
1247 $row_inc = ROWS_PER_SEGMENT;
1248 }
1249 do {
1250 // don't include extra stuff, if so requested
1251 $excs = array('revisions' => 0, 'spam' => 1); //TODO, FIX THIS
1252 $where = '';
1253 if ( is_array($excs['spam'] ) && in_array($table, $excs['spam']) ) {
1254 $where = ' WHERE comment_approved != "spam"';
1255 } elseif ( is_array($excs['revisions'] ) && in_array($table, $excs['revisions']) ) {
1256 $where = ' WHERE post_type != "revision"';
1257 }
1258
1259 if ( !ini_get('safe_mode') || strtolower(ini_get('safe_mode')) == "off") @set_time_limit(15*60);
1260 $table_data = $wpdb->get_results("SELECT * FROM $table $where LIMIT {$row_start}, {$row_inc}", ARRAY_A);
1261 $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
1262 // \x08\\x09, not required
1263 $search = array("\x00", "\x0a", "\x0d", "\x1a");
1264 $replace = array('\0', '\n', '\r', '\Z');
1265 if($table_data) {
1266 foreach ($table_data as $row) {
1267 $total_rows++;
1268 $values = array();
1269 foreach ($row as $key => $value) {
1270 if ($ints[strtolower($key)]) {
1271 // make sure there are no blank spots in the insert syntax,
1272 // yet try to avoid quotation marks around integers
1273 $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
1274 $values[] = ( '' === $value ) ? "''" : $value;
1275 } else {
1276 $values[] = "'" . str_replace($search, $replace, $this->sql_addslashes($value)) . "'";
1277 }
1278 }
1279 $this->stow(" \n" . $entries . implode(', ', $values) . ');');
1280 }
1281 $row_start += $row_inc;
1282 }
1283 } while((count($table_data) > 0) and ($segment=='none'));
1284 }
1285
1286 if(($segment == 'none') || ($segment < 0)) {
1287 // Create footer/closing comment in SQL-file
1288 $this->stow("\n");
1289 $this->stow("#\n");
1290 $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
1291 $this->stow("# --------------------------------------------------------\n");
1292 $this->stow("\n");
1293 }
1294 $this->log("Table $table: Total rows added: $total_rows");
1295
1296 } // end backup_table()
1297
1298
1299 function stow($query_line) {
1300 if (function_exists('gzopen')) {
1301 if(! @gzwrite($this->dbhandle, $query_line)) {
1302 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1303 }
1304 } else {
1305 if(false === @fwrite($this->dbhandle, $query_line)) {
1306 //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
1307 }
1308 }
1309 }
1310
1311
1312 function close($handle) {
1313 if (function_exists('gzopen')) {
1314 gzclose($handle);
1315 } else {
1316 fclose($handle);
1317 }
1318 }
1319
1320 function error($error,$severity='') {
1321 $this->errors[] = $error;
1322 return true;
1323 }
1324
1325 /**
1326 * Add backquotes to tables and db-names in
1327 * SQL queries. Taken from phpMyAdmin.
1328 */
1329 function backquote($a_name) {
1330 if (!empty($a_name) && $a_name != '*') {
1331 if (is_array($a_name)) {
1332 $result = array();
1333 reset($a_name);
1334 while(list($key, $val) = each($a_name))
1335 $result[$key] = '`' . $val . '`';
1336 return $result;
1337 } else {
1338 return '`' . $a_name . '`';
1339 }
1340 } else {
1341 return $a_name;
1342 }
1343 }
1344
1345 /**
1346 * Better addslashes for SQL queries.
1347 * Taken from phpMyAdmin.
1348 */
1349 function sql_addslashes($a_string = '', $is_like = false) {
1350 if ($is_like) $a_string = str_replace('\\', '\\\\\\\\', $a_string);
1351 else $a_string = str_replace('\\', '\\\\', $a_string);
1352 return str_replace('\'', '\\\'', $a_string);
1353 }
1354
1355 /*END OF WP-DB-BACKUP BLOCK */
1356
1357 /*
1358 this function is both the backup scheduler and ostensibly a filter callback for saving the option.
1359 it is called in the register_setting for the updraft_interval, which means when the admin settings
1360 are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
1361 so the option can be properly saved.
1362 */
1363 function schedule_backup($interval) {
1364 //clear schedule and add new so we don't stack up scheduled backups
1365 wp_clear_scheduled_hook('updraft_backup');
1366 switch($interval) {
1367 case 'daily':
1368 case 'weekly':
1369 case 'monthly':
1370 wp_schedule_event(time()+30, $interval, 'updraft_backup');
1371 break;
1372 }
1373 return wp_filter_nohtml_kses($interval);
1374 }
1375
1376 function schedule_backup_database($interval) {
1377 //clear schedule and add new so we don't stack up scheduled backups
1378 wp_clear_scheduled_hook('updraft_backup_database');
1379 switch($interval) {
1380 case 'daily':
1381 case 'weekly':
1382 case 'monthly':
1383 wp_schedule_event(time()+30, $interval, 'updraft_backup_database');
1384 break;
1385 }
1386 return wp_filter_nohtml_kses($interval);
1387 }
1388
1389 //wp-cron only has hourly, daily and twicedaily, so we need to add weekly and monthly.
1390 function modify_cron_schedules($schedules) {
1391 $schedules['weekly'] = array(
1392 'interval' => 604800,
1393 'display' => 'Once Weekly'
1394 );
1395 $schedules['monthly'] = array(
1396 'interval' => 2592000,
1397 'display' => 'Once Monthly'
1398 );
1399 return $schedules;
1400 }
1401
1402 function backups_dir_location() {
1403 $updraft_dir = untrailingslashit(get_option('updraft_dir'));
1404 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1405 //if the option isn't set, default it to /backups inside the upload dir
1406 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1407 //check for the existence of the dir and an enumeration preventer.
1408 if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
1409 @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
1410 @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
1411 @file_put_contents($updraft_dir.'/.htaccess','deny from all');
1412 }
1413 return $updraft_dir;
1414 }
1415
1416 function updraft_download_backup() {
1417 $type = $_POST['type'];
1418 $timestamp = (int)$_POST['timestamp'];
1419 $backup_history = $this->get_backup_history();
1420 $file = $backup_history[$timestamp][$type];
1421 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1422 if(!is_readable($fullpath)) {
1423 //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
1424 $this->download_backup($file);
1425 }
1426 if(@is_readable($fullpath) && is_file($fullpath)) {
1427 $len = filesize($fullpath);
1428
1429 $filearr = explode('.',$file);
1430 // //we've only got zip and gz...for now
1431 $file_ext = array_pop($filearr);
1432 if($file_ext == 'zip') {
1433 header('Content-type: application/zip');
1434 } else {
1435 // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
1436 header('Content-type: application/x-gzip');
1437 }
1438 header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
1439 header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
1440 header("Content-Length: $len;");
1441 if ($file_ext == 'crypt') {
1442 header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
1443 } else {
1444 header("Content-Disposition: attachment; filename=\"$file\";");
1445 }
1446 ob_end_flush();
1447 if ($file_ext == 'crypt') {
1448 $encryption = get_option('updraft_encryptionphrase');
1449 if ($encryption == "") {
1450 $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
1451 } else {
1452 require_once(dirname(__FILE__).'/includes/Rijndael.php');
1453 $rijndael = new Crypt_Rijndael();
1454 $rijndael->setKey($encryption);
1455 $in_handle = fopen($fullpath,'r');
1456 $ciphertext = "";
1457 while (!feof ($in_handle)) {
1458 $ciphertext .= fread($in_handle, 16384);
1459 }
1460 fclose ($in_handle);
1461 print $rijndael->decrypt($ciphertext);
1462 }
1463 } else {
1464 readfile($fullpath);
1465 }
1466 $this->delete_local($file);
1467 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')?
1468 } else {
1469 echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then S3 or Google Drive or FTP retrieval may have failed. (Note that Google Drive downloading is not yet supported - you need to download manually if you use Google Drive).';
1470 }
1471 }
1472
1473 function download_backup($file) {
1474 switch(get_option('updraft_service')) {
1475 case 'googledrive':
1476 $this->download_googledrive_backup($file);
1477 break;
1478 case 's3':
1479 $this->download_s3_backup($file);
1480 break;
1481 case 'ftp':
1482 $this->download_ftp_backup($file);
1483 break;
1484 default:
1485 $this->error('Automatic backup restoration is only available via S3, FTP, and local. Email and downloaded backup restoration must be performed manually.');
1486 }
1487 }
1488
1489 function download_googledrive_backup($file) {
1490
1491 require_once(dirname(__FILE__).'/includes/class-gdocs.php');
1492
1493 // Do we have an access token?
1494 if ( !$access_token = $this->access_token( get_option('updraft_googledrive_token'), get_option('updraft_googledrive_clientid'), get_option('updraft_googledrive_secret') )) {
1495 $this->error('ERROR: Have not yet obtained an access token from Google (has the user authorised?)');
1496 return false;
1497 }
1498
1499 $this->gdocs_access_token = $access_token;
1500
1501 // Make sure $this->gdocs is a UpdraftPlus_GDocs object, or give an error
1502 if ( is_wp_error( $e = $this->need_gdocs() ) ) return false;
1503
1504 $ids = get_option('updraft_file_ids', array());
1505 if (!isset($ids[$file])) {
1506 $this->error("Google Drive error: $file: could not download: could not find a record of the Google Drive file ID for this file");
1507 return;
1508 } else {
1509 $content_link = $this->gdocs->get_content_link( $ids[$file], $file );
1510 if (is_wp_error($content_link)) {
1511 $this->error("Could not find $file in order to download it (id: ".$ids[$file].")");
1512 foreach ($content_link->get_error_messages() as $msg) {
1513 $this->error($msg);
1514 }
1515 return false;
1516 }
1517 // Actually download the thing
1518 $download_to = trailingslashit(get_option('updraft_dir')).$file;
1519 $this->gdocs->download_data($content_link, $download_to);
1520
1521 if (filesize($download_to) >0) {
1522 return true;
1523 } else {
1524 $this->error("Google Drive error: zero-size file was downloaded");
1525 return false;
1526 }
1527
1528 }
1529
1530 return;
1531
1532 }
1533
1534 function download_s3_backup($file) {
1535 if(!class_exists('S3')) {
1536 require_once(dirname(__FILE__).'/includes/S3.php');
1537 }
1538 $s3 = new S3(get_option('updraft_s3_login'), get_option('updraft_s3_pass'));
1539 $bucket_name = untrailingslashit(get_option('updraft_s3_remote_path'));
1540 $bucket_path = "";
1541 if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
1542 $bucket_name = $bmatches[1];
1543 $bucket_path = $bmatches[2]."/";
1544 }
1545 if (@$s3->getBucketLocation($bucket_name)) {
1546 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1547 if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath)) {
1548 $this->error("S3 Error: Failed to download $fullpath. Error was ".$php_errormsg);
1549 }
1550 } else {
1551 $this->error("S3 Error: Failed to create bucket $bucket_name. Error was ".$php_errormsg);
1552 }
1553 }
1554
1555 function download_ftp_backup($file) {
1556 if( !class_exists('ftp_wrapper')) require_once(dirname(__FILE__).'/includes/ftp.class.php');
1557
1558 //handle SSL and errors at some point TODO
1559 $ftp = new ftp_wrapper(get_option('updraft_server_address'),get_option('updraft_ftp_login'),get_option('updraft_ftp_pass'));
1560 $ftp->passive = true;
1561 $ftp->connect();
1562 //$ftp->make_dir(); we may need to recursively create dirs? TODO
1563
1564 $ftp_remote_path = trailingslashit(get_option('updraft_ftp_remote_path'));
1565 $fullpath = trailingslashit(get_option('updraft_dir')).$file;
1566 $ftp->get($fullpath,$ftp_remote_path.$file,FTP_BINARY);
1567 }
1568
1569 function restore_backup($timestamp) {
1570 global $wp_filesystem;
1571 $backup_history = get_option('updraft_backup_history');
1572 if(!is_array($backup_history[$timestamp])) {
1573 echo '<p>This backup does not exist in the backup history - restoration aborted. Timestamp: '.$timestamp.'</p><br/>';
1574 return false;
1575 }
1576
1577 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
1578 WP_Filesystem($credentials);
1579 if ( $wp_filesystem->errors->get_error_code() ) {
1580 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1581 show_message($message);
1582 exit;
1583 }
1584
1585 //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?)
1586 echo '<span style="font-weight:bold">Restoration Progress </span><div id="updraft-restore-progress">';
1587
1588 $updraft_dir = trailingslashit(get_option('updraft_dir'));
1589 foreach($backup_history[$timestamp] as $type=>$file) {
1590 $fullpath = $updraft_dir.$file;
1591 if(!is_readable($fullpath) && $type != 'db') {
1592 $this->download_backup($file);
1593 }
1594 # Types: uploads, themes, plugins, others, db
1595 if(is_readable($fullpath) && $type != 'db') {
1596 if(!class_exists('WP_Upgrader')) {
1597 require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
1598 }
1599 require_once('includes/updraft-restorer.php');
1600 $restorer = new Updraft_Restorer();
1601 $val = $restorer->restore_backup($fullpath,$type);
1602 if(is_wp_error($val)) {
1603 print_r($val);
1604 echo '</div>'; //close the updraft_restore_progress div even if we error
1605 return false;
1606 }
1607 }
1608 }
1609 echo '</div>'; //close the updraft_restore_progress div
1610 # 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
1611 if(ini_get('safe_mode') && strtolower(ini_get('safe_mode')) != "off") {
1612 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/>";
1613 return false;
1614 }
1615 return true;
1616 }
1617
1618 //deletes the -old directories that are created when a backup is restored.
1619 function delete_old_dirs() {
1620 global $wp_filesystem;
1621 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
1622 WP_Filesystem($credentials);
1623 if ( $wp_filesystem->errors->get_error_code() ) {
1624 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1625 show_message($message);
1626 exit;
1627 }
1628
1629 $to_delete = array('themes-old','plugins-old','uploads-old','others-old');
1630
1631 foreach($to_delete as $name) {
1632 //recursively delete
1633 if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
1634 return false;
1635 }
1636 }
1637 return true;
1638 }
1639
1640 //scans the content dir to see if any -old dirs are present
1641 function scan_old_dirs() {
1642 $dirArr = scandir(WP_CONTENT_DIR);
1643 foreach($dirArr as $dir) {
1644 if(strpos($dir,'-old') !== false) {
1645 return true;
1646 }
1647 }
1648 return false;
1649 }
1650
1651
1652 function retain_range($input) {
1653 $input = (int)$input;
1654 if($input > 0 && $input < 3650) {
1655 return $input;
1656 } else {
1657 return 1;
1658 }
1659 }
1660
1661 function create_backup_dir() {
1662 global $wp_filesystem;
1663 $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
1664 WP_Filesystem($credentials);
1665 if ( $wp_filesystem->errors->get_error_code() ) {
1666 foreach ( $wp_filesystem->errors->get_error_messages() as $message )
1667 show_message($message);
1668 exit;
1669 }
1670
1671 $updraft_dir = untrailingslashit(get_option('updraft_dir'));
1672 $default_backup_dir = WP_CONTENT_DIR.'/updraft';
1673 $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
1674
1675 //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...)
1676 if(!$wp_filesystem->mkdir($updraft_dir, 0777)) {
1677 return false;
1678 }
1679 return true;
1680 }
1681
1682
1683 function memory_check_current() {
1684 # Returns in megabytes
1685 $memory_limit = ini_get('memory_limit');
1686 $memory_unit = $memory_limit[strlen($memory_limit)-1];
1687 $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
1688 switch($memory_unit) {
1689 case 'K':
1690 $memory_limit = $memory_limit/1024;
1691 break;
1692 case 'G':
1693 $memory_limit = $memory_limit*1024;
1694 break;
1695 case 'M':
1696 //assumed size, no change needed
1697 break;
1698 }
1699 return $memory_limit;
1700 }
1701
1702 function memory_check($memory) {
1703 $memory_limit = $this->memory_check_current();
1704 return ($memory_limit >= $memory)?true:false;
1705 }
1706
1707 function execution_time_check($time) {
1708 return (ini_get('max_execution_time') >= $time)?true:false;
1709 }
1710
1711 function admin_init() {
1712 if(get_option('updraft_debug_mode')) {
1713 ini_set('display_errors',1);
1714 error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
1715 ini_set('track_errors',1);
1716 }
1717 wp_enqueue_script('jquery');
1718 register_setting( 'updraft-options-group', 'updraft_interval', array($this,'schedule_backup') );
1719 register_setting( 'updraft-options-group', 'updraft_interval_database', array($this,'schedule_backup_database') );
1720 register_setting( 'updraft-options-group', 'updraft_retain', array($this,'retain_range') );
1721 register_setting( 'updraft-options-group', 'updraft_encryptionphrase', 'wp_filter_nohtml_kses' );
1722 register_setting( 'updraft-options-group', 'updraft_service', 'wp_filter_nohtml_kses' );
1723 register_setting( 'updraft-options-group', 'updraft_s3_login', 'wp_filter_nohtml_kses' );
1724 register_setting( 'updraft-options-group', 'updraft_s3_pass', 'wp_filter_nohtml_kses' );
1725 register_setting( 'updraft-options-group', 'updraft_s3_remote_path', 'wp_filter_nohtml_kses' );
1726 register_setting( 'updraft-options-group', 'updraft_googledrive_clientid', 'wp_filter_nohtml_kses' );
1727 register_setting( 'updraft-options-group', 'updraft_googledrive_secret', 'wp_filter_nohtml_kses' );
1728 register_setting( 'updraft-options-group', 'updraft_googledrive_remotepath', 'wp_filter_nohtml_kses' );
1729 register_setting( 'updraft-options-group', 'updraft_ftp_login', 'wp_filter_nohtml_kses' );
1730 register_setting( 'updraft-options-group', 'updraft_ftp_pass', 'wp_filter_nohtml_kses' );
1731 register_setting( 'updraft-options-group', 'updraft_dir', 'wp_filter_nohtml_kses' );
1732 register_setting( 'updraft-options-group', 'updraft_email', 'wp_filter_nohtml_kses' );
1733 register_setting( 'updraft-options-group', 'updraft_ftp_remote_path', 'wp_filter_nohtml_kses' );
1734 register_setting( 'updraft-options-group', 'updraft_server_address', 'wp_filter_nohtml_kses' );
1735 register_setting( 'updraft-options-group', 'updraft_delete_local', 'absint' );
1736 register_setting( 'updraft-options-group', 'updraft_debug_mode', 'absint' );
1737 register_setting( 'updraft-options-group', 'updraft_include_plugins', 'absint' );
1738 register_setting( 'updraft-options-group', 'updraft_include_themes', 'absint' );
1739 register_setting( 'updraft-options-group', 'updraft_include_uploads', 'absint' );
1740 register_setting( 'updraft-options-group', 'updraft_include_others', 'absint' );
1741 register_setting( 'updraft-options-group', 'updraft_include_others_exclude', 'wp_filter_nohtml_kses' );
1742
1743 /* I see no need for this check; people can only download backups/logs if they can guess a nonce formed from a random number and if .htaccess files have no effect. The database will be encrypted. Very unlikely.
1744 if (current_user_can('manage_options')) {
1745 $updraft_dir = $this->backups_dir_location();
1746 if(strpos($updraft_dir,WP_CONTENT_DIR) !== false) {
1747 $relative_dir = str_replace(WP_CONTENT_DIR,'',$updraft_dir);
1748 $possible_updraft_url = WP_CONTENT_URL.$relative_dir;
1749 $resp = wp_remote_request($possible_updraft_url, array('timeout' => 15));
1750 if ( is_wp_error($resp) ) {
1751 add_action('admin_notices', array($this,'show_admin_warning_accessible_unknownresult') );
1752 } else {
1753 if(strpos($resp['response']['code'],'403') === false) {
1754 add_action('admin_notices', array($this,'show_admin_warning_accessible') );
1755 }
1756 }
1757 }
1758 }
1759 */
1760 if (current_user_can('manage_options') && get_option('updraft_service') == "googledrive" && get_option('updraft_googledrive_clientid') != "" && get_option('updraft_googledrive_token','xyz') == 'xyz') {
1761 add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
1762 }
1763 }
1764
1765 function add_admin_pages() {
1766 add_submenu_page('options-general.php', "UpdraftPlus", "UpdraftPlus", "manage_options", "updraftplus",
1767 array($this,"settings_output"));
1768 }
1769
1770 function wordshell_random_advert($urls) {
1771 $url_start = ($urls) ? '<a href="http://wordshell.net">' : "";
1772 $url_end = ($urls) ? '</a>' : " (www.wordshell.net)";
1773 if (rand(0,1) == 0) {
1774 return "Like automating WordPress operations? Use the CLI? ${url_start}You will love WordShell${url_end} - saves time and money fast.";
1775 } else {
1776 return "${url_start}Check out WordShell${url_end} - manage WordPress from the command line - huge time-saver";
1777 }
1778 }
1779
1780 function settings_output() {
1781
1782 /*
1783 we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1784 for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
1785 passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
1786 */
1787 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
1788 $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
1789 if(empty($this->errors) && $backup_success == true) {
1790 echo '<p>Restore successful!</p><br/>';
1791 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
1792 return;
1793 } else {
1794 echo '<p>Restore failed...</p><ul>';
1795 foreach ($this->errors as $err) {
1796 echo "<li>";
1797 if (is_string($err)) { echo htmlspecialchars($err); } else {
1798 print_r($err);
1799 }
1800 echo "</li>";
1801 }
1802 echo '</ul><b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1803 return;
1804 }
1805 //uncomment the below once i figure out how i want the flow of a restoration to work.
1806 //echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1807 }
1808 $deleted_old_dirs = false;
1809 if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
1810 if($this->delete_old_dirs()) {
1811 $deleted_old_dirs = true;
1812 } else {
1813 echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
1814 }
1815 echo '<p>Old directories successfully removed.</p><br/>';
1816 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1817 return;
1818 }
1819
1820 if(isset($_GET['error'])) {
1821 echo "<p><strong>ERROR:</strong> ".htmlspecialchars($_GET['error'])."</p>";
1822 }
1823 if(isset($_GET['message'])) {
1824 echo "<p><strong>Note:</strong> ".htmlspecialchars($_GET['message'])."</p>";
1825 }
1826
1827 if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
1828 if(!$this->create_backup_dir()) {
1829 echo '<p>Backup directory could not be created...</p><br/>';
1830 }
1831 echo '<p>Backup directory successfully created.</p><br/>';
1832 echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
1833 return;
1834 }
1835
1836 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
1837 echo '<div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;"><strong>Schedule backup:</strong> ';
1838 if (wp_schedule_single_event(time()+5, 'updraft_backup_all') === false) {
1839 echo "Failed.";
1840 } else {
1841 echo "OK. Now load a page from your site to make sure the schedule can trigger.";
1842 }
1843 echo '</div>';
1844 }
1845 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') {
1846 $this->backup(true,true);
1847 }
1848 if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') {
1849 $this->backup_db();
1850 }
1851
1852 ?>
1853 <div class="wrap">
1854 <h1>UpdraftPlus - Backup/Restore</h1>
1855
1856 <!-- Version: <b><?php echo $this->version; ?></b><br>-->
1857 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; ?>
1858 <br>
1859 <?php
1860 if(isset($_GET['updraft_restore_success'])) {
1861 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>";
1862 }
1863
1864 $ws_advert = $this->wordshell_random_advert(1);
1865 echo <<<ENDHERE
1866 <div class="updated fade" style="max-width: 800px; font-size:140%; padding:14px; clear:left;">${ws_advert}</div>
1867 ENDHERE;
1868
1869
1870 if($deleted_old_dirs) {
1871 echo '<div style="color:blue">Old directories successfully deleted.</div>';
1872 }
1873 if(!$this->memory_check(96)) {?>
1874 <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 (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>
1875 <?php
1876 }
1877 if(!$this->execution_time_check(300)) {?>
1878 <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>
1879 <?php
1880 }
1881
1882 if($this->scan_old_dirs()) {?>
1883 <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>
1884 <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
1885 <input type="hidden" name="action" value="updraft_delete_old_dirs" />
1886 <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.'))" />
1887 </form>
1888 <?php
1889 }
1890 if(!empty($this->errors)) {
1891 foreach($this->errors as $error) {
1892 //ignoring severity here right now
1893 echo '<div style="color:red">'.$error['error'].'</div>';
1894 }
1895 }
1896 ?>
1897
1898 <h2 style="clear:left;">Existing Schedule And Backups</h2>
1899 <table class="form-table" style="float:left; clear: both; width:475px">
1900 <tr>
1901 <?php
1902 $next_scheduled_backup = wp_next_scheduled('updraft_backup');
1903 $next_scheduled_backup = ($next_scheduled_backup) ? date('D, F j, Y H:i T',$next_scheduled_backup) : 'No backups are scheduled at this time.';
1904 $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
1905 if (get_option('updraft_interval_database',get_option('updraft_interval')) == get_option('updraft_interval')) {
1906 $next_scheduled_backup_database = "Will take place at the same time as the files backup.";
1907 } else {
1908 $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.';
1909 }
1910 $current_time = date('D, F j, Y H:i T',time());
1911 $updraft_last_backup = get_option('updraft_last_backup');
1912 if($updraft_last_backup) {
1913 if($updraft_last_backup['success']) {
1914 $last_backup = date('D, F j, Y H:i T',$updraft_last_backup['backup_time']);
1915 $last_backup_color = 'green';
1916 } else {
1917 $last_backup = print_r($updraft_last_backup['errors'],true);
1918 $last_backup_color = 'red';
1919 }
1920 } else {
1921 $last_backup = 'No backup has been completed.';
1922 $last_backup_color = 'blue';
1923 }
1924
1925 $updraft_dir = $this->backups_dir_location();
1926 if(is_writable($updraft_dir)) {
1927 $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
1928 $backup_disabled = "";
1929 } else {
1930 $backup_disabled = 'disabled="disabled"';
1931 $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>';
1932 }
1933 ?>
1934
1935 <th>The Time Now:</th>
1936 <td style="color:blue"><?php echo $current_time?></td>
1937 </tr>
1938 <tr>
1939 <th>Next Scheduled Files Backup:</th>
1940 <td style="color:blue"><?php echo $next_scheduled_backup?></td>
1941 </tr>
1942 <tr>
1943 <th>Next Scheduled DB Backup:</th>
1944 <td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
1945 </tr>
1946 <tr>
1947 <th>Last Backup:</th>
1948 <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
1949 </tr>
1950 </table>
1951 <div style="float:left; width:200px; padding-top: 100px;">
1952 <form method="post" action="">
1953 <input type="hidden" name="action" value="updraft_backup" />
1954 <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>
1955 </form>
1956 <div style="position:relative">
1957 <div style="position:absolute;top:0;left:0">
1958 <?php
1959 $backup_history = get_option('updraft_backup_history');
1960 $backup_history = (is_array($backup_history))?$backup_history:array();
1961 $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
1962 ?>
1963 <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')" />
1964 </div>
1965 <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
1966 <form method="post" action="">
1967 <b>Choose: </b>
1968 <select name="backup_timestamp" style="display:inline">
1969 <?php
1970 foreach($backup_history as $key=>$value) {
1971 echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
1972 }
1973 ?>
1974 </select>
1975
1976 <input type="hidden" name="action" value="updraft_restore" />
1977 <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?'))" />
1978 </form>
1979 </div>
1980 </div>
1981 </div>
1982 <br style="clear:both" />
1983 <table class="form-table">
1984 <tr>
1985 <th>Download Backups</th>
1986 <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
1987 </tr>
1988 <tr>
1989 <td></td><td class="download-backups" style="display:none">
1990 <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>
1991 <table>
1992 <?php
1993 foreach($backup_history as $key=>$value) {
1994 ?>
1995 <tr>
1996 <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
1997 <td>
1998 <?php if (isset($value['db'])) { ?>
1999 <form action="admin-ajax.php" method="post">
2000 <input type="hidden" name="action" value="updraft_download_backup" />
2001 <input type="hidden" name="type" value="db" />
2002 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2003 <input type="submit" value="Database" />
2004 </form>
2005 <?php } else { echo "(No database in backup)"; } ?>
2006 </td>
2007 <td>
2008 <?php if (isset($value['plugins'])) { ?>
2009 <form action="admin-ajax.php" method="post">
2010 <input type="hidden" name="action" value="updraft_download_backup" />
2011 <input type="hidden" name="type" value="plugins" />
2012 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2013 <input type="submit" value="Plugins" />
2014 </form>
2015 <?php } else { echo "(No plugins in backup)"; } ?>
2016 </td>
2017 <td>
2018 <?php if (isset($value['themes'])) { ?>
2019 <form action="admin-ajax.php" method="post">
2020 <input type="hidden" name="action" value="updraft_download_backup" />
2021 <input type="hidden" name="type" value="themes" />
2022 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2023 <input type="submit" value="Themes" />
2024 </form>
2025 <?php } else { echo "(No themes in backup)"; } ?>
2026 </td>
2027 <td>
2028 <?php if (isset($value['uploads'])) { ?>
2029 <form action="admin-ajax.php" method="post">
2030 <input type="hidden" name="action" value="updraft_download_backup" />
2031 <input type="hidden" name="type" value="uploads" />
2032 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2033 <input type="submit" value="Uploads" />
2034 </form>
2035 <?php } else { echo "(No uploads in backup)"; } ?>
2036 </td>
2037 <td>
2038 <?php if (isset($value['others'])) { ?>
2039 <form action="admin-ajax.php" method="post">
2040 <input type="hidden" name="action" value="updraft_download_backup" />
2041 <input type="hidden" name="type" value="others" />
2042 <input type="hidden" name="timestamp" value="<?php echo $key?>" />
2043 <input type="submit" value="Others" />
2044 </form>
2045 <?php } else { echo "(No others in backup)"; } ?>
2046 </td>
2047 </tr>
2048 <?php }?>
2049 </table>
2050 </td>
2051 </tr>
2052 </table>
2053 <form method="post" action="options.php">
2054 <?php settings_fields('updraft-options-group'); ?>
2055 <h2>Configure Backup Contents And Schedule</h2>
2056 <table class="form-table" style="width:850px;">
2057 <tr>
2058 <th>File Backup Intervals:</th>
2059 <td><select name="updraft_interval">
2060 <?php
2061 $intervals = array ("manual", "daily", "weekly", "monthly");
2062 foreach ($intervals as $ival) {
2063 echo "<option value=\"$ival\" ";
2064 if ($ival == get_option('updraft_interval','manual')) { echo 'selected="selected"';}
2065 echo ">".ucfirst($ival)."</option>\n";
2066 }
2067 ?>
2068 </select></td>
2069 </tr>
2070 <tr>
2071 <th>Database Backup Intervals:</th>
2072 <td><select name="updraft_interval_database">
2073 <?php
2074 $intervals = array ("manual", "daily", "weekly", "monthly");
2075 foreach ($intervals as $ival) {
2076 echo "<option value=\"$ival\" ";
2077 if ($ival == get_option('updraft_interval_database',get_option('updraft_interval'))) { echo 'selected="selected"';}
2078 echo ">".ucfirst($ival)."</option>\n";
2079 }
2080 ?>
2081 </select></td>
2082 </tr>
2083 <tr class="backup-interval-description">
2084 <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>
2085 </tr>
2086 <?php
2087 # The true (default value if non-existent) here has the effect of forcing a default of on.
2088 $include_themes = (get_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
2089 $include_plugins = (get_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
2090 $include_uploads = (get_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
2091 $include_others = (get_option('updraft_include_others',true)) ? 'checked="checked"' : "";
2092 $include_others_exclude = get_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE);
2093 ?>
2094 <tr>
2095 <th>Include in Files Backup:</th>
2096 <td>
2097 <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br>
2098 <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br>
2099 <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br>
2100 <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>
2101 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>
2102 </td>
2103 </tr>
2104 <tr>
2105 <th>Retain Backups:</th>
2106 <?php
2107 $updraft_retain = get_option('updraft_retain');
2108 $retain = ((int)$updraft_retain > 0)?get_option('updraft_retain'):1;
2109 ?>
2110 <td><input type="text" name="updraft_retain" value="<?php echo $retain ?>" style="width:50px" /></td>
2111 </tr>
2112 <tr class="email" <?php echo $email_display?>>
2113 <th>Email:</th>
2114 <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>
2115 </tr>
2116 <tr class="deletelocal s3 ftp email" <?php echo $display_delete_local?>>
2117 <th>Delete local backup:</th>
2118 <td><input type="checkbox" name="updraft_delete_local" value="1" <?php $delete_local = (get_option('updraft_delete_local')) ? 'checked="checked"' : "";
2119 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>
2120 </tr>
2121
2122 <tr class="backup-retain-description">
2123 <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>
2124 </tr>
2125 <tr>
2126 <th>Database encryption phrase:</th>
2127 <?php
2128 $updraft_encryptionphrase = get_option('updraft_encryptionphrase');
2129 ?>
2130 <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
2131 </tr>
2132 <tr class="backup-crypt-description">
2133 <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>
2134 </tr>
2135 </table>
2136
2137 <h2>Copying Your Backup To Remote Storage</h2>
2138
2139 <table class="form-table" style="width:850px;">
2140 <tr>
2141 <th>Remote backup:</th>
2142 <td><select name="updraft_service" id="updraft-service">
2143 <?php
2144 $debug_mode = (get_option('updraft_debug_mode')) ? 'checked="checked"' : "";
2145
2146 $display_none = 'style="display:none"';
2147 $s3 = ""; $ftp = ""; $email = ""; $googledrive="";
2148 $email_display="";
2149 $display_email_complete = "";
2150 $set = 'selected="selected"';
2151 switch(get_option('updraft_service')) {
2152 case 's3':
2153 $s3 = $set;
2154 $googledrive_display = $display_none;
2155 $ftp_display = $display_none;
2156 break;
2157 case 'googledrive':
2158 $googledrive = $set;
2159 $s3_display = $display_none;
2160 $ftp_display = $display_none;
2161 break;
2162 case 'ftp':
2163 $ftp = $set;
2164 $googledrive_display = $display_none;
2165 $s3_display = $display_none;
2166 break;
2167 case 'email':
2168 $email = $set;
2169 $ftp_display = $display_none;
2170 $s3_display = $display_none;
2171 $googledrive_display = $display_none;
2172 $display_email_complete = $display_none;
2173 break;
2174 default:
2175 $none = $set;
2176 $ftp_display = $display_none;
2177 $googledrive_display = $display_none;
2178 $s3_display = $display_none;
2179 $display_delete_local = $display_none;
2180 break;
2181 }
2182 ?>
2183 <option value="none" <?php echo $none?>>None</option>
2184 <option value="s3" <?php echo $s3?>>Amazon S3</option>
2185 <option value="googledrive" <?php echo $googledrive?>>Google Drive</option>
2186 <option value="ftp" <?php echo $ftp?>>FTP</option>
2187 <option value="email" <?php echo $email?>>E-mail</option>
2188 </select></td>
2189 </tr>
2190 <tr class="backup-service-description">
2191 <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>
2192
2193 </tr>
2194
2195 <!-- Amazon S3 -->
2196 <tr class="s3" <?php echo $s3_display?>>
2197 <td></td>
2198 <td><em>Amazon S3 is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your blog is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.</em></td>
2199 </tr>
2200 <tr class="s3" <?php echo $s3_display?>>
2201 <th>S3 access key:</th>
2202 <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_login" value="<?php echo get_option('updraft_s3_login') ?>" /></td>
2203 </tr>
2204 <tr class="s3" <?php echo $s3_display?>>
2205 <th>S3 secret key:</th>
2206 <td><input type="text" autocomplete="off" style="width:292px" name="updraft_s3_pass" value="<?php echo get_option('updraft_s3_pass'); ?>" /></td>
2207 </tr>
2208 <tr class="s3" <?php echo $s3_display?>>
2209 <th>S3 location:</th>
2210 <td>s3://<input type="text" style="width:292px" name="updraft_s3_remote_path" value="<?php echo get_option('updraft_s3_remote_path'); ?>" /></td>
2211 </tr>
2212 <tr class="s3" <?php echo $s3_display?>>
2213 <th></th>
2214 <td><p>Get your access key and secret key from your AWS page, then pick a (globally unique) bucket name (letters and numbers) (and optionally a path) to use for storage. This bucket will be created for you if it does not already exist.</p></td>
2215 </tr>
2216
2217 <!-- Google Drive -->
2218
2219 <tr class="googledrive" <?php echo $googledrive_display?>>
2220 <th>Google Drive:</th>
2221 <td>
2222 <p><a href="http://david.dw-perspective.org.uk/da/index.php/computer-resources/updraftplus-googledrive-authorisation/"><strong>For longer help, including screenshots, follow this link. The description below is sufficient for more expert users.</strong></a></p>
2223 <p><a href="https://code.google.com/apis/console/">Follow this link to your Google API Console</a>, and there create a Client ID in the API Access section. Select 'Web Application' as the application type.</p><p>You must add <kbd><?php echo admin_url('options-general.php?page=updraftplus&action=auth'); ?></kbd> as the authorised redirect URI when asked.
2224
2225 <?php
2226 if (!class_exists('SimpleXMLElement')) { echo " <b>WARNING:</b> You do not have the SimpleXMLElement installed. Google Drive backups will <b>not</b> work until you do."; }
2227 ?>
2228 </p>
2229 </td>
2230 </tr>
2231
2232 <tr class="googledrive" <?php echo $googledrive_display?>>
2233 <th>Google Drive Client ID:</th>
2234 <td><input type="text" autocomplete="off" style="width:332px" name="updraft_googledrive_clientid" value="<?php echo get_option('updraft_googledrive_clientid') ?>" /></td>
2235 </tr>
2236 <tr class="googledrive" <?php echo $googledrive_display?>>
2237 <th>Google Drive Client Secret:</th>
2238 <td><input type="text" style="width:332px" name="updraft_googledrive_secret" value="<?php echo get_option('updraft_googledrive_secret'); ?>" /></td>
2239 </tr>
2240 <tr class="googledrive" <?php echo $googledrive_display?>>
2241 <th>Google Drive Folder ID:</th>
2242 <td><input type="text" style="width:332px" name="updraft_googledrive_remotepath" value="<?php echo get_option('updraft_googledrive_remotepath'); ?>" /> <em>(To get a folder's ID navigate to that folder in Google Drive in your web browser and copy the ID from your browser's address bar. It is the part that comes after <kbd>#folders/.</kbd> Leave empty to use your root folder)</em></td>
2243 </tr>
2244 <tr class="googledrive" <?php echo $googledrive_display?>>
2245 <th>Authenticate with Google:</th>
2246 <td><p><?php if (get_option('updraft_googledrive_token','xyz') != 'xyz') echo "<strong>(You appear to be already authenticated).</strong>"; ?> <a href="?page=updraftplus&action=auth&updraftplus_googleauth=doit"><strong>After</strong> you have saved your settings (by clicking &quot;Save Changes&quot; below), then come back here once and click this link to complete authentication with Google.</a>
2247
2248 </p>
2249 </td>
2250 </tr>
2251
2252 <tr class="ftp" <?php echo $ftp_display?>>
2253 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Server:</a></th>
2254 <td><input type="text" style="width:260px" name="updraft_server_address" value="<?php echo get_option('updraft_server_address'); ?>" /></td>
2255 </tr>
2256 <tr class="ftp" <?php echo $ftp_display?>>
2257 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Login:</a></th>
2258 <td><input type="text" autocomplete="off" name="updraft_ftp_login" value="<?php echo get_option('updraft_ftp_login') ?>" /></td>
2259 </tr>
2260 <tr class="ftp" <?php echo $ftp_display?>>
2261 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">FTP Password:</a></th>
2262 <td><input type="text" autocomplete="off" style="width:260px" name="updraft_ftp_pass" value="<?php echo get_option('updraft_ftp_pass'); ?>" /></td>
2263 </tr>
2264 <tr class="ftp" <?php echo $ftp_display?>>
2265 <th><a href="#" title="Click for help!" onclick="jQuery('.ftp-description').toggle();return false;">Remote Path:</a></th>
2266 <td><input type="text" style="width:260px" name="updraft_ftp_remote_path" value="<?php echo get_option('updraft_ftp_remote_path'); ?>" /></td>
2267 </tr>
2268 <tr class="ftp-description" style="display:none">
2269 <td colspan="2">An FTP remote path will look like '/home/backup/some/folder'</td>
2270 </tr>
2271 </table>
2272 <table class="form-table" style="width:850px;">
2273 <tr><td colspan="2"><h2>Advanced / Debugging Settings</h2></td></tr>
2274 <tr>
2275 <th>Backup Directory:</th>
2276 <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo $updraft_dir ?>" /></td>
2277 </tr>
2278 <tr>
2279 <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>
2280 </tr>
2281 <tr>
2282 <th>Debug mode:</th>
2283 <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>
2284 </tr>
2285 <tr>
2286 <td>
2287 <input type="hidden" name="action" value="update" />
2288 <input type="submit" class="button-primary" value="Save Changes" />
2289 </td>
2290 </tr>
2291 </table>
2292 </form>
2293 <?php
2294 if(get_option('updraft_debug_mode')) {
2295 ?>
2296 <div style="padding-top: 40px;">
2297 <hr>
2298 <h3>Debug Information</h3>
2299 <?php
2300 $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
2301 $memory_usage = memory_get_usage(true)/1024/1024;
2302 echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
2303 echo 'Current memory usage: '.$memory_usage.' MB<br/>';
2304 echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
2305 ?>
2306 <form method="post" action="">
2307 <input type="hidden" name="action" value="updraft_backup_debug_all" />
2308 <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>
2309 </form>
2310 <form method="post" action="">
2311 <input type="hidden" name="action" value="updraft_backup_debug_db" />
2312 <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>
2313 </form>
2314 </div>
2315 <?php } ?>
2316
2317 <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>
2318
2319
2320 <script type="text/javascript">
2321 jQuery(document).ready(function() {
2322 jQuery('#updraft-service').change(function() {
2323 switch(jQuery(this).val()) {
2324 case 'none':
2325 jQuery('.deletelocal,.s3,.ftp,.googledrive,.s3-description,.ftp-description').fadeOut()
2326 jQuery('.email,.email-complete').fadeIn()
2327 break;
2328 case 's3':
2329 jQuery('.ftp,.ftp-description,.googledrive').fadeOut()
2330 jQuery('.s3,.deletelocal,.email,.email-complete').fadeIn()
2331 break;
2332 case 'googledrive':
2333 jQuery('.ftp,.ftp-description,.s3').fadeOut()
2334 jQuery('.googledrive,.deletelocal,.googledrive,.email,.email-complete').fadeIn()
2335 break;
2336 case 'ftp':
2337 jQuery('.googledrive,.s3,.s3-description').fadeOut()
2338 jQuery('.ftp,.deletelocal,.email,.email-complete').fadeIn()
2339 break;
2340 case 'email':
2341 jQuery('.s3,.ftp,.s3-description,.googledrive,.ftp-description,.email-complete').fadeOut()
2342 jQuery('.email,.deletelocal').fadeIn()
2343 break;
2344 }
2345 })
2346 })
2347 jQuery(window).load(function() {
2348 //this is for hiding the restore progress at the top after it is done
2349 setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
2350 jQuery('#updraft-restore-progress-toggle').click(function() {
2351 jQuery('#updraft-restore-progress').toggle(500)
2352 })
2353 })
2354 </script>
2355 <?php
2356 }
2357
2358 /*array2json provided by bin-co.com under BSD license*/
2359 function array2json($arr) {
2360 if(function_exists('json_encode')) return stripslashes(json_encode($arr)); //Latest versions of PHP already have this functionality.
2361 $parts = array();
2362 $is_list = false;
2363
2364 //Find out if the given array is a numerical array
2365 $keys = array_keys($arr);
2366 $max_length = count($arr)-1;
2367 if(($keys[0] == 0) and ($keys[$max_length] == $max_length)) {//See if the first key is 0 and last key is length - 1
2368 $is_list = true;
2369 for($i=0; $i<count($keys); $i++) { //See if each key correspondes to its position
2370 if($i != $keys[$i]) { //A key fails at position check.
2371 $is_list = false; //It is an associative array.
2372 break;
2373 }
2374 }
2375 }
2376
2377 foreach($arr as $key=>$value) {
2378 if(is_array($value)) { //Custom handling for arrays
2379 if($is_list) $parts[] = $this->array2json($value); /* :RECURSION: */
2380 else $parts[] = '"' . $key . '":' . $this->array2json($value); /* :RECURSION: */
2381 } else {
2382 $str = '';
2383 if(!$is_list) $str = '"' . $key . '":';
2384
2385 //Custom handling for multiple data types
2386 if(is_numeric($value)) $str .= $value; //Numbers
2387 elseif($value === false) $str .= 'false'; //The booleans
2388 elseif($value === true) $str .= 'true';
2389 else $str .= '"' . addslashes($value) . '"'; //All other things
2390 // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
2391
2392 $parts[] = $str;
2393 }
2394 }
2395 $json = implode(',',$parts);
2396
2397 if($is_list) return '[' . $json . ']';//Return numerical JSON
2398 return '{' . $json . '}';//Return associative JSON
2399 }
2400
2401 function show_admin_warning($message) {
2402 echo '<div id="updraftmessage" class="updated fade">';
2403 echo "<p>$message</p></div>";
2404 }
2405 function show_admin_warning_accessible() {
2406 $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.");
2407 }
2408 function show_admin_warning_googledrive() {
2409 $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="?page=updraftplus&action=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>');
2410 }
2411 function show_admin_warning_accessible_unknownresult() {
2412 $this->show_admin_warning("UpdraftPlus tried to check if the backup directory is accessible via web, but the result was unknown.");
2413 }
2414
2415
2416 }
2417
2418
2419 ?>
2420