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

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