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