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

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

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