PluginProbe
InfiniteWP Client / trunk
InfiniteWP Client vtrunk
1.13.10 1.13.7 trunk 0.1.4 0.1.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.11.0 1.11.1 1.12.1 1.12.3 All 92 releases
iwp-client / backup / backup.core.class.php

backup.core.class.php in InfiniteWP Client trunk, at backup/backup.core.class.php

5,145 lines 218.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined('ABSPATH') )
4 die();
5
6 class IWP_MMB_Backup_Core {
7
8 public $errors = array();
9 public $nonce;
10 public $logfile_name = "";
11 public $logfile_handle = false;
12 public $backup_time;
13 public $job_time_ms;
14 public $version;
15 public $opened_log_time;
16 private $iwp_backup_dir;
17 public $blog_name;
18
19 private $jobdata;
20
21 public $something_useful_happened = false;
22
23 // Used to schedule resumption attempts beyond the tenth, if needed
24 public $current_resumption;
25 public $newresumption_scheduled = false;
26
27 public $cpanel_quota_readable = false;
28
29 public $error_reporting_stop_when_logged = false;
30
31 private $combine_jobs_around;
32 public $no_deprecation_warnings;
33 public $backup_is_already_complete;
34 public $last_successful_resumption;
35 public $no_checkin_last_time;
36 public $error_count_before_cloud_backup;
37 public $semaphore;
38 public $backup_dir;
39 public $backups_instance_ids;
40
41 public function __construct() {
42
43 # The two actions which we schedule upon
44 $this->version = IWP_MMB_CLIENT_VERSION;
45 add_action('IWP_backup', array($this, 'backup_files'));
46 add_action('IWP_backup_database', array($this, 'backup_database'));
47 add_filter('IWP_backupable_file_entities_final', array($this, 'backupable_file_entities_final'), 10, 3);
48
49
50 # The three actions that can be called from "Backup Now"
51 add_action('IWP_backupnow_backup', array($this, 'backupnow_files'));
52 add_action('IWP_backupnow_backup_database', array($this, 'backupnow_database'));
53 add_action('IWP_backupnow_backup_all', array($this, 'backup_all'));
54 add_action('IWP_backup_resume', array($this, 'backup_resume'), 10, 3);
55 # backup_all as an action is legacy (Oct 2013) - there may be some people who wrote cron scripts to use it
56 add_action('IWP_backup_all', array($this, 'backup_all'));
57
58 add_filter('schedule_event', array($this, 'schedule_event'));
59 add_filter('IWP_dropbox_modpath', array($this, 'dropbox_modpath'),10, 2);
60
61 }
62
63 // Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on
64 private function no_deprecation_warnings_on_php7() {
65 // PHP_MAJOR_VERSION is defined in PHP 5.2.7+
66 // We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to).
67 if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) {
68 $old_level = error_reporting();
69 $new_level = $old_level & ~E_DEPRECATED;
70 if ($old_level != $new_level) error_reporting($new_level);
71 $this->no_deprecation_warnings = true;
72 }
73 }
74
75 /**
76 * This converts array-style options (i.e. late 2013-onwards) to
77 * 2017-style multi-array-style options.
78 *
79 * N.B. Don't actually call this on any particular method's options
80 * until the functions which read the options can cope!
81 *
82 * N.B. Until the UI is changed (DOM changed), saving settings will
83 * revert to the previous format. But that does not break anything.
84 *
85 * Don't call for settings that aren't array-style. You may lose
86 * the settings if you do.
87 *
88 * It is safe to call this if you are not sure if the options are
89 * already updated.
90 *
91 * @param String $method - the method identifier
92 *
93 * @returns Array|WP_Error - returns the new options, or a WP_Error if it failed
94 */
95 public function update_remote_storage_options_format($method) {
96 // Prevent recursion
97 static $already_active = false;
98
99 if ($already_active) return new WP_Error('recursion', 'IWP_MMB_Backup_Core::update_remote_storage_options_format() was called in a loop. This is usually caused by an options filter failing to correctly process a "recursion" error code');
100
101 if (!file_exists($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$method.'.php')) return new WP_Error('no_such_method', 'Remote storage method not found', $method);
102
103 // Sanity/inconsistency check
104 $settings_keys = $this->get_settings_keys();
105
106 $method_key = 'IWP_'.$method;
107
108 if (!in_array($method_key, $settings_keys)) return new WP_Error('no_such_setting', 'Setting not found for this method', $method);
109
110 $current_setting = IWP_MMB_Backup_Options::get_iwp_backup_option($method_key, array());
111
112 if (!is_array($current_setting) && false !== $current_setting) return new WP_Error('format_unrecognised', 'Settings format not recognised', array('method' => $method, 'current_setting' => $current_setting));
113
114 // Already converted?
115 if (isset($current_setting['version'])) return $current_setting;
116
117 $new_setting = $this->wrap_remote_storage_options($current_setting);
118
119 $already_active = true;
120 $updated = IWP_MMB_Backup_Options::update_iwp_backup_option($method_key, $new_setting);
121 $already_active = false;
122
123 if ($updated) {
124 return $new_setting;
125 } else {
126 return new WP_Error('save_failed', 'Saving the options in the new format failed', array('method' => $method, 'current_setting' => $new_setting));
127 }
128
129 }
130
131 /**
132 * This method will update the old style remote storage options to the new style (Apr 2017) if the user has imported a old style version of settings
133 *
134 * @param Array $options - The remote storage options settings array
135 * @return Array - The updated remote storage options settings array
136 */
137 public function wrap_remote_storage_options($options) {
138 // Already converted?
139 if (isset($options['version'])) return $options;
140
141 $options['version'] = 1;
142
143 return $options;
144 }
145
146 // Returns the number of bytes free, if it can be detected; otherwise, false
147 // Presently, we only detect CPanel. If you know of others, then feel free to contribute!
148 public function get_hosting_disk_quota_free() {
149 if (!@is_dir('/usr/local/cpanel') || $this->detect_safe_mode() || !function_exists('popen') || (!@is_executable('/usr/local/bin/perl') && !@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) || (defined('IWP_SKIP_CPANEL_QUOTA_CHECK') && IWP_SKIP_CPANEL_QUOTA_CHECK)) return false;
150
151 $perl = (@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ? '/usr/local/cpanel/3rdparty/bin/perl' : '/usr/local/bin/perl';
152
153 $exec = "IWPKEY=IWP $perl ".$GLOBALS['iwp_mmb_plugin_dir']."/lib/get-cpanel-quota-usage.pl";
154
155 $handle = @popen($exec, 'r');
156 if (!is_resource($handle)) return false;
157
158 $found = false;
159 $lines = 0;
160 while (false === $found && !feof($handle) && $lines<100) {
161 $lines++;
162 $w = fgets($handle);
163 # Used, limit, remain
164 if (preg_match('/RESULT: (\d+) (\d+) (\d+) /', $w, $matches)) { $found = true; }
165 }
166 $ret = pclose($handle);
167 if (false === $found ||$ret != 0) return false;
168
169 if ((int)$matches[2]<100 || ($matches[1] + $matches[3] != $matches[2])) return false;
170
171 $this->cpanel_quota_readable = true;
172
173 return $matches;
174 }
175
176 public function last_modified_log() {
177 $iwp_backup_dir = $this->backups_dir_location();
178
179 $log_file = '';
180 $mod_time = false;
181 $nonce = '';
182
183 if ($handle = @opendir($iwp_backup_dir)) {
184 while (false !== ($entry = readdir($handle))) {
185 // The latter match is for files created internally by zipArchive::addFile
186 if (preg_match('/^log\.([a-z0-9]+)\.txt$/i', $entry, $matches)) {
187 $mtime = filemtime($iwp_backup_dir.'/'.$entry);
188 if ($mtime > $mod_time) {
189 $mod_time = $mtime;
190 $log_file = $iwp_backup_dir.'/'.$entry;
191 $nonce = $matches[1];
192 }
193 }
194 }
195 @closedir($handle);
196 }
197
198 return array($mod_time, $log_file, $nonce);
199 }
200
201 public function register_wp_http_option_hooks($register = true) {
202 if ($register) {
203 add_filter('http_request_args', array($this, 'modify_http_options'));
204 add_action('http_api_curl', array($this, 'http_api_curl'));
205 } else {
206 remove_filter('http_request_args', array($this, 'modify_http_options'));
207 remove_action('http_api_curl', array($this, 'http_api_curl'));
208 }
209 }
210
211 public function http_api_curl($handle) {
212 if (defined('IWP_IPV4_ONLY') && IWP_IPV4_ONLY) {
213 curl_setopt($handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
214 }
215 return $handle;
216 }
217
218 public function modify_http_options($opts) {
219
220 if (!is_array($opts)) return $opts;
221
222 if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_useservercerts')) $opts['sslcertificates'] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
223
224 $opts['sslverify'] = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_ssl_disableverify') ? false : true;
225
226 return $opts;
227
228 }
229
230 public function get_table_prefix($allow_override = false) {
231 global $wpdb;
232 if (is_multisite() && !defined('MULTISITE')) {
233 # In this case (which should only be possible on installs upgraded from pre WP 3.0 WPMU), $wpdb->get_blog_prefix() cannot be made to return the right thing. $wpdb->base_prefix is not explicitly marked as public, so we prefer to use get_blog_prefix if we can, for future compatibility.
234 $prefix = $wpdb->base_prefix;
235 } else {
236 $prefix = $wpdb->get_blog_prefix(0);
237 }
238 return ($allow_override) ? apply_filters('IWP_get_table_prefix', $prefix) : $prefix;
239 }
240
241 public function siteid() {
242 $sid = get_site_option('IWP-addons_siteid');
243 if (!is_string($sid) || empty($sid)) {
244 $sid = md5(rand().microtime(true).home_url());
245 update_site_option('IWP-addons_siteid', $sid);
246 }
247 return $sid;
248 }
249
250 public function plugins_loaded() {
251
252 // The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us
253 if ((defined('DOING_CRON') && DOING_CRON) || (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['subaction']) && 'backupnow' == $_REQUEST['subaction']) ) {
254 remove_action('init', 'ganalyticator_stats_init');
255 // Appointments+ does the same; but provides a cleaner way to disable it
256 @define('APP_GCAL_DISABLE', true);
257 }
258
259 }
260
261 // Cleans up temporary files found in the InfinteWP directory (and some in the site root - pclzip)
262 // Always cleans up temporary files over 12 hours old.
263 // With parameters, also cleans up those.
264 // Also cleans out old job data older than 12 hours old (immutable value)
265 // include_cachelist also looks to match any files of cached file analysis data
266 public function clean_temporary_files($match = '', $older_than = 43200, $include_cachelist = false) {
267 # Clean out old job data
268 if ($older_than > 10000) {
269 global $wpdb;
270
271 $all_jobs = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'IWP_jobdata_%'", ARRAY_A);
272 foreach ($all_jobs as $job) {
273 $val = maybe_unserialize($job['option_value']);
274 # TODO: Can simplify this after a while (now all jobs use job_time_ms) - 1 Jan 2014
275 $delete = false;
276 if (!empty($val['next_increment_start_scheduled_for'])) {
277 if (time() > $val['next_increment_start_scheduled_for'] + 86400) $delete = true;
278 } elseif (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) {
279 $delete = true;
280 } elseif (!empty($val['job_time_ms']) && time() > $val['job_time_ms'] + 86400) {
281 $delete = true;
282 } elseif (!empty($val['job_type']) && 'backup' != $val['job_type'] && empty($val['backup_time_ms']) && empty($val['job_time_ms'])) {
283 $delete = true;
284 }
285 if ($delete) delete_option($job['option_name']);
286 }
287 }
288 $iwp_backup_dir = $this->backups_dir_location();
289 $now_time=time();
290 if ($handle = opendir($iwp_backup_dir)) {
291 while (false !== ($entry = readdir($handle))) {
292 $manifest_match = preg_match("/^udmanifest$match\.json$/i", $entry);
293 // This match is for files created internally by zipArchive::addFile
294 $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $entry);
295 // zi followed by 6 characters is the pattern used by /usr/bin/zip on Linux systems. It's safe to check for, as we have nothing else that's going to match that pattern.
296 $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry);
297 $cachelist_match = ($include_cachelist) ? preg_match("/$match-cachelist-.*.tmp$/i", $entry) : false;
298 $browserlog_match = preg_match('/^log\.[0-9a-f]+-browser\.txt$/', $entry);
299 # Temporary files from the database dump process - not needed, as is caught by the catch-all
300 # $table_match = preg_match("/${match}-table-(.*)\.table(\.tmp)?\.gz$/i", $entry);
301 # The gz goes in with the txt, because we *don't* want to reap the raw .txt files
302 if ((preg_match("/$match\.(tmp|table|txt\.gz)(\.gz)?$/i", $entry) || $cachelist_match || $ziparchive_match || $binzip_match || $manifest_match || $browserlog_match) && is_file($iwp_backup_dir.'/'.$entry) && !strrpos($entry,'backup_meta')) {
303 // We delete if a parameter was specified (and either it is a ZipArchive match or an order to delete of whatever age), or if over 12 hours old
304 if ((($match || $match == '') && ($ziparchive_match || $binzip_match || $cachelist_match || $manifest_match || 0 == $older_than) && $now_time-filemtime($iwp_backup_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($iwp_backup_dir.'/'.$entry)>43200) {
305 $this->log("Deleting old temporary file: $entry");
306 @unlink($iwp_backup_dir.'/'.$entry);
307 }
308 }
309 }
310 @closedir($handle);
311 }
312
313 foreach (array(ABSPATH, ABSPATH.'wp-admin/', $iwp_backup_dir.'/') as $path) {
314 if ($handle = opendir($path)) {
315 while (false !== ($entry = readdir($handle))) {
316 // With the old pclzip temporary files, there is no need to keep them around after they're not in use - so we don't use $older_than here - just go for 15 minutes
317 if (preg_match("/^pclzip-[a-z0-9]+.tmp$/", $entry) && $now_time-filemtime($path.$entry) >= 900) {
318 $this->log("Deleting old PclZip temporary file: $entry");
319 @unlink($path.$entry);
320 }
321 }
322 @closedir($handle);
323 }
324 }
325 }
326
327 public function backup_time_nonce($nonce = false) {
328 $this->job_time_ms = microtime(true);
329 $this->backup_time = time();
330 if (false === $nonce) $nonce = substr(md5(time().rand()), 20);
331 $this->nonce = $nonce;
332 return $nonce;
333 }
334
335 public function get_wordpress_version() {
336 static $got_wp_version = false;
337 if (!$got_wp_version) {
338 global $wp_version;
339 @include(ABSPATH.WPINC.'/version.php');
340 $got_wp_version = $wp_version;
341 }
342 return $got_wp_version;
343 }
344
345 /**
346 * Opens the log file, writes a standardised header, and stores the resulting name and handle in the class variables logfile_name/logfile_handle/opened_log_time (and possibly backup_is_already_complete)
347 *
348 * @param string $nonce - Used in the log file name to distinguish it from other log files. Should be the job nonce.
349 * @returns void
350 */
351 public function logfile_open($nonce, $writeMode = false) {
352
353 $iwp_backup_dir = $this->backups_dir_location();
354 $this->logfile_name = $iwp_backup_dir."/log.$nonce.txt";
355
356 if (file_exists($this->logfile_name)) {
357 $seek_to = max((filesize($this->logfile_name) - 340), 1);
358 $handle = fopen($this->logfile_name, 'r');
359 if (is_resource($handle)) {
360 // Returns 0 on success
361 if (0 === @fseek($handle, $seek_to)) {
362 $bytes_back = filesize($this->logfile_name) - $seek_to;
363 # Return to the end of the file
364 $read_recent = fread($handle, $bytes_back);
365 # Move to end of file - ought to be redundant
366 if (false !== strpos($read_recent, ') The backup apparently succeeded') && false !== strpos($read_recent, 'and is now complete')) {
367 $this->backup_is_already_complete = true;
368 }
369 }
370 fclose($handle);
371 }
372 }
373 # code...
374 if ($writeMode === false) {
375 $this->logfile_handle = fopen($this->logfile_name, 'a');
376 }else{
377 $this->logfile_handle = fopen($this->logfile_name, 'w');
378 }
379
380 $this->opened_log_time = microtime(true);
381
382 $this->write_log_header(array($this, 'log'));
383
384 }
385
386 /**
387 * Writes a standardised header to the log file, using the specified logging function, which needs to be compatible with (or to be) InfiniteWP::log()
388 *
389 * @param callable $logging_function
390 */
391 public function write_log_header($logging_function) {
392
393 global $wpdb;
394
395 $iwp_backup_dir = $this->backups_dir_location();
396
397 call_user_func($logging_function, 'Opened log file at time: '.date('r').' on '.network_site_url());
398
399 $wp_version = $this->get_wordpress_version();
400 $mysql_version = $wpdb->db_version();
401 $safe_mode = $this->detect_safe_mode();
402
403 $memory_limit = ini_get('memory_limit');
404 $memory_usage = round(@memory_get_usage(false)/1048576, 1);
405 $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
406
407 // Attempt to raise limit to avoid false positives
408 @set_time_limit(IWP_SET_TIME_LIMIT);
409 $max_execution_time = (int)@ini_get("max_execution_time");
410
411 $logline = "InfiniteWP WordPress plugin (https://infinitewp.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".PHP_SAPI.", ".@php_uname().") MySQL: $mysql_version WPLANG: ".get_locale()." Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ".$memory_usage."M | ".$memory_usage2."M) multisite: ".(is_multisite() ? 'Y' : 'N')." openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N')." mcrypt: ".(function_exists('mcrypt_encrypt') ? 'Y' : 'N')." LANG: ".getenv('LANG')." ZipArchive::addFile: ";
412
413 // method_exists causes some faulty PHP installations to segfault, leading to support requests
414 if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
415 $logline .= 'Y';
416 } else {
417 $logline .= (class_exists('ZipArchive') && method_exists('ZipArchive', 'addFile')) ? "Y" : "N";
418 }
419
420 if (0 === $this->current_resumption) {
421 $memlim = $this->memory_check_current();
422 if ($memlim<65 && $memlim>0) {
423 $this->log(sprintf(__('The amount of memory (RAM) allowed for PHP is very low (%s Mb) - you should increase it to avoid failures due to insufficient memory (consult your web hosting company for more help)', 'InfiniteWP'), round($memlim, 1)), 'warning', 'lowram');
424 }
425 if ($max_execution_time>0 && $max_execution_time<20) {
426 call_user_func($logging_function, sprintf(__('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid backup failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', 'InfiniteWP'), $max_execution_time, 90), 'warning', 'lowmaxexecutiontime');
427 }
428
429 }
430
431 call_user_func($logging_function, $logline);
432
433 $hosting_bytes_free = $this->get_hosting_disk_quota_free();
434 if (is_array($hosting_bytes_free)) {
435 $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
436 $quota_free = ' / '.sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." MB", "$perc %");
437 if ($hosting_bytes_free[3] < 1048576*50) {
438 $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1);
439 call_user_func($logging_function, sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'InfiniteWP'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb);
440 }
441 } else {
442 $quota_free = '';
443 }
444
445 $disk_free_space = function_exists('disk_free_space') ? @disk_free_space($iwp_backup_dir) : false;
446 # == rather than === here is deliberate; support experience shows that a result of (int)0 is not reliable. i.e. 0 can be returned when the real result should be false.
447 if ($disk_free_space == false) {
448 call_user_func($logging_function, "Free space on disk containing InfiniteWP's temporary directory: Unknown".$quota_free);
449 } else {
450 call_user_func($logging_function, "Free space on disk containing InfiniteWP's temporary directory: ".round($disk_free_space/1048576, 1)." MB".$quota_free);
451 $disk_free_mb = round($disk_free_space/1048576, 1);
452 if ($disk_free_space < 50*1048576) call_user_func($logging_function, sprintf(__('Your free disk space is very low - only %s Mb remain', 'InfiniteWP'), round($disk_free_space/1048576, 1)), 'warning', 'lowdiskspace'.$disk_free_mb);
453 }
454
455 }
456
457 /* Logs the given line, adding (relative) time stamp and newline
458 Note these subtleties of log handling:
459 - Messages at level 'error' are not logged to file - it is assumed that a separate call to log() at another level will take place. This is because at level 'error', messages are translated; whereas the log file is for developers who may not know the translated language. Messages at level 'error' are for the user.
460 - Messages at level 'error' do not persist through the job (they are only saved with save_backup_history(), and never restored from there - so only the final save_backup_history() errors persist); we presume that either a) they will be cleared on the next attempt, or b) they will occur again on the final attempt (at which point they will go to the user). But...
461 - ... messages at level 'warning' persist. These are conditions that are unlikely to be cleared, not-fatal, but the user should be informed about. The $uniq_id field (which should not be numeric) can then be used for warnings that should only be logged once
462 $skip_dblog = true is suitable when there's a risk of excessive logging, and the information is not important for the user to see in the browser on the settings page
463
464 The uniq_id field is also used with PHP event detection - it is set then to 'php_event' - which is useful for anything hooking the action to detect
465 */
466
467 public function verify_free_memory($how_many_bytes_needed) {
468 // This returns in MB
469 $memory_limit = $this->memory_check_current();
470 if (!is_numeric($memory_limit)) return false;
471 $memory_limit = $memory_limit * 1048576;
472 $memory_usage = round(@memory_get_usage(false)/1048576, 1);
473 $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
474 if ($memory_limit - $memory_usage > $how_many_bytes_needed && $memory_limit - $memory_usage2 > $how_many_bytes_needed) return true;
475 return false;
476 }
477
478 /*
479 $line - the log line
480 $level - the log level: notice, warning, error. If suffixed with a hypen and a destination, then the default destination is changed too.
481 $uniq_id - (string)each of these will only be logged once
482 $skip_dblog - if true, then do not write to the database
483 */
484 public function log($line, $level = 'notice', $uniq_id = false, $skip_dblog = false) {
485
486 $destination = 'default';
487 if (preg_match('/^([a-z]+)-([a-z]+)$/', $level, $matches)) {
488 $level = $matches[1];
489 $destination = $matches[2];
490 }
491
492 if ('error' == $level || 'warning' == $level) {
493 if ('error' == $level && 0 == $this->error_count()) $this->log('An error condition has occurred for the first time during this job');
494 if ($uniq_id) {
495 $this->errors[$uniq_id] = array('level' => $level, 'message' => $line);
496 } else {
497 $this->errors[] = array('level' => $level, 'message' => $line);
498 }
499 # Errors are logged separately
500 if ('error' == $level) return;
501 # It's a warning
502 $warnings = $this->jobdata_get('warnings');
503 if (!is_array($warnings)) $warnings = array();
504 if ($uniq_id) {
505 $warnings[$uniq_id] = $line;
506 } else {
507 $warnings[] = $line;
508 }
509 $this->jobdata_set('warnings', $warnings);
510 }
511
512 if (false === ($line = apply_filters('IWP_logline', $line, $this->nonce, $level, $uniq_id, $destination))) return;
513
514 if ($this->logfile_handle) {
515 # Record log file times relative to the backup start, if possible
516 $rtime = (!empty($this->job_time_ms)) ? microtime(true)-$this->job_time_ms : microtime(true)-$this->opened_log_time;
517 fwrite($this->logfile_handle, sprintf("%08.03f", round($rtime, 3))." (".$this->current_resumption.") ".(('notice' != $level) ? '['.ucfirst($level).'] ' : '').$line."\n");
518 }
519
520 switch ($this->jobdata_get('job_type')) {
521 case 'download':
522 // Download messages are keyed on the job (since they could be running several), and type
523 // The values of the POST array were checked before
524 $findex = empty($_POST['findex']) ? 0 : $_POST['findex'];
525
526 if (!empty($_POST['timestamp']) && !empty($_POST['type'])) $this->jobdata_set('dlmessage_'.$_POST['timestamp'].'_'.$_POST['type'].'_'.$findex, $line);
527
528 break;
529 case 'restore':
530 #if ('debug' != $level) echo $line."\n";
531 break;
532 default:
533 if (!$skip_dblog && 'debug' != $level) IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_lastmessage', $line." (".date_i18n('M d H:i:s').")", false);
534 break;
535 }
536
537 if (defined('IWP_BROWSERLOG_CONSOLELOG')) print $line."\n";
538 if (defined('IWP_BROWSERLOG_BROWSERLOG')) print htmlentities($line)."<br>\n";
539 }
540
541 public function log_removewarning($uniq_id) {
542 $warnings = $this->jobdata_get('warnings');
543 if (!is_array($warnings)) $warnings=array();
544 unset($warnings[$uniq_id]);
545 $this->jobdata_set('warnings', $warnings);
546 unset($this->errors[$uniq_id]);
547 }
548
549 # For efficiency, you can also feed false or a string into this function
550 public function log_wp_error($err, $echo = false, $logerror = false) {
551 if (false === $err) return false;
552 if (is_string($err)) {
553 $this->log("Error message: $err");
554 if ($echo) $this->log(sprintf(__('Error: %s', 'InfiniteWP'), $err), 'notice-warning');
555 if ($logerror) $this->log($err, 'error');
556 return false;
557 }
558 foreach ($err->get_error_messages() as $msg) {
559 $this->log("Error message: $msg");
560 if ($echo) $this->log(sprintf(__('Error: %s', 'InfiniteWP'), $msg), 'notice-warning');
561 if ($logerror) $this->log($msg, 'error');
562 }
563 $codes = $err->get_error_codes();
564 if (is_array($codes)) {
565 foreach ($codes as $code) {
566 $data = $err->get_error_data($code);
567 if (!empty($data)) {
568 $ll = (is_string($data)) ? $data : serialize($data);
569 $this->log("Error data (".$code."): ".$ll);
570 }
571 }
572 }
573 # Returns false so that callers can return with false more efficiently if they wish
574 return false;
575 }
576
577 public function get_max_packet_size() {
578 global $wpdb;
579 $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
580 # Default to 1MB
581 $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
582 # 32MB
583 if ($mp < 33554432) {
584 $save = $wpdb->show_errors(false);
585 $req = @$wpdb->query("SET GLOBAL max_allowed_packet=33554432");
586 $wpdb->show_errors($save);
587 if (!$req) $this->log("Tried to raise max_allowed_packet from ".round($mp/1048576,1)." MB to 32 MB, but failed (".$wpdb->last_error.", ".serialize($req).")");
588 $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
589 # Default to 1MB
590 $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
591 }
592 $this->log("Max packet size: ".round($mp/1048576, 1)." MB");
593 return $mp;
594 }
595
596 # Q. Why is this abstracted into a separate function? A. To allow poedit and other parsers to pick up the need to translate strings passed to it (and not pick up all of those passed to log()).
597 # 1st argument = the line to be logged (obligatory)
598 # Further arguments = parameters for sprintf()
599 public function log_e() {
600 $args = func_get_args();
601 # Get first argument
602 $pre_line = array_shift($args);
603 # Log it whilst still in English
604 if (is_wp_error($pre_line)) {
605 $this->log_wp_error($pre_line);
606 } else {
607 // Now run (v)sprintf on it, using any remaining arguments. vsprintf = sprintf but takes an array instead of individual arguments
608 $this->log(vsprintf($pre_line, $args));
609 // This is slightly hackish, in that we have no way to use a different level or destination. In that case, the caller should instead call log() twice with different parameters, instead of using this convenience function.
610 $this->log(vsprintf(__($pre_line, 'InfiniteWP'), $args), 'notice-restore');
611 }
612 }
613
614 // This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile
615 public function record_uploaded_chunk($percent, $extra = '', $file_path = false, $log_it = true) {
616
617 // Touch the original file, which helps prevent overlapping runs
618 if ($file_path) touch($file_path);
619
620 // What this means in effect is that at least one of the files touched during the run must reach this percentage (so lapping round from 100 is OK)
621 if ($percent > 0.7 * ($this->current_resumption - max($this->jobdata_get('uploaded_lastreset'), 9))) $this->something_useful_happened();
622
623 // Log it
624 global $IWP_backup;
625 $log = (!empty($IWP_backup->current_service)) ? ucfirst($IWP_backup->current_service)." chunked upload: $percent % uploaded" : '';
626 if ($log && $log_it) $this->log($log.(($extra) ? " ($extra)" : ''));
627 // If we are on an 'overtime' resumption run, and we are still meaningfully uploading, then schedule a new resumption
628 // Our definition of meaningful is that we must maintain an overall average of at least 0.7% per run, after allowing 9 runs for everything else to get going
629 // i.e. Max 100/.7 + 9 = 150 runs = 760 minutes = 12 hrs 40, if spaced at 5 minute intervals. However, our algorithm now decreases the intervals if it can, so this should not really come into play
630 // If they get 2 minutes on each run, and the file is 1GB, then that equals 10.2MB/120s = minimum 59KB/s upload speed required
631
632 $upload_status = $this->jobdata_get('uploading_substatus');
633 if (is_array($upload_status)) {
634 $upload_status['p'] = $percent/100;
635 $this->jobdata_set('uploading_substatus', $upload_status);
636 }
637
638 }
639
640 /**
641 * Method for helping remote storage methods to upload files in chunks without needing to duplicate all the overhead
642 *
643 * @param string $file the full path to the file
644 * @param object $caller the object to call back to do the actual network API calls; needs to have a chunked_upload() method.
645 * @param string $cloudpath this is passed back to the callback function; within this function, it is used only for logging
646 * @param string $logname the prefix used on log lines. Also passed back to the callback function.
647 * @param integer $chunk_size the size, in bytes, of each upload chunk
648 * @param integer $uploaded_size how many bytes have already been uploaded. This is passed back to the callback function; within this method, it is only used for logging.
649 * @param boolean $singletons when the file, given the chunk size, would only have one chunk, should that be uploaded (true), or instead should 1 be returned (false) ?
650 */
651 public function chunked_upload($caller, $file, $cloudpath, $logname, $chunk_size, $uploaded_size, $singletons = false) {
652
653 $fullpath = $this->backups_dir_location().'/'.$file;
654 $orig_file_size = filesize($fullpath);
655 if ($uploaded_size >= $orig_file_size) return true;
656
657 $chunks = floor($orig_file_size / $chunk_size);
658 // There will be a remnant unless the file size was exactly on a chunk boundary
659 if ($orig_file_size % $chunk_size > 0) $chunks++;
660
661 $this->log("$logname upload: $file (chunks: $chunks, size: $chunk_size) -> $cloudpath ($uploaded_size)");
662
663 if (0 == $chunks) {
664 return 1;
665 } elseif ($chunks < 2 && !$singletons) {
666 return 1;
667 } else {
668
669 if (false == ($fp = @fopen($fullpath, 'rb'))) {
670 $this->log("$logname: failed to open file: $fullpath");
671 $this->log("$file: ".sprintf(__('%s Error: Failed to open local file','InfiniteWP'), $logname), 'error');
672 return false;
673 }
674
675 $errors_so_far = 0;
676 $upload_start = 0;
677 $upload_end = -1;
678 $chunk_index = 1;
679 // The file size minus one equals the byte offset of the final byte
680 $upload_end = min($chunk_size - 1, $orig_file_size - 1);
681
682 while ($upload_start < $orig_file_size) {
683
684 // Don't forget the +1; otherwise the last byte is omitted
685 $upload_size = $upload_end - $upload_start + 1;
686
687 if ($upload_start) fseek($fp, $upload_start);
688
689 /*
690 * Valid return values for $uploaded are many, as the possibilities have grown over time.
691 * This could be cleaned up; but, it works, and it's not hugely complex.
692 *
693 * WP_Error : an error occured. The only permissible codes are: reduce_chunk_size (only on the first chunk), try_again
694 * (bool)true : What was requested was done
695 * (int)1 : What was requested was done, but do not log anything
696 * (bool)false : There was an error
697 * (Object) : Properties:
698 * (bool)log: (bool) - if absent, defaults to true
699 * (int)new_chunk_size: advisory amount for the chunk size for future chunks
700 * NOT IMPLEMENTED: (int)bytes_uploaded: Actual number of bytes uploaded (needs to be positive - o/w, should return an error instead)
701 *
702 * N.B. Consumers should consult $fp and $upload_start to get data; they should not re-calculate from $chunk_index, which is not an indicator of file position.
703 */
704 $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size);
705
706 // Try again? (Just once - added in 1.12.6 (can make more sophisticated if there is a need))
707 if (is_wp_error($uploaded) && 'try_again' == $uploaded->get_error_code()) {
708 // Arbitrary wait
709 sleep(3);
710 $this->log("Re-trying after wait (to allow apparent inconsistency to clear)");
711 $uploaded = $caller->chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $orig_file_size);
712 }
713
714 // This is the only other supported case of a WP_Error - otherwise, a boolean must be returned
715 // Note that this is only allowed on the first chunk. The caller is responsible to remember its chunk size if it uses this facility.
716 if (1 == $chunk_index && is_wp_error($uploaded) && 'reduce_chunk_size' == $uploaded->get_error_code() && false != ($new_chunk_size = $uploaded->get_error_data()) && is_numeric($new_chunk_size)) {
717 $this->log("Re-trying with new chunk size: ".$new_chunk_size);
718 return $this->chunked_upload($caller, $file, $cloudpath, $logname, $new_chunk_size, $uploaded_size, $singletons);
719 }
720
721 $uploaded_amount = $chunk_size;
722
723 /*
724 // Not using this approach for now. Instead, going to allow the consumers to increase the next chunk size
725 if (is_object($uploaded) && isset($uploaded->bytes_uploaded)) {
726 if (!$uploaded->bytes_uploaded) {
727 $uploaded = false;
728 } else {
729 $uploaded_amount = $uploaded->bytes_uploaded;
730 $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1;
731 }
732 }
733 */
734 if (is_object($uploaded) && isset($uploaded->new_chunk_size)) {
735 if ($uploaded->new_chunk_size >= 1048576) $new_chunk_size = $uploaded->new_chunk_size;
736 $uploaded = (!isset($uploaded->log) || $uploaded->log) ? true : 1;
737 }
738
739 if ($uploaded) {
740 $perc = round(100*($upload_end + 1)/max($orig_file_size, 1), 1);
741 // Consumers use a return value of (int)1 (rather than (bool)true) to suppress logging
742 $log_it = ($uploaded === 1) ? false : true;
743 $this->record_uploaded_chunk($perc, $chunk_index, $fullpath, $log_it);
744
745 // $uploaded_bytes = $upload_end + 1;
746
747 } else {
748 $errors_so_far++;
749 if ($errors_so_far >= 3) { @fclose($fp); return false; }
750 }
751
752 $chunk_index++;
753 $upload_start = $upload_end + 1;
754 $upload_end += isset($new_chunk_size) ? $uploaded_amount + $new_chunk_size - $chunk_size : $uploaded_amount;
755 $upload_end = min($upload_end, $orig_file_size - 1);
756
757 }
758
759 @fclose($fp);
760
761 if ($errors_so_far) return false;
762
763 // All chunks are uploaded - now combine the chunks
764 $ret = true;
765 if (method_exists($caller, 'chunked_upload_finish')) {
766 $ret = $caller->chunked_upload_finish($file);
767 if (!$ret) {
768 $this->log("$logname - failed to re-assemble chunks ");
769 $this->log(sprintf(__('%s error - failed to re-assemble chunks', 'InfiniteWP'), $logname), 'error');
770 }
771 }
772 if ($ret) {
773 $this->log("$logname upload: success");
774 # calls this itself
775 if (!is_a($caller, 'IWP_MMB_Addons_RemoteStorage_sftp')) $this->uploaded_file($file);
776 }
777
778 return $ret;
779
780 }
781 }
782
783 /**
784 * Provides a convenience function allowing remote storage methods to download a file in chunks, without duplicated overhead.
785 *
786 * @param string $file - The basename of the file being downloaded
787 * @param object $method - This remote storage method object needs to have a chunked_download() method to call back
788 * @param integer $remote_size - The size, in bytes, of the object being downloaded
789 * @param boolean $manually_break_up - Whether to break the download into multiple network operations (rather than just issuing a GET with a range beginning at the end of the already-downloaded data, and carrying on until it times out)
790 * @param * $passback - A value to pass back to the callback function
791 * @param integer $chunk_size - Break up the download into chunks of this number of bytes. Should be set if and only if $manually_break_up is true.
792 */
793 public function chunked_download($file, $method, $remote_size, $manually_break_up = false, $passback = null, $chunk_size = 1048576) {
794
795 try {
796
797 $fullpath = $this->backups_dir_location().'/'.$file;
798 $start_offset = file_exists($fullpath) ? filesize($fullpath) : 0;
799
800 if ($start_offset >= $remote_size) {
801 $this->log("File is already completely downloaded ($start_offset/$remote_size)");
802 return true;
803 }
804
805 // Some more remains to download - so let's do it
806 // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php
807 if (!($fh = fopen($fullpath, 'c'))) {
808 $this->log("Error opening local file: $fullpath");
809 $this->log($file.": ".__("Error",'InfiniteWP').": ".__('Error opening local file: Failed to download','InfiniteWP'), 'error');
810 return false;
811 }
812
813 $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size ) : $remote_size;
814
815 # This only affects logging
816 $expected_bytes_delivered_so_far = true;
817
818 while ($start_offset < $remote_size) {
819 if($this->restore_loop_break()){
820 fclose($fh);
821 return 'partial';
822 }
823 $headers = array();
824 // If resuming, then move to the end of the file
825
826 $requested_bytes = $last_byte-$start_offset;
827
828 if ($expected_bytes_delivered_so_far) {
829 $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next $requested_bytes bytes");
830 } else {
831 $this->log("$file: local file is status: $start_offset/$remote_size bytes; requesting next chunk (".$start_offset."-)");
832 }
833
834 if ($start_offset > 0 || $last_byte<$remote_size) {
835 fseek($fh, $start_offset);
836 // N.B. Don't alter this format without checking what relies upon it
837 $last_byte_start = $last_byte - 1;
838 $headers['Range'] = "bytes=$start_offset-$last_byte_start";
839 }
840
841 /*
842 * The most common method is for the remote storage module to return a string with the results in it. In that case, the final $fh parameter is unused. However, since not all SDKs have that option conveniently, it is also possible to use the file handle and write directly to that; in that case, the method can either return the number of bytes written, or (boolean)true to infer it from the new file *pointer*.
843 * The method is free to write/return as much data as it pleases.
844 */
845 $ret = $method->chunked_download($file, $headers, $passback, $fh);
846 if (true === $ret) {
847 clearstatcache();
848 // Some SDKs (including AWS/S3) close the resource
849 // N.B. We use ftell(), which precludes us from using open in append-only ('a') mode - see https://php.net/manual/en/function.fopen.php
850 if (is_resource($fh)) {
851 $ret = ftell($fh);
852 } else {
853 $ret = filesize($fullpath);
854 // fseek returns - on success
855 if (false == ($fh = fopen($fullpath, 'c')) || 0 !== fseek($fh, $ret)) {
856 $this->log("Error opening local file: $fullpath");
857 $this->log($file.": ".__("Error",'InfiniteWP').": ".__('Error opening local file: Failed to download','InfiniteWP'), 'error');
858 return false;
859 }
860 }
861 if (is_integer($ret)) $ret -= $start_offset;
862 }
863
864 // Note that this covers a false code returned either by chunked_download() or by ftell.
865 if (false === $ret) return false;
866
867 $returned_bytes = is_integer($ret) ? $ret : strlen($ret);
868
869 if ($returned_bytes > $requested_bytes || $returned_bytes < $requested_bytes - 1) $expected_bytes_delivered_so_far = false;
870
871 if (!is_integer($ret) && !fwrite($fh, $ret)) throw new Exception('Write failure (start offset: '.$start_offset.', bytes: '.strlen($ret).'; requested: '.$requested_bytes.')');
872
873 clearstatcache();
874 $start_offset = ftell($fh);
875 $last_byte = ($manually_break_up) ? min($remote_size, $start_offset + $chunk_size) : $remote_size;
876
877 }
878
879 } catch(Exception $e) {
880 $this->log('Error ('.get_class($e).') - failed to download the file ('.$e->getCode().', '.$e->getMessage().')');
881 $this->log("$file: ".__('Error - failed to download the file', 'InfiniteWP').' ('.$e->getCode().', '.$e->getMessage().')' ,'error');
882 return false;
883 }
884
885 fclose($fh);
886
887 return true;
888 }
889
890 /**
891 * This will decrypt an encryped db file
892 * @param string $fullpath This is the full path to the encrypted file location
893 * @param string $key This is the key (satling) to be used when decrypting
894 * @param boolean $to_temporary_file Use if the resulting file is not intended to be kept
895 * @return array This bring back an array of full decrypted path
896 */
897 public function decrypt($fullpath, $key, $to_temporary_file = false) {
898 $this->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
899 if (defined('IWP_DECRYPTION_ENGINE')) {
900 if ('openssl' == IWP_DECRYPTION_ENGINE) {
901 $rijndael->setPreferredEngine(CRYPT_ENGINE_OPENSSL);
902 } elseif ('mcrypt' == IWP_DECRYPTION_ENGINE) {
903 $rijndael->setPreferredEngine(CRYPT_ENGINE_MCRYPT);
904 } elseif ('internal' == IWP_DECRYPTION_ENGINE) {
905 $rijndael->setPreferredEngine(CRYPT_ENGINE_INTERNAL);
906 }
907 }
908
909 //open file to read
910 if (false === ($file_handle = fopen($fullpath, 'rb'))) return false;
911
912 $decrypted_path = dirname($fullpath).'/decrypt_'.basename($fullpath).'.tmp';
913 //open new file from new path
914 if (false === ($decrypted_handle = fopen($decrypted_path, 'wb+'))) return false;
915
916 //setup encryption
917 $rijndael = new Crypt_Rijndael();
918 $rijndael->setKey($key);
919 $rijndael->disablePadding();
920 $rijndael->enableContinuousBuffer();
921
922 $file_size = filesize($fullpath);
923 $bytes_decrypted = 0;
924 $buffer_size = defined('IWP_CRYPT_BUFFER_SIZE') ? IWP_CRYPT_BUFFER_SIZE : 2097152;
925
926 //loop around the file
927 while ($bytes_decrypted < $file_size) {
928 //read buffer sized amount from file
929 if (false === ($file_part = fread($file_handle, $buffer_size))) return false;
930 //check to ensure padding is needed before decryption
931 $length = strlen($file_part);
932 if ($length % 16 != 0) {
933 $pad = 16 - ($length % 16);
934 $file_part = str_pad($file_part, $length + $pad, chr($pad));
935 // $file_part = str_pad($file_part, $length + $pad, chr(0));
936 }
937
938 $decrypted_data = $rijndael->decrypt($file_part);
939
940 $is_last_block = ($bytes_decrypted + strlen($decrypted_data) >= $file_size);
941
942 $write_bytes = min($file_size - $bytes_decrypted, strlen($decrypted_data));
943 if ($is_last_block) {
944 $is_padding = false;
945 $last_byte = ord(substr($decrypted_data, -1, 1));
946 if ($last_byte < 16) {
947 $is_padding = true;
948 for ($j = 1 ; $j<=$last_byte; $j++) {
949 if (substr($decrypted_data, -$j, 1) != chr($last_byte)) $is_padding = false;
950 }
951 }
952 if ($is_padding) {
953 $write_bytes -= $last_byte;
954 }
955 }
956
957 if (false === fwrite($decrypted_handle, $decrypted_data, $write_bytes)) return false;
958 $bytes_decrypted += $buffer_size;
959 }
960
961 //close the main file handle
962 fclose($decrypted_handle);
963 //close original file
964 fclose($file_handle);
965
966 //remove the crypt extension from the end as this causes issues when opening
967 $fullpath_new = preg_replace('/\.crypt$/', '', $fullpath, 1);
968 // //need to replace original file with tmp file
969
970 $fullpath_basename = basename($fullpath_new);
971
972 if ($to_temporary_file) {
973 return array(
974 'fullpath' => $decrypted_path,
975 'basename' => $fullpath_basename
976 );
977 }
978
979 if (false === rename($decrypted_path, $fullpath_new)) return false;
980
981 //need to send back the new decrypted path
982 $decrypt_return = array(
983 'fullpath' => $fullpath_new,
984 'basename' => $fullpath_basename
985 );
986
987 return $decrypt_return;
988 }
989
990 public function detect_safe_mode() {
991 return (@ini_get('safe_mode') && strtolower(@ini_get('safe_mode')) != "off") ? 1 : 0;
992 }
993
994 public function find_working_sqldump($logit = true, $cacheit = true) {
995
996 // The hosting provider may have explicitly disabled the popen or proc_open functions
997 if ($this->detect_safe_mode() || !function_exists('popen') || !function_exists('escapeshellarg')) {
998 if ($cacheit) $this->jobdata_set('binsqldump', false);
999 return false;
1000 }
1001 $existing = $this->jobdata_get('binsqldump', null);
1002 # Theoretically, we could have moved machines, due to a migration
1003 if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;
1004
1005 $iwp_backup_dir = $this->backups_dir_location();
1006 global $wpdb;
1007 $table_name = $wpdb->get_blog_prefix().'options';
1008 $tmp_file = md5(time().rand()).".sqltest.tmp";
1009 $pfile = md5(time().rand()).'.tmp';
1010 file_put_contents($iwp_backup_dir.'/'.$pfile, "[mysqldump]\npassword=".DB_PASSWORD."\n");
1011
1012 $result = false;
1013 foreach (explode(',', IWP_MYSQLDUMP_EXECUTABLE) as $potsql) {
1014
1015 if (!@is_executable($potsql)) continue;
1016
1017 if ($logit) $this->log("Testing: $potsql");
1018
1019 if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
1020 $exec = "cd ".escapeshellarg(str_replace('/', '\\', $iwp_backup_dir))." & ";
1021 $siteurl = "'siteurl'";
1022 if (false !== strpos($potsql, ' ')) $potsql = '"'.$potsql.'"';
1023 } else {
1024 $exec = "cd ".escapeshellarg($iwp_backup_dir)."; ";
1025 $siteurl = "\\'siteurl\\'";
1026 if (false !== strpos($potsql, ' ')) $potsql = "'$potsql'";
1027 }
1028
1029 $exec .= "$potsql --defaults-file=$pfile --max_allowed_packet=1M --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --where=option_name=$siteurl --user=".escapeshellarg(DB_USER)." --host=".escapeshellarg(DB_HOST)." ".DB_NAME." ".escapeshellarg($table_name)."";
1030
1031 $handle = popen($exec, "r");
1032 if ($handle) {
1033 if (!feof($handle)) {
1034 $output = fread($handle, 8192);
1035 if ($output && $logit) {
1036 $log_output = (strlen($output) > 512) ? substr($output, 0, 512).' (truncated - '.strlen($output).' bytes total)' : $output;
1037 $this->log("Output: ".str_replace("\n", '\\n', trim($log_output)));
1038 }
1039 } else {
1040 $output = '';
1041 }
1042 $ret = pclose($handle);
1043 if ($ret !=0) {
1044 if ($logit) {
1045 $this->log("Binary mysqldump: error (code: $ret)");
1046 }
1047 } else {
1048 // $dumped = file_get_contents($iwp_backup_dir.'/'.$tmp_file, false, null, 0, 4096);
1049 if (stripos($output, 'insert into') !== false) {
1050 if ($logit) $this->log("Working binary mysqldump found: $potsql");
1051 $result = $potsql;
1052 break;
1053 }
1054 }
1055 } else {
1056 if ($logit) $this->log("Error: popen failed");
1057 }
1058 }
1059
1060 @unlink($iwp_backup_dir.'/'.$pfile);
1061 @unlink($iwp_backup_dir.'/'.$tmp_file);
1062
1063 if ($cacheit) $this->jobdata_set('binsqldump', $result);
1064
1065 return $result;
1066 }
1067
1068 // We require -@ and -u -r to work - which is the usual Linux binzip
1069 public function find_working_bin_zip($logit = true, $cacheit = true) {
1070 if ($this->detect_safe_mode()) return false;
1071 // The hosting provider may have explicitly disabled the popen or proc_open functions
1072 if (!function_exists('popen') || !function_exists('proc_open') || !function_exists('escapeshellarg')) {
1073 if ($cacheit) $this->jobdata_set('binzip', false);
1074 return false;
1075 }
1076
1077 $existing = $this->jobdata_get('binzip', null);
1078 # Theoretically, we could have moved machines, due to a migration
1079 if (null !== $existing && (!is_string($existing) || @is_executable($existing))) return $existing;
1080
1081 $iwp_backup_dir = $this->backups_dir_location();
1082 foreach (explode(',', IWP_ZIP_EXECUTABLE) as $potzip) {
1083 if (!@is_executable($potzip)) continue;
1084 if ($logit) $this->log("Testing: $potzip");
1085
1086 # Test it, see if it is compatible with Info-ZIP
1087 # If you have another kind of zip, then feel free to tell me about it
1088 @mkdir($iwp_backup_dir.'/binziptest/subdir1/subdir2', 0777, true);
1089
1090 if (!file_exists($iwp_backup_dir.'/binziptest/subdir1/subdir2')) return false;
1091
1092 file_put_contents($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html', '<html><body><a href="https://infinitewp.com">InfiniteWP is a great backup and restoration plugin for WordPress.</a></body></html>');
1093 if(file_exists($iwp_backup_dir.'/binziptest/test.zip')){
1094 @unlink($iwp_backup_dir.'/binziptest/test.zip');
1095 }
1096 if (is_file($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html')) {
1097
1098 $exec = "cd ".escapeshellarg($iwp_backup_dir)."; $potzip";
1099 if (defined('IWP_BINZIP_OPTS') && IWP_BINZIP_OPTS) $exec .= ' '.IWP_BINZIP_OPTS;
1100 $exec .= " -v -u -r binziptest/test.zip binziptest/subdir1";
1101
1102 $all_ok=true;
1103 $handle = popen($exec, "r");
1104 if ($handle) {
1105 while (!feof($handle)) {
1106 $w = fgets($handle);
1107 if ($w && $logit) $this->log("Output: ".trim($w));
1108 }
1109 $ret = pclose($handle);
1110 if ($ret !=0) {
1111 if ($logit) $this->log("Binary zip: error (code: $ret)");
1112 $all_ok = false;
1113 }
1114 } else {
1115 if ($logit) $this->log("Error: popen failed");
1116 $all_ok = false;
1117 }
1118
1119 # Now test -@
1120 if (true == $all_ok) {
1121 file_put_contents($iwp_backup_dir.'/binziptest/subdir1/subdir2/test2.html', '<html><body><a href="https://infinitewp.com">InfiniteWP is a really great backup and restoration plugin for WordPress.</a></body></html>');
1122
1123 $exec = $potzip;
1124 if (defined('IWP_BINZIP_OPTS') && IWP_BINZIP_OPTS) $exec .= ' '.IWP_BINZIP_OPTS;
1125 $exec .= " -v -@ binziptest/test.zip";
1126
1127 $all_ok=true;
1128
1129 $descriptorspec = array(
1130 0 => array('pipe', 'r'),
1131 1 => array('pipe', 'w'),
1132 2 => array('pipe', 'w')
1133 );
1134 $handle = proc_open($exec, $descriptorspec, $pipes, $iwp_backup_dir);
1135 if (is_resource($handle)) {
1136 if (!fwrite($pipes[0], "binziptest/subdir1/subdir2/test2.html\n")) {
1137 @fclose($pipes[0]);
1138 @fclose($pipes[1]);
1139 @fclose($pipes[2]);
1140 $all_ok = false;
1141 } else {
1142 fclose($pipes[0]);
1143 while (!feof($pipes[1])) {
1144 $w = fgets($pipes[1]);
1145 if ($w && $logit) $this->log("Output: ".trim($w));
1146 }
1147 fclose($pipes[1]);
1148
1149 while (!feof($pipes[2])) {
1150 $last_error = fgets($pipes[2]);
1151 if (!empty($last_error) && $logit) $this->log("Stderr output: ".trim($w));
1152 }
1153 fclose($pipes[2]);
1154
1155 $ret = proc_close($handle);
1156 if ($ret !=0) {
1157 if ($logit) $this->log("Binary zip: error (code: $ret)");
1158 $all_ok = false;
1159 }
1160
1161 }
1162
1163 } else {
1164 if ($logit) $this->log("Error: proc_open failed");
1165 $all_ok = false;
1166 }
1167
1168 }
1169
1170 // Do we now actually have a working zip? Need to test the created object using PclZip
1171 // If it passes, then remove dirs and then return $potzip;
1172 $found_first = false;
1173 $found_second = false;
1174 if ($all_ok && file_exists($iwp_backup_dir.'/binziptest/test.zip')) {
1175 if (function_exists('gzopen')) {
1176 if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
1177 $zip = new PclZip($iwp_backup_dir.'/binziptest/test.zip');
1178 $foundit = 0;
1179 if (($list = $zip->listContent()) != 0) {
1180 foreach ($list as $obj) {
1181 if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test.html' == $obj['stored_filename'] && $obj['size']==129) $found_first=true;
1182 if ($obj['filename'] && !empty($obj['stored_filename']) && 'binziptest/subdir1/subdir2/test2.html' == $obj['stored_filename'] && $obj['size']==136) $found_second=true;
1183 }
1184 }
1185 } else {
1186 // PclZip will die() if gzopen is not found
1187 // Obviously, this is a kludge - we assume it's working. We could, of course, just return false - but since we already know now that PclZip can't work, that only leaves ZipArchive
1188 $this->log("gzopen function not found; PclZip cannot be invoked; will assume that binary zip works if we have a non-zero file");
1189 if (filesize($iwp_backup_dir.'/binziptest/test.zip') > 0) {
1190 $found_first = true;
1191 $found_second = true;
1192 }
1193 }
1194 }
1195 $this->remove_binzip_test_files($iwp_backup_dir);
1196 if ($found_first && $found_second) {
1197 if ($logit) $this->log("Working binary zip found: $potzip");
1198 if ($cacheit) $this->jobdata_set('binzip', $potzip);
1199 return $potzip;
1200 }
1201
1202 }
1203 $this->remove_binzip_test_files($iwp_backup_dir);
1204 }
1205 if ($cacheit) $this->jobdata_set('binzip', false);
1206 return false;
1207 }
1208
1209 private function remove_binzip_test_files($iwp_backup_dir) {
1210 @unlink($iwp_backup_dir.'/binziptest/subdir1/subdir2/test.html');
1211 @unlink($iwp_backup_dir.'/binziptest/subdir1/subdir2/test2.html');
1212 @rmdir($iwp_backup_dir.'/binziptest/subdir1/subdir2');
1213 @rmdir($iwp_backup_dir.'/binziptest/subdir1');
1214 @unlink($iwp_backup_dir.'/binziptest/test.zip');
1215 @rmdir($iwp_backup_dir.'/binziptest');
1216 }
1217
1218 // This function is purely for timing - we just want to know the maximum run-time; not whether we have achieved anything during it
1219 public function record_still_alive() {
1220 // Update the record of maximum detected runtime on each run
1221 $time_passed = $this->jobdata_get('run_times');
1222 if (!is_array($time_passed)) $time_passed = array();
1223
1224 $time_this_run = microtime(true)-$this->opened_log_time;
1225 $time_passed[$this->current_resumption] = $time_this_run;
1226 $this->jobdata_set('run_times', $time_passed);
1227
1228 $resume_interval = $this->jobdata_get('resume_interval');
1229 if ($time_this_run + 30 > $resume_interval) {
1230 $new_interval = ceil($time_this_run + 30);
1231 set_site_transient('IWP_initial_resume_interval', (int)$new_interval, 8*86400);
1232 $this->log("The time we have been running (".round($time_this_run,1).") is approaching the resumption interval ($resume_interval) - increasing resumption interval to $new_interval");
1233 $this->jobdata_set('resume_interval', $new_interval);
1234 }
1235
1236 }
1237
1238 public function something_useful_happened() {
1239
1240 $this->record_still_alive();
1241
1242 if (!$this->something_useful_happened) {
1243 $useful_checkin = $this->jobdata_get('useful_checkin');
1244 if (empty($useful_checkin) || $this->current_resumption > $useful_checkin) $this->jobdata_set('useful_checkin', $this->current_resumption);
1245 }
1246
1247 $this->something_useful_happened = true;
1248
1249 $iwp_backup_dir = $this->backups_dir_location();
1250 if (file_exists($iwp_backup_dir.'/deleteflag-'.$this->nonce.'.txt')) {
1251 $this->log("User request for abort: backup job will be immediately halted");
1252 @unlink($iwp_backup_dir.'/deleteflag-'.$this->nonce.'.txt');
1253 $this->backup_finish($this->current_resumption + 1, true, true, $this->current_resumption, true);
1254 die;
1255 }
1256
1257 if ($this->current_resumption >=5 && false == $this->newresumption_scheduled) {
1258 $this->log("This is resumption ".$this->current_resumption.", but meaningful activity is still taking place; so a new one will be scheduled");
1259 // We just use max here to make sure we get a number at all
1260 $resume_interval = max($this->jobdata_get('resume_interval'), 75);
1261 // Don't consult the minimum here
1262 // if (!is_numeric($resume_interval) || $resume_interval<300) { $resume_interval = 300; }
1263 $schedule_for = time()+$resume_interval;
1264 $this->newresumption_scheduled = $schedule_for;
1265 wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($this->current_resumption + 1, $this->nonce));
1266 } else {
1267 $this->reschedule_if_needed();
1268 }
1269 }
1270
1271 public function option_filter_get($which) {
1272 global $wpdb;
1273 $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $which));
1274 // Has to be get_row instead of get_var because of funkiness with 0, false, null values
1275 return (is_object($row)) ? $row->option_value : false;
1276 }
1277
1278 public function parse_filename($filename) {
1279 if (preg_match('/^backup_([\-0-9]{10})-([0-9]{4})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?+\.(zip|gz|gz\.crypt)$/i', $filename, $matches)) {
1280 return array(
1281 'date' => strtotime($matches[1].' '.$matches[2]),
1282 'nonce' => $matches[3],
1283 'type' => $matches[4],
1284 'index' => (empty($matches[5]) ? 0 : $matches[5]-1),
1285 'extension' => $matches[6]);
1286 } else {
1287 return false;
1288 }
1289 }
1290
1291 /**
1292 * Indicate which checksums to take for backup files. Abstracted for extensibilty and future changes.
1293 *
1294 * @returns array - a list of hashing algorithms, as understood by PHP's hash() function
1295 */
1296 public function which_checksums() {
1297 return apply_filters('IWP_which_checksums', array('sha1', 'sha256'));
1298 }
1299
1300 // Pretty printing
1301 public function printfile($description, $history, $entity, $checksums, $jobdata, $smaller=false) {
1302
1303 if (empty($history[$entity])) return;
1304
1305 if ($smaller) {
1306 $pfiles = "<strong>".$description." (".sprintf(__('files: %s', 'InfiniteWP'), count($history[$entity])).")</strong><br>\n";
1307 } else {
1308 $pfiles = "<h3>".$description." (".sprintf(__('files: %s', 'InfiniteWP'), count($history[$entity])).")</h3>\n\n";
1309 }
1310
1311 $pfiles .= '<ul>';
1312 $files = $history[$entity];
1313 if (is_string($files)) $files = array($files);
1314
1315 foreach ($files as $ind => $file) {
1316
1317 $op = htmlspecialchars($file)."\n";
1318 $skey = $entity.((0 == $ind) ? '' : $ind).'-size';
1319
1320 $meta = '';
1321 if ('db' == substr($entity, 0, 2) && 'db' != $entity) {
1322 $dind = substr($entity, 2);
1323 if (is_array($jobdata) && !empty($jobdata['backup_database']) && is_array($jobdata['backup_database']) && !empty($jobdata['backup_database'][$dind]) && is_array($jobdata['backup_database'][$dind]['dbinfo']) && !empty($jobdata['backup_database'][$dind]['dbinfo']['host'])) {
1324 $dbinfo = $jobdata['backup_database'][$dind]['dbinfo'];
1325 $meta .= sprintf(__('External database (%s)', 'InfiniteWP'), $dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'])."<br>";
1326 }
1327 }
1328 if (isset($history[$skey])) $meta .= sprintf(__('Size: %s MB', 'InfiniteWP'), round($history[$skey]/1048576, 1));
1329 $ckey = $entity.$ind;
1330 foreach ($checksums as $ck) {
1331 $ck_plain = false;
1332 if (isset($history['checksums'][$ck][$ckey])) {
1333 $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'InfiniteWP'), strtoupper($ck), $history['checksums'][$ck][$ckey]);
1334 $ck_plain = true;
1335 }
1336 if (isset($history['checksums'][$ck][$ckey.'.crypt'])) {
1337 if ($ck_plain) $meta .= ' '.__('(when decrypted)');
1338 $meta .= (($meta) ? ', ' : '').sprintf(__('%s checksum: %s', 'InfiniteWP'), strtoupper($ck), $history['checksums'][$ck][$ckey.'.crypt']);
1339 }
1340 }
1341
1342 $fileinfo = apply_filters("IWP_fileinfo_$entity", array(), $ind);
1343 if (is_array($fileinfo) && !empty($fileinfo)) {
1344 if (isset($fileinfo['html'])) {
1345 $meta .= $fileinfo['html'];
1346 }
1347 }
1348
1349 #if ($meta) $meta = " ($meta)";
1350 if ($meta) $meta = "<br><em>$meta</em>";
1351 $pfiles .= '<li>'.$op.$meta."\n</li>\n";
1352 }
1353
1354 $pfiles .= "</ul>\n";
1355
1356 return $pfiles;
1357
1358 }
1359
1360 // This important function returns a list of file entities that can potentially be backed up (subject to users settings), and optionally further meta-data about them
1361 public function get_backupable_file_entities($include_others = true, $full_info = false) {
1362
1363 $wp_upload_dir = $this->wp_upload_dir();
1364
1365 if ($full_info) {
1366 $arr = array(
1367 'plugins' => array('path' => untrailingslashit(WP_PLUGIN_DIR), 'description' => __('Plugins','IWP')),
1368 'themes' => array('path' => WP_CONTENT_DIR.'/themes', 'description' => __('Themes','IWP')),
1369 'uploads' => array('path' => untrailingslashit($wp_upload_dir['basedir']), 'description' => __('Uploads','IWP'))
1370 );
1371 } else {
1372 $arr = array(
1373 'plugins' => untrailingslashit(WP_PLUGIN_DIR),
1374 'themes' => WP_CONTENT_DIR.'/themes',
1375 'uploads' => untrailingslashit($wp_upload_dir['basedir'])
1376 );
1377 }
1378
1379 $arr = apply_filters('IWP_backupable_file_entities', $arr, $full_info);
1380
1381 // We then add 'others' on to the end
1382 if ($include_others) {
1383 if ($full_info) {
1384 $arr['others'] = array('path' => WP_CONTENT_DIR, 'description' => __('Others', 'IWP'));
1385 } else {
1386 $arr['others'] = WP_CONTENT_DIR;
1387 }
1388 }
1389
1390 // Entries that should be added after 'others'
1391 $arr = apply_filters('IWP_backupable_file_entities_final', $arr, $full_info);
1392
1393 return $arr;
1394
1395 }
1396
1397 # This is just a long-winded way of forcing WP to get the value afresh from the db, instead of using the auto-loaded/cached value (which can be out of date, especially since backups are, by their nature, long-running)
1398 public function filter_IWP_backup_history($v) {
1399 global $wpdb;
1400 $row = $wpdb->get_row( $wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", 'IWP_backup_history' ) );
1401 if (is_object($row )) return maybe_unserialize($row->option_value);
1402 return false;
1403 }
1404
1405 public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
1406 switch ($errno) {
1407 case 1: $e_type = 'E_ERROR'; break;
1408 case 2: $e_type = 'E_WARNING'; break;
1409 case 4: $e_type = 'E_PARSE'; break;
1410 case 8: $e_type = 'E_NOTICE'; break;
1411 case 16: $e_type = 'E_CORE_ERROR'; break;
1412 case 32: $e_type = 'E_CORE_WARNING'; break;
1413 case 64: $e_type = 'E_COMPILE_ERROR'; break;
1414 case 128: $e_type = 'E_COMPILE_WARNING'; break;
1415 case 256: $e_type = 'E_USER_ERROR'; break;
1416 case 512: $e_type = 'E_USER_WARNING'; break;
1417 case 1024: $e_type = 'E_USER_NOTICE'; break;
1418 case 2048: $e_type = 'E_STRICT'; break;
1419 case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
1420 case 8192: $e_type = 'E_DEPRECATED'; break;
1421 case 16384: $e_type = 'E_USER_DEPRECATED'; break;
1422 case 30719: $e_type = 'E_ALL'; break;
1423 default: $e_type = "E_UNKNOWN ($errno)"; break;
1424 }
1425
1426 if (!is_string($errstr)) $errstr = serialize($errstr);
1427
1428 if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
1429
1430 if ('E_DEPRECATED' == $e_type && !empty($this->no_deprecation_warnings)) {
1431 return false;
1432 }
1433
1434 return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
1435
1436 }
1437
1438 public function php_error($errno, $errstr, $errfile, $errline) {
1439 if (0 == error_reporting()) return true;
1440 $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
1441 if (false !== $logline) $this->log($logline, 'notice', 'php_event');
1442 // Pass it up the chain
1443 return $this->error_reporting_stop_when_logged;
1444 }
1445
1446 public function backup_resume($resumption_no, $bnonce, $first_call = false) {
1447 global $iwp_mmb_core;
1448
1449 set_error_handler(array($this, 'php_error'), E_ALL & ~E_STRICT);
1450 if ($first_call) {
1451 $this->reschedule(10, $first_call);
1452 die;
1453 }
1454 $this->current_resumption = $resumption_no;
1455
1456 @set_time_limit(IWP_SET_TIME_LIMIT);
1457 @ignore_user_abort(true);
1458
1459 $runs_started = array();
1460 $time_now = microtime(true);
1461
1462 add_filter('pre_option_IWP_backup_history', array($this, 'filter_IWP_backup_history'));
1463
1464 // Restore state
1465 $resumption_extralog = '';
1466 $prev_resumption = $resumption_no - 1;
1467 $last_successful_resumption = -1;
1468 $job_type = 'backup';
1469
1470 if ($resumption_no > 0) {
1471
1472 $this->nonce = $bnonce;
1473 $this->backup_time = $this->jobdata_get('backup_time');
1474 $this->job_time_ms = $this->jobdata_get('job_time_ms');
1475
1476 # Get the warnings before opening the log file, as opening the log file may generate new ones (which then leads to $this->errors having duplicate entries when they are copied over below)
1477 $warnings = $this->jobdata_get('warnings');
1478
1479 $this->logfile_open($bnonce);
1480
1481 // Import existing warnings. The purpose of this is so that when save_backup_history() is called, it has a complete set - because job data expires quickly, whilst the warnings of the last backup run need to persist
1482 if (is_array($warnings)) {
1483 foreach ($warnings as $warning) {
1484 $this->errors[] = array('level' => 'warning', 'message' => $warning);
1485 }
1486 }
1487
1488 $runs_started = $this->jobdata_get('runs_started');
1489 if (!is_array($runs_started)) $runs_started=array();
1490 $time_passed = $this->jobdata_get('run_times');
1491 if (!is_array($time_passed)) $time_passed = array();
1492
1493 foreach ($time_passed as $run => $passed) {
1494 if (isset($runs_started[$run]) && $runs_started[$run] + $time_passed[$run] + 30 > $time_now) {
1495 // We don't want to increase the resumption if WP has started two copies of the same resumption off
1496 if ($run && $run == $resumption_no) {
1497 $increase_resumption = false;
1498 $this->log("It looks like WordPress's scheduler has started multiple instances of this resumption");
1499 } else {
1500 $increase_resumption = true;
1501 }
1502 $this->terminate_due_to_activity('check-in', round($time_now, 1), round($runs_started[$run] + $time_passed[$run], 1), $increase_resumption);
1503 }
1504 }
1505
1506 for ($i = 0; $i<=$prev_resumption; $i++) {
1507 if (isset($time_passed[$i])) $last_successful_resumption = $i;
1508 }
1509
1510 if (isset($time_passed[$prev_resumption])) {
1511 $resumption_extralog = ", previous check-in=".round($time_passed[$prev_resumption], 1)."s";
1512 } else {
1513 $this->no_checkin_last_time = true;
1514 }
1515
1516 // This is just a simple test to catch restorations of old backup sets where the backup includes a resumption of the backup job
1517 if ($time_now - $this->backup_time > 172800 && true == apply_filters('IWP_check_obsolete_backup', true, $time_now, $this)) {
1518
1519 // We have seen cases where the get_site_option() call that self::get_jobdata() relies on returns nothing, even though the data was there in the database. This appears to be sometimes reproducible for the people who get it, but stops being reproducible if they change their backup times - which suggests that they're having failures at times of extreme load. We can attempt to detect this case, and reschedule, instead of aborting.
1520 if (empty($this->backup_time) && empty($this->backup_is_already_complete) && !empty($this->logfile_name) && is_readable($this->logfile_name)) {
1521 $first_log_bit = file_get_contents($this->logfile_name, false, null, 0, 250);
1522 if (preg_match('/\(0\) Opened log file at time: (.*) on /', $first_log_bit, $matches)) {
1523 $first_opened = strtotime($matches[1]);
1524 // The value of 1000 seconds here is somewhat arbitrary; but allows for the problem to occur in ~ the first 15 minutes. In practice, the problem is extremely rare; if this does not catch it, we can tweak the algorithm.
1525 if (time() - $first_opened < 1000) {
1526 $this->log("This backup task (".$this->nonce.") failed to load its job data (possible database server malfunction), but appears to be only recently started: scheduling a fresh resumption in order to try again, and then ending this resumption ($time_now, ".$this->backup_time.") (existing jobdata keys: ".implode(', ', array_keys($this->jobdata)).")");
1527 $this->reschedule(120);
1528 die;
1529 }
1530 }
1531 }
1532
1533 $this->log("This backup task (".$this->nonce.") is either complete or began over 2 days ago: ending ($time_now, ".$this->backup_time.") (existing jobdata keys: ".implode(', ', array_keys($this->jobdata)).")");
1534 die;
1535 }
1536
1537 } else {
1538 $label = $this->jobdata_get('label');
1539 if ($label) $resumption_extralog = ", label=$label";
1540 }
1541
1542 $this->last_successful_resumption = $last_successful_resumption;
1543
1544 $runs_started[$resumption_no] = $time_now;
1545 if (!empty($this->backup_time)) $this->jobdata_set('runs_started', $runs_started);
1546
1547 // Schedule again, to run in 5 minutes again, in case we again fail
1548 // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
1549 $resume_interval = max(intval($this->jobdata_get('resume_interval')), 100);
1550
1551 $btime = $this->backup_time;
1552
1553 $job_type = $this->jobdata_get('job_type');
1554
1555 do_action('IWP_resume_backup_'.$job_type);
1556
1557 $iwp_backup_dir = $this->backups_dir_location();
1558
1559 $time_ago = time()-$btime;
1560
1561 $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime (".$time_ago."s ago), job type=$job_type".$resumption_extralog);
1562
1563 // This works round a bizarre bug seen in one WP install, where delete_transient and wp_clear_scheduled_hook both took no effect, and upon 'resumption' the entire backup would repeat.
1564 // Argh. In fact, this has limited effect, as apparently (at least on another install seen), the saving of the updated transient via jobdata_set() also took no effect. Still, it does not hurt.
1565 if ($resumption_no >= 1 && 'finished' == $this->jobdata_get('jobstatus')) {
1566 $this->log('Terminate: This backup job is already finished (1).');
1567 $iwp_mmb_core->iwp_delete_option('IWP_backup_status');
1568 die;
1569 } elseif ('backup' == $job_type && !empty($this->backup_is_already_complete)) {
1570 $this->jobdata_set('jobstatus', 'finished');
1571 $this->log('Terminate: This backup job is already finished (2).');
1572 $iwp_mmb_core->iwp_delete_option('IWP_backup_status');
1573 die;
1574 }
1575
1576 if ($resumption_no > 0 && isset($runs_started[$prev_resumption])) {
1577 $our_expected_start = $runs_started[$prev_resumption] + $resume_interval;
1578 # If the previous run increased the resumption time, then it is timed from the end of the previous run, not the start
1579 if (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption]>0) $our_expected_start += $time_passed[$prev_resumption];
1580 $our_expected_start = apply_filters('IWP_expected_start', $our_expected_start, $job_type);
1581 # More than 12 minutes late?
1582 if ($time_now > $our_expected_start + 720) {
1583 $this->log('Long time past since expected resumption time: approx expected='.round($our_expected_start,1).", now=".round($time_now, 1).", diff=".round($time_now-$our_expected_start,1));
1584 $this->log(__('Your website is visited infrequently and InfiniteWP is not getting the resources it hoped for; please set Uptime monitor', 'InfiniteWP'), 'warning', 'infrequentvisits');
1585 }
1586 }
1587
1588 $this->jobdata_set('current_resumption', $resumption_no);
1589
1590 $first_run = apply_filters('IWP_filerun_firstrun', 0);
1591
1592 // We just do this once, as we don't want to be in permanent conflict with the overlap detector
1593 if ($resumption_no >= $first_run + 8 && $resumption_no < $first_run + 15 && $resume_interval >= 300) {
1594
1595 // $time_passed is set earlier
1596 list($max_time, $timings_string, $run_times_known) = $this->max_time_passed($time_passed, $resumption_no - 1, $first_run);
1597
1598 # Do this on resumption 8, or the first time that we have 6 data points
1599 if (($first_run + 8 == $resumption_no && $run_times_known >= 6) || (6 == $run_times_known && !empty($time_passed[$prev_resumption]))) {
1600 $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time)");
1601 // Remember that 30 seconds is used as the 'perhaps something is still running' detection threshold, and that 45 seconds is used as the 'the next resumption is approaching - reschedule!' interval
1602 if ($max_time + 52 < $resume_interval) {
1603 $resume_interval = round($max_time + 52);
1604 $this->log("Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds");
1605 $this->jobdata_set('resume_interval', $resume_interval);
1606 }
1607 // This next condition was added in response to HS#9174, a case where on one resumption, PHP was allowed to run for >3000 seconds - but other than that, up to 500 seconds. As a result, the resumption interval got stuck at a large value, whilst resumptions were only allowed to run for a much smaller amount.
1608 // This detects whether our last run was less than half the resume interval, but was non-trivial (at least 50 seconds - so, indicating it didn't just error out straight away), but with a resume interval of over 300 seconds. In this case, it is reduced.
1609 } elseif (isset($time_passed[$prev_resumption]) && $time_passed[$prev_resumption] > 50 && $resume_interval > 300 && $time_passed[$prev_resumption] < $resume_interval/2 && 'clouduploading' == $this->jobdata_get('jobstatus')) {
1610 $resume_interval = round($time_passed[$prev_resumption] + 52);
1611 $this->log("Time passed on previous resumptions: $timings_string (known: $run_times_known, max: $max_time). Based on the available data, we are bringing the resumption interval down to: $resume_interval seconds");
1612 $this->jobdata_set('resume_interval', $resume_interval);
1613 }
1614
1615 }
1616
1617 // A different argument than before is needed otherwise the event is ignored
1618 $next_resumption = $resumption_no+1;
1619 if ($next_resumption < $first_run + 10) {
1620 if (true === $this->jobdata_get('one_shot')) {
1621 if (true === $this->jobdata_get('reschedule_before_upload') && 1 == $next_resumption) {
1622 $this->log('A resumption will be scheduled for the cloud backup stage');
1623 $schedule_resumption = true;
1624 } else {
1625 $this->log('We are in "one shot" mode - no resumptions will be scheduled');
1626 }
1627 } else {
1628 $schedule_resumption = true;
1629 }
1630 } else {
1631 // We're in over-time - we only reschedule if something useful happened last time (used to be that we waited for it to happen this time - but that meant that temporary errors, e.g. Google 400s on uploads, scuppered it all - we'd do better to have another chance
1632 $useful_checkin = $this->jobdata_get('useful_checkin');
1633 $last_resumption = $resumption_no-1;
1634
1635 if (empty($useful_checkin) || $useful_checkin < $last_resumption) {
1636 $this->log(sprintf('The current run is resumption number %d, and there was nothing useful done on the last run (last useful run: %s) - will not schedule a further attempt until we see something useful happening this time', $resumption_no, $useful_checkin));
1637 } else {
1638 $schedule_resumption = true;
1639 }
1640 }
1641
1642 // Sanity check
1643 if (empty($this->backup_time)) {
1644 $this->log('The backup_time parameter appears to be empty (usually caused by resuming an already-complete backup).');
1645 return false;
1646 }
1647
1648 if (isset($schedule_resumption)) {
1649 $schedule_for = time()+$resume_interval;
1650 $this->log("Scheduling a resumption ($next_resumption) after $resume_interval seconds ($schedule_for) in case this run gets aborted");
1651 wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($next_resumption, $bnonce));
1652 $this->newresumption_scheduled = $schedule_for;
1653 }
1654
1655 $backup_files = $this->jobdata_get('backup_files');
1656
1657 global $IWP_backup;
1658 // Bring in all the backup routines
1659 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/backup.php');
1660 $IWP_backup = new IWP_MMB_Backup($backup_files, apply_filters('IWP_files_altered_since', -1, $job_type));
1661
1662 $undone_files = array();
1663
1664 if ('no' == $backup_files) {
1665 $this->log("This backup run is not intended for files - skipping");
1666 $our_files = array();
1667 } else {
1668
1669 // This should be always called; if there were no files in this run, it returns us an empty array
1670 $backup_array = $IWP_backup->resumable_backup_of_files($resumption_no);
1671
1672 // This save, if there was something, is then immediately picked up again
1673 if (is_array($backup_array)) {
1674 $this->log('Saving backup status to database (elements: '.count($backup_array).")");
1675 $this->save_backup_history($backup_array);
1676 }
1677
1678 // Switch of variable name is purely vestigial
1679 $our_files = $backup_array;
1680 if (!is_array($our_files)) $our_files = array();
1681
1682 }
1683
1684 $backup_databases = $this->jobdata_get('backup_database');
1685
1686 if (!is_array($backup_databases)) $backup_databases = array('wp' => $backup_databases);
1687
1688 foreach ($backup_databases as $whichdb => $backup_database) {
1689
1690 if (is_array($backup_database)) {
1691 $dbinfo = $backup_database['dbinfo'];
1692 $backup_database = $backup_database['status'];
1693 } else {
1694 $dbinfo = array();
1695 }
1696
1697 $tindex = ('wp' == $whichdb) ? 'db' : 'db'.$whichdb;
1698
1699 if ('begun' == $backup_database || 'finished' == $backup_database || 'encrypted' == $backup_database) {
1700
1701 if ('wp' == $whichdb) {
1702 $db_descrip = 'WordPress DB';
1703 } else {
1704 if (!empty($dbinfo) && is_array($dbinfo) && !empty($dbinfo['host'])) {
1705 $db_descrip = "External DB $whichdb - ".$dbinfo['user'].'@'.$dbinfo['host'].'/'.$dbinfo['name'];
1706 } else {
1707 $db_descrip = "External DB $whichdb - details appear to be missing";
1708 }
1709 }
1710
1711 if ('begun' == $backup_database) {
1712 if ($resumption_no > 0) {
1713 $this->log("Resuming creation of database dump ($db_descrip)");
1714 } else {
1715 $this->log("Beginning creation of database dump ($db_descrip)");
1716 }
1717 } elseif ('encrypted' == $backup_database) {
1718 $this->log("Database dump ($db_descrip): Creation and encryption were completed already");
1719 } else {
1720 $this->log("Database dump ($db_descrip): Creation was completed already");
1721 }
1722
1723 if ('wp' != $whichdb && (empty($dbinfo) || !is_array($dbinfo) || empty($dbinfo['host']))) {
1724 unset($backup_databases[$whichdb]);
1725 $this->jobdata_set('backup_database', $backup_databases);
1726 continue;
1727 }
1728
1729 $db_backup = $IWP_backup->backup_db($backup_database, $whichdb, $dbinfo);
1730
1731 if(is_array($our_files) && is_string($db_backup)) $our_files[$tindex] = $db_backup;
1732
1733 if ('encrypted' != $backup_database) {
1734 $backup_databases[$whichdb] = array('status' => 'finished', 'dbinfo' => $dbinfo);
1735 $this->jobdata_set('backup_database', $backup_databases);
1736 }
1737 } elseif ('no' == $backup_database) {
1738 $this->log("No database backup ($whichdb) - not part of this run");
1739 } else {
1740 $this->log("Unrecognised data when trying to ascertain if the database ($whichdb) was backed up (".serialize($backup_database).")");
1741 }
1742
1743 // 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.
1744 $this->save_backup_history($our_files);
1745
1746 // Potentially encrypt the database if it is not already
1747 if ('no' != $backup_database && isset($our_files[$tindex]) && !preg_match("/\.crypt$/", $our_files[$tindex])) {
1748 $our_files[$tindex] = $IWP_backup->encrypt_file($our_files[$tindex]);
1749 // No need to save backup history now, as it will happen in a few lines time
1750 if (preg_match("/\.crypt$/", $our_files[$tindex])) {
1751 $backup_databases[$whichdb] = array('status' => 'encrypted', 'dbinfo' => $dbinfo);
1752 $this->jobdata_set('backup_database', $backup_databases);
1753 }
1754 }
1755
1756 if ('no' != $backup_database && isset($our_files[$tindex]) && file_exists($iwp_backup_dir.'/'.$our_files[$tindex])) {
1757 $our_files[$tindex.'-size'] = filesize($iwp_backup_dir.'/'.$our_files[$tindex]);
1758 $this->save_backup_history($our_files);
1759 }
1760
1761 }
1762
1763 $backupable_entities = $this->get_backupable_file_entities(true);
1764
1765 $checksum_list = $this->which_checksums();
1766
1767 $checksums = array();
1768
1769 foreach ($checksum_list as $checksum) {
1770 $checksums[$checksum] = array();
1771 }
1772
1773 $total_size = 0;
1774
1775 // Queue files for upload
1776 foreach ($our_files as $key => $files) {
1777 // Only continue if the stored info was about a dump
1778 if (!isset($backupable_entities[$key]) && ('db' != substr($key, 0, 2) || '-size' == substr($key, -5, 5))) continue;
1779 if (is_string($files)) $files = array($files);
1780 foreach ($files as $findex => $file) {
1781
1782 $size_key = (0 == $findex) ? $key.'-size' : $key.$findex.'-size';
1783 $total_size = (false === $total_size || !isset($our_files[$size_key]) || !is_numeric($our_files[$size_key])) ? false : $total_size + $our_files[$size_key];
1784
1785 foreach ($checksum_list as $checksum) {
1786
1787 $cksum = $this->jobdata_get($checksum.'-'.$key.$findex);
1788 if ($cksum) $checksums[$checksum][$key.$findex] = $cksum;
1789 $cksum = $this->jobdata_get($checksum.'-'.$key.$findex.'.crypt');
1790 if ($cksum) $checksums[$checksum][$key.$findex.".crypt"] = $cksum;
1791
1792 }
1793
1794 if ($this->is_uploaded($file)) {
1795 $this->log("$file: $key: This file has already been successfully uploaded");
1796 } elseif (is_file($iwp_backup_dir.'/'.$file)) {
1797 if (!in_array($file, $undone_files)) {
1798 $this->log("$file: $key: This file has not yet been successfully uploaded: will queue");
1799 $undone_files[$key.$findex] = $file;
1800 } else {
1801 $this->log("$file: $key: This file was already queued for upload (this condition should never be seen)");
1802 }
1803 } else {
1804 $this->log("$file: $key: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem ($iwp_backup_dir/$file)");
1805 $this->uploaded_file($file, true);
1806 }
1807 }
1808 }
1809 $our_files['checksums'] = $checksums;
1810
1811 // Save again (now that we have checksums)
1812 $size_description = (false === $total_size) ? 'Unknown' : $this->convert_numeric_size_to_text($total_size);
1813 $this->log("Saving backup history. Total backup size: $size_description");
1814 $backup_meta_file = $this->createBackupMetaFile($our_files);
1815 if ($backup_meta_file) {
1816 $our_files['backup_file_basename'] = $backup_meta_file;
1817 $undone_files['backup_file_basename'] = $backup_meta_file;
1818 }
1819 $this->save_backup_history($our_files);
1820 do_action('IWP_final_backup_history', $our_files);
1821 // We finished; so, low memory was not a problem
1822 $this->log_removewarning('lowram');
1823
1824 if (0 == count($undone_files)) {
1825 $this->log("Resume backup ($bnonce, $resumption_no): finish run");
1826 if (is_array($our_files)) $this->save_last_backup($our_files);
1827 $this->log("There were no more files that needed uploading");
1828 // No email, as the user probably already got one if something else completed the run
1829 $allow_email = false;
1830 if ('begun' == $this->jobdata_get('prune')) {
1831 // Begun, but not finished
1832 $this->log("Restarting backup prune operation");
1833 $IWP_backup->do_prune_standalone();
1834 $allow_email = true;
1835 }
1836 $this->backup_finish($next_resumption, true, $allow_email, $resumption_no);
1837 restore_error_handler();
1838 return;
1839 }
1840
1841 $this->error_count_before_cloud_backup = $this->error_count();
1842
1843 // This is intended for one-shot backups, where we do want a resumption if it's only for uploading
1844 if (empty($this->newresumption_scheduled) && 0 == $resumption_no && 0 == $this->error_count_before_cloud_backup && true === $this->jobdata_get('reschedule_before_upload')) {
1845 $this->log("Cloud backup stage reached on one-shot backup: scheduling resumption for the cloud upload");
1846 $this->reschedule(60);
1847 $this->record_still_alive();
1848 }
1849
1850 $this->log("Requesting upload of the files that have not yet been successfully uploaded (".count($undone_files).")");
1851
1852 $IWP_backup->cloud_backup($undone_files);
1853
1854 $this->log("Resume backup ($bnonce, $resumption_no): finish run");
1855 if (is_array($our_files)) $this->save_last_backup($our_files);
1856 $this->backup_finish($next_resumption, true, true, $resumption_no);
1857
1858 restore_error_handler();
1859
1860 }
1861
1862 public function convert_numeric_size_to_text($size) {
1863 if ($size > 1073741824) {
1864 return round($size / 1073741824, 1).' GB';
1865 } elseif ($size > 1048576) {
1866 return round($size / 1048576, 1).' MB';
1867 } elseif ($size > 1024) {
1868 return round($size / 1024, 1).' KB';
1869 } else {
1870 return round($size, 1).' B';
1871 }
1872 }
1873
1874 public function max_time_passed($time_passed, $upto, $first_run) {
1875 $max_time = 0;
1876 $timings_string = "";
1877 $run_times_known=0;
1878 for ($i=$first_run; $i<=$upto; $i++) {
1879 $timings_string .= "$i:";
1880 if (isset($time_passed[$i])) {
1881 $timings_string .= round($time_passed[$i], 1).' ';
1882 $run_times_known++;
1883 if ($time_passed[$i] > $max_time) $max_time = round($time_passed[$i]);
1884 } else {
1885 $timings_string .= '? ';
1886 }
1887 }
1888 return array($max_time, $timings_string, $run_times_known);
1889 }
1890
1891 public function jobdata_getarray($non) {
1892 return get_site_option("IWP_jobdata_".$non, array());
1893 }
1894
1895 public function jobdata_set_from_array($array) {
1896 $this->jobdata = $array;
1897 if (!empty($this->nonce)) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1898 }
1899
1900 // This works with any amount of settings, but we provide also a jobdata_set for efficiency as normally there's only one setting
1901 public function jobdata_set_multi() {
1902 if (!is_array($this->jobdata)) $this->jobdata = array();
1903
1904 $args = func_num_args();
1905
1906 for ($i=1; $i<=$args/2; $i++) {
1907 $key = func_get_arg($i*2-2);
1908 $value = func_get_arg($i*2-1);
1909 $this->jobdata[$key] = $value;
1910 }
1911 if (!empty($this->nonce)) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1912 }
1913
1914 public function jobdata_set($key, $value) {
1915 if (empty($this->jobdata)) {
1916 $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce);
1917 if (!is_array($this->jobdata)) $this->jobdata = array();
1918 }
1919 $this->jobdata[$key] = $value;
1920 if ($this->nonce) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1921 }
1922
1923 public function jobdata_delete($key) {
1924 if (!is_array($this->jobdata)) {
1925 $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce);
1926 if (!is_array($this->jobdata)) $this->jobdata = array();
1927 }
1928 unset($this->jobdata[$key]);
1929 if ($this->nonce) update_site_option("IWP_jobdata_".$this->nonce, $this->jobdata);
1930 }
1931
1932 public function get_job_option($opt) {
1933 // These are meant to be read-only
1934 if (empty($this->jobdata['option_cache']) || !is_array($this->jobdata['option_cache'])) {
1935 if (!is_array($this->jobdata)) $this->jobdata = get_site_option("IWP_jobdata_".$this->nonce, array());
1936 $this->jobdata['option_cache'] = array();
1937 }
1938 return isset($this->jobdata['option_cache'][$opt]) ? $this->jobdata['option_cache'][$opt] : IWP_MMB_Backup_Options::get_iwp_backup_option($opt);
1939 }
1940
1941 public function jobdata_get($key, $default = null, $all_data = false) {
1942 if (empty($this->jobdata)) {
1943 $this->jobdata = empty($this->nonce) ? array() : get_site_option("IWP_jobdata_".$this->nonce, array());
1944 if ($all_data) return $this->jobdata;
1945 if (!is_array($this->jobdata)) return $default;
1946 }
1947 if ($all_data) return $this->jobdata;
1948 return isset($this->jobdata[$key]) ? $this->jobdata[$key] : $default;
1949 }
1950
1951 public function jobdata_reset() {
1952 $this->jobdata = null;
1953 }
1954
1955 private function ensure_semaphore_exists($semaphore) {
1956 // Make sure the options for semaphores exist
1957 global $wpdb;
1958 $results = $wpdb->get_results("
1959 SELECT option_id
1960 FROM $wpdb->options
1961 WHERE option_name IN ('IWP_locked_$semaphore', 'IWP_unlocked_$semaphore', 'IWP_last_lock_time_$semaphore', 'IWP_semaphore_$semaphore')
1962 ");
1963
1964 if (!is_array($results) || count($results) < 3) {
1965
1966 if (is_array($results) && count($results) > 0) {
1967 $this->log("Semaphore ($semaphore, ".$wpdb->options.") in an impossible/broken state - fixing (".count($results).")");
1968 } else {
1969 $this->log("Semaphore ($semaphore, ".$wpdb->options.") being initialised");
1970 }
1971
1972 $wpdb->query("
1973 DELETE FROM $wpdb->options
1974 WHERE option_name IN ('IWP_locked_$semaphore', 'IWP_unlocked_$semaphore', 'IWP_last_lock_time_$semaphore', 'IWP_semaphore_$semaphore')
1975 ");
1976
1977 $wpdb->query($wpdb->prepare("
1978 INSERT INTO $wpdb->options (option_name, option_value, autoload)
1979 VALUES
1980 ('IWP_unlocked_$semaphore', '1', 'no'),
1981 ('IWP_last_lock_time_$semaphore', '%s', 'no'),
1982 ('IWP_semaphore_$semaphore', '0', 'no')
1983 ", current_time('mysql', 1)));
1984 }
1985 }
1986
1987 public function backup_files() {
1988 # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
1989 $this->boot_backup(true, false);
1990 }
1991
1992 public function backup_database() {
1993 # Note that nothing will happen if the file backup had the same schedule
1994 $this->boot_backup(false, true);
1995 }
1996
1997 public function backup_all($options) {
1998 $skip_cloud = empty($options['nocloud']) ? false : false;
1999 $this->boot_backup(1, 1, false, false, ($skip_cloud) ? 'none' : false, $options);
2000 }
2001
2002 public function backupnow_files($options) {
2003 $skip_cloud = empty($options['nocloud']) ? false : true;
2004 $this->boot_backup(1, 0, false, false, ($skip_cloud) ? 'none' : false, $options);
2005 }
2006
2007 public function backupnow_database($options) {
2008 $skip_cloud = empty($options['nocloud']) ? false : true;
2009 $this->boot_backup(0, 1, false, false, ($skip_cloud) ? 'none' : false, $options);
2010 }
2011
2012 // This procedure initiates a backup run
2013 // $backup_files/$backup_database: true/false = yes/no (over-write allowed); 1/0 = yes/no (force)
2014 public function boot_backup($backup_files, $backup_database, $restrict_files_to_override = false, $one_shot = false, $service = false, $options = array()) {
2015 global $iwp_mmb_core;
2016
2017 @ignore_user_abort(true);
2018 @set_time_limit(IWP_SET_TIME_LIMIT);
2019
2020 if (false === $restrict_files_to_override && isset($options['restrict_files_to_override'])) $restrict_files_to_override = $options['restrict_files_to_override'];
2021 // Generate backup information
2022 $use_nonce = (empty($options['use_nonce'])) ? false : $options['use_nonce'];
2023 $this->backup_time_nonce($use_nonce);
2024 $iwp_mmb_core->iwp_update_option($option_name = 'IWP_running_backupID',$this->nonce);
2025 // The current_resumption is consulted within logfile_open()
2026 $this->current_resumption = 0;
2027 $this->logfile_open($this->nonce);
2028 $iwp_backup_dir = $this->backups_dir_location();
2029 if (!is_file($this->logfile_name)) {
2030 $this->log('Failed to open log file ('.$this->logfile_name.') - the directory ('.$iwp_backup_dir.') for creating files in is not writable, or you ran out of disk space). Backup aborted.');
2031 $this->log(__('Could not create files in the backup directory. Backup aborted','InfiniteWP'), 'error');
2032 $this->save_last_backup($our_files = array());
2033 $iwp_mmb_core->iwp_delete_option('IWP_running_backupID');
2034 return false;
2035 }
2036
2037 // Some house-cleaning
2038 $this->clean_temporary_files();
2039
2040 // Log some information that may be helpful
2041 $this->log("Tasks: Backup files: $backup_files (schedule: ".IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval', 'unset').") Backup DB: $backup_database (schedule: ".IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval_database', 'unset').")");
2042
2043 $semaphore = (($backup_files) ? 'f' : '') . (($backup_database) ? 'd' : '');
2044 $this->ensure_semaphore_exists($semaphore);
2045
2046 if (!is_string($service) && !is_array($service)) $service = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_service');
2047 $service = $this->just_one($service);
2048 if (is_string($service)) $service = array($service);
2049 if (!is_array($service)) $service = array('none');
2050
2051 if (!empty($options['extradata']) && preg_match('#services=remotesend/(\d+)#', $options['extradata'])) {
2052 if ($service === array('none')) $service = array();
2053 $service[] = 'remotesend';
2054 }
2055
2056 $option_cache = array();
2057 foreach ($service as $serv) {
2058 if ('' == $serv || 'none' == $serv) continue;
2059 include_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$serv.'.php');
2060 $cclass = 'IWP_MMB_UploadModule_'.$serv;
2061 if (!class_exists($cclass)) {
2062 error_log("InfiniteWP: backup class does not exist: $cclass");
2063 continue;
2064 }
2065 $obj = new $cclass;
2066
2067 if (is_callable(array($obj, 'get_credentials'))) {
2068 $opts = $obj->get_credentials();
2069 if (is_array($opts)) {
2070 foreach ($opts as $opt) $option_cache[$opt] = IWP_MMB_Backup_Options::get_iwp_backup_option($opt);
2071 }
2072 }
2073 }
2074
2075 // If nothing to be done, then just finish
2076 if (!$backup_files && !$backup_database) {
2077 $ret = $this->backup_finish(1, false, false, 0);
2078 // Don't keep useless log files
2079 if (!IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode') && !empty($this->logfile_name) && file_exists($this->logfile_name)) {
2080 unlink($this->logfile_name);
2081 }
2082 $iwp_mmb_core->iwp_delete_option('IWP_running_backupID');
2083 return $ret;
2084 }
2085
2086 // Are we doing an action called by the WP scheduler? If so, we want to check when that last happened; the point being that the dodgy WP scheduler, when overloaded, can call the event multiple times - and sometimes, it evades the semaphore because it calls a second run after the first has finished, or > 3 minutes (our semaphore lock time) later
2087 // doing_action() was added in WP 3.9
2088 // wp_cron() can be called from the 'init' action
2089
2090 if (function_exists('doing_action') && (doing_action('init') || defined('DOING_CRON') && DOING_CRON) && (doing_action('IWP_backup_database') || doing_action('IWP_backup'))) {
2091 $last_scheduled_action_called_at = get_option("IWP_last_scheduled_$semaphore");
2092 // 11 minutes - so, we're assuming that they haven't custom-modified their schedules to run scheduled backups more often than that. If they have, they need also to use the filter to over-ride this check.
2093 $seconds_ago = time() - $last_scheduled_action_called_at;
2094 if ($last_scheduled_action_called_at && $seconds_ago < 660 && apply_filters('IWP_check_repeated_scheduled_backups', true)) {
2095 $this->log(sprintf('Scheduled backup aborted - another backup of this type was apparently invoked by the WordPress scheduler only %d seconds ago - the WordPress scheduler invoking events multiple times usually indicates a very overloaded server (or other plugins that mis-use the scheduler)', $seconds_ago));
2096 $iwp_mmb_core->iwp_delete_option('IWP_running_backupID');
2097 return;
2098 }
2099 }
2100 update_option("IWP_last_scheduled_$semaphore", time());
2101
2102 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/backup/class.semaphore.php');
2103 $this->semaphore = IWP_MMB_Semaphore::factory();
2104 $this->semaphore->lock_name = $semaphore;
2105
2106 $semaphore_log_message = 'Requesting semaphore lock ('.$semaphore.')';
2107 if (!empty($last_scheduled_action_called_at)) {
2108 $semaphore_log_message .= " (apparently via scheduler: last_scheduled_action_called_at=$last_scheduled_action_called_at, seconds_ago=$seconds_ago)";
2109 } else {
2110 $semaphore_log_message .= " (apparently not via scheduler)";
2111 }
2112
2113 $this->log($semaphore_log_message);
2114 if (!$this->semaphore->lock()) {
2115 $this->log('Failed to gain semaphore lock ('.$semaphore.') - another backup of this type is apparently already active - aborting (if this is wrong - i.e. if the other backup crashed without removing the lock, then another can be started after 3 minutes)');
2116 $iwp_mmb_core->iwp_delete_option('IWP_running_backupID');
2117 return;
2118 }
2119
2120 // Allow the resume interval to be more than 300 if last time we know we went beyond that - but never more than 600
2121 if (defined('IWP_INITIAL_RESUME_INTERVAL') && is_numeric(IWP_INITIAL_RESUME_INTERVAL)) {
2122 $resume_interval = IWP_INITIAL_RESUME_INTERVAL;
2123 } else {
2124 $resume_interval = (int)min(max(300, get_site_transient('IWP_initial_resume_interval')), 600);
2125 }
2126 # We delete it because we only want to know about behaviour found during the very last backup run (so, if you move servers then old data is not retained)
2127 delete_site_transient('IWP_initial_resume_interval');
2128
2129 $job_file_entities = array();
2130 if ($backup_files) {
2131 $possible_backups = $this->get_backupable_file_entities(true);
2132 foreach ($possible_backups as $youwhat => $whichdir) {
2133 if ((false === $restrict_files_to_override && IWP_MMB_Backup_Options::get_iwp_backup_option("IWP_include_$youwhat", apply_filters("IWP_defaultoption_include_$youwhat", true))) || (is_array($restrict_files_to_override) && !in_array($youwhat, $restrict_files_to_override))) {
2134 // The 0 indicates the zip file index
2135 $job_file_entities[$youwhat] = array(
2136 'index' => 0
2137 );
2138 }
2139 }
2140 }
2141
2142 $followups_allowed = (((!$one_shot && defined('DOING_CRON') && DOING_CRON)) || (defined('IWP_FOLLOWUPS_ALLOWED') && IWP_FOLLOWUPS_ALLOWED));
2143
2144 $split_every = max(intval(IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_split_every', 200)), IWP_SPLIT_MIN);
2145
2146 $initial_jobdata = array(
2147 'resume_interval', $resume_interval,
2148 'job_type', 'backup',
2149 'jobstatus', 'begun',
2150 'backup_time', $this->backup_time,
2151 'job_time_ms', $this->job_time_ms,
2152 'service', $service,
2153 'split_every', $split_every,
2154 'maxzipbatch', 26214400, #25MB
2155 'job_file_entities', $job_file_entities,
2156 'option_cache', $option_cache,
2157 'uploaded_lastreset', 9,
2158 'one_shot', $one_shot,
2159 'followsups_allowed', $followups_allowed
2160 );
2161
2162
2163
2164 if (!empty($options['extradata']) && 'autobackup' == $options['extradata']) array_push($initial_jobdata, 'is_autobackup', true);
2165
2166 // Save what *should* be done, to make it resumable from this point on
2167 if ($backup_database) {
2168 $dbs = apply_filters('IWP_backup_databases', array('wp' => 'begun'));
2169 if (is_array($dbs)) {
2170 foreach ($dbs as $key => $db) {
2171 if ('wp' != $key && (!is_array($db) || empty($db['dbinfo']) || !is_array($db['dbinfo']) || empty($db['dbinfo']['host']))) unset($dbs[$key]);
2172 }
2173 }
2174 } else {
2175 $dbs = "no";
2176 }
2177
2178 array_push($initial_jobdata, 'backup_database', $dbs);
2179 array_push($initial_jobdata, 'backup_files', (($backup_files) ? 'begun' : 'no'));
2180
2181 if (is_array($options) && !empty($options['label'])) array_push($initial_jobdata, 'label', $options['label']);
2182 if (is_array($options) && !empty($options['backup_name'])) array_push($initial_jobdata, 'backup_name', $options['backup_name']);
2183 try {
2184 // Use of jobdata_set_multi saves around 200ms
2185 call_user_func_array(array($this, 'jobdata_set_multi'), apply_filters('IWP_initial_jobdata', $initial_jobdata, $options, $split_every));
2186 } catch (Exception $e) {
2187 $this->log($e->getMessage());
2188 return false;
2189 }
2190
2191 // Everything is set up; now go
2192 if (!empty($options['cron_start'])) {
2193 $this->backup_resume(0, $this->nonce, 1);
2194 }else{
2195 $this->backup_resume(0, $this->nonce);
2196 }
2197
2198 }
2199
2200 // This function examines inside the InfiniteWP directory to see if any new archives have been uploaded. If so, it adds them to the backup set. (Non-present items are also removed, only if the service is 'none').
2201 // If $remotescan is set, then remote storage is also scanned
2202 // $only_add_this_file : an array with keys 'name' and (optionally) 'label'
2203 public function rebuild_backup_history($remotescan = false, $only_add_this_file = false) {
2204
2205 # TODO: Make compatible with incremental naming scheme
2206
2207 $messages = array();
2208 $gmt_offset = get_option('gmt_offset');
2209
2210 // Array of nonces keyed by filename
2211 $known_files = array();
2212 // Array of backup times keyed by nonce
2213 $known_nonces = array();
2214 $changes = false;
2215 $site_name = iwp_getSiteName();
2216
2217 $backupable_entities = $this->get_backupable_file_entities(true, false);
2218
2219 $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
2220 if (!is_array($backup_history)) $backup_history = array();
2221 $iwp_backup_dir = $this->backups_dir_location();
2222 if (!is_dir($iwp_backup_dir)) return;
2223
2224 $accept = apply_filters('IWP_accept_archivename', array());
2225 if (!is_array($accept)) $accept = array();
2226 // Process what is known from the database backup history; this means populating $known_files and $known_nonces
2227 foreach ($backup_history as $btime => $bdata) {
2228 $found_file = false;
2229 foreach ($bdata as $key => $values) {
2230 if ('db' != $key && !isset($backupable_entities[$key])) continue;
2231 // Record which set this file is found in
2232 if (!is_array($values)) $values=array($values);
2233 foreach ($values as $val) {
2234 if (!is_string($val)) continue;
2235 if (preg_match('/^'.$site_name.'backup_([\-0-9]{15})_.*_([0-9a-f]{12})-[\-a-z]+([0-9]+)?+(\.(zip|gz|gz\.crypt))?$/i', $val, $matches)) {
2236 $nonce = $matches[2];
2237 if (isset($bdata['service']) && ($bdata['service'] === 'none' || (is_array($bdata['service']) && (array('none') === $bdata['service'] || (1 == count($bdata['service']) && isset($bdata['service'][0]) && empty($bdata['service'][0]))))) && !is_file($iwp_backup_dir.'/'.$val)) {
2238 # File without remote storage is no longer present
2239 } else {
2240 $found_file = true;
2241 $known_files[$val] = $nonce;
2242 $known_nonces[$nonce] = (empty($known_nonces[$nonce]) || $known_nonces[$nonce]<100) ? $btime : min($btime, $known_nonces[$nonce]);
2243 }
2244 } else {
2245 $accepted = false;
2246 foreach ($accept as $fkey => $acc) {
2247 if (preg_match('/'.$acc['pattern'].'/i', $val)) $accepted = $fkey;
2248 }
2249 if (!empty($accepted) && (false != ($btime = apply_filters('IWP_foreign_gettime', false, $accepted, $val))) && $btime > 0) {
2250 $found_file = true;
2251 # Generate a nonce; this needs to be deterministic and based on the filename only
2252 $nonce = substr(md5($val), 0, 12);
2253 $known_files[$val] = $nonce;
2254 $known_nonces[$nonce] = (empty($known_nonces[$nonce]) || $known_nonces[$nonce]<100) ? $btime : min($btime, $known_nonces[$nonce]);
2255 }
2256 }
2257 }
2258 }
2259 if (!$found_file) {
2260 # File recorded as being without remote storage is no longer present - though it may in fact exist in remote storage, and this will be picked up later
2261 unset($backup_history[$btime]);
2262 $changes = true;
2263 }
2264 }
2265
2266 $remotefiles = array();
2267 $remotesizes = array();
2268
2269 if (!$handle = opendir($iwp_backup_dir)) return;
2270
2271 // See if there are any more files in the local directory than the ones already known about
2272 while (false !== ($entry = readdir($handle))) {
2273 $accepted_foreign = false;
2274 $potmessage = false;
2275
2276 if ($only_add_this_file !== false && $entry != $only_add_this_file['file']) continue;
2277
2278 if ('.' == $entry || '..' == $entry) continue;
2279 # TODO: Make compatible with Incremental naming
2280 if (preg_match('/^'.$site_name.'backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $entry, $matches)) {
2281
2282 // Interpret the time as one from the blog's local timezone, rather than as UTC
2283 # $matches[1] is YYYY-MM-DD-HHmm, to be interpreted as being the local timezone
2284 $btime2 = strtotime($matches[1]);
2285 $btime = (!empty($gmt_offset)) ? $btime2 - $gmt_offset*3600 : $btime2;
2286 $nonce = $matches[2];
2287 $type = $matches[3];
2288 if ('db' == $type) {
2289 $type .= (!empty($matches[4])) ? $matches[4] : '';
2290 $index = 0;
2291 } else {
2292 $index = (empty($matches[4])) ? '0' : (max((int)$matches[4]-1,0));
2293 }
2294 $itext = ($index == 0) ? '' : $index;
2295 } elseif (false != ($accepted_foreign = apply_filters('IWP_accept_foreign', false, $entry)) && false !== ($btime = apply_filters('IWP_foreign_gettime', false, $accepted_foreign, $entry))) {
2296 $nonce = substr(md5($entry), 0, 12);
2297 $type = (preg_match('/\.sql(\.(bz2|gz))?$/i', $entry) || preg_match('/-database-([-0-9]+)\.zip$/i', $entry) || preg_match('/backup_db_/', $entry)) ? 'db' : 'wpcore';
2298 $index = apply_filters('IWP_accepted_foreign_index', 0, $entry, $accepted_foreign);
2299 $itext = $index ? $index : '';
2300 $potmessage = array(
2301 'code' => 'foundforeign_'.md5($entry),
2302 'desc' => $entry,
2303 'method' => '',
2304 'message' => sprintf(__('Backup created by: %s.', 'InfiniteWP'), $accept[$accepted_foreign]['desc'])
2305 );
2306 } elseif ('.zip' == strtolower(substr($entry, -4, 4)) || preg_match('/\.sql(\.(bz2|gz))?$/i', $entry)) {
2307 $potmessage = array(
2308 'code' => 'possibleforeign_'.md5($entry),
2309 'desc' => $entry,
2310 'method' => '',
2311 'message' => __('This file does not appear to be an InfiniteWP backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'InfiniteWP')
2312 );
2313 $messages[$potmessage['code']] = $potmessage;
2314 continue;
2315 } else {
2316 continue;
2317 }
2318 // The time from the filename does not include seconds. Need to identify the seconds to get the right time
2319 if (isset($known_nonces[$nonce])) {
2320 $btime_exact = $known_nonces[$nonce];
2321 # TODO: If the btime we had was more than 60 seconds earlier, then this must be an increment - we then need to change the $backup_history array accordingly. We can pad the '60 second' test, as there's no option to run an increment more frequently than every 4 hours (though someone could run one manually from the CLI)
2322 if ($btime > 100 && $btime_exact - $btime > 60 && !empty($backup_history[$btime_exact])) {
2323 # TODO: This needs testing
2324 # The code below assumes that $backup_history[$btime] is presently empty
2325 # Re-key array, indicating the newly-found time to be the start of the backup set
2326 $backup_history[$btime] = $backup_history[$btime_exact];
2327 unset($backup_history[$btime_exact]);
2328 $btime_exact = $btime;
2329 }
2330 $btime = $btime_exact;
2331 }
2332 if ($btime <= 100) continue;
2333 $fs = @filesize($iwp_backup_dir.'/'.$entry);
2334
2335 if (!isset($known_files[$entry])) {
2336 $changes = true;
2337 if (is_array($potmessage)) $messages[$potmessage['code']] = $potmessage;
2338 if (is_array($only_add_this_file)) {
2339 if (isset($only_add_this_file['label'])) $backup_history[$btime]['label'] = $only_add_this_file['label'];
2340 $backup_history[$btime]['native'] = false;
2341 } elseif ('db' == $type && !$accepted_foreign) {
2342 list ($mess, $warn, $err, $info) = $this->analyse_db_file(false, array(), $iwp_backup_dir.'/'.$entry, true);
2343 if (!empty($info['label'])) {
2344 $backup_history[$btime]['label'] = $info['label'];
2345 }
2346 if (!empty($info['created_by_version'])) {
2347 $backup_history[$btime]['created_by_version'] = $info['created_by_version'];
2348 }
2349 }
2350 }
2351
2352 # TODO: Code below here has not been reviewed or adjusted for compatibility with incremental backups
2353 # Make sure we have the right list of services
2354 $current_services = (!empty($backup_history[$btime]) && !empty($backup_history[$btime]['service'])) ? $backup_history[$btime]['service'] : array();
2355 if (is_string($current_services)) $current_services = array($current_services);
2356 if (!is_array($current_services)) $current_services = array();
2357 if (!empty($remotefiles[$entry])) {
2358 if (0 == count(array_diff($current_services, $remotefiles[$entry]))) {
2359 $backup_history[$btime]['service'] = $remotefiles[$entry];
2360 $changes = true;
2361 }
2362 # Get the right size (our local copy may be too small)
2363 foreach ($remotefiles[$entry] as $rem) {
2364 if (!empty($rem['size']) && $rem['size'] > $fs) {
2365 $fs = $rem['size'];
2366 $changes = true;
2367 }
2368 }
2369 # Remove from $remotefiles, so that we can later see what was left over
2370 unset($remotefiles[$entry]);
2371 } else {
2372 # Not known remotely
2373 if (!empty($backup_history[$btime])) {
2374 if (empty($backup_history[$btime]['service']) || ('none' !== $backup_history[$btime]['service'] && '' !== $backup_history[$btime]['service'] && array('none') !== $backup_history[$btime]['service'])) {
2375 $backup_history[$btime]['service'] = 'none';
2376 $changes = true;
2377 }
2378 } else {
2379 $backup_history[$btime]['service'] = 'none';
2380 $changes = true;
2381 }
2382 }
2383
2384 $backup_history[$btime][$type][$index] = $entry;
2385 if ($fs > 0) $backup_history[$btime][$type.$itext.'-size'] = $fs;
2386 $backup_history[$btime]['nonce'] = $nonce;
2387 if (!empty($accepted_foreign)) $backup_history[$btime]['meta_foreign'] = $accepted_foreign;
2388 }
2389
2390 # Any found in remote storage that we did not previously know about?
2391 # Compare $remotefiles with $known_files / $known_nonces, and adjust $backup_history
2392 if (count($remotefiles) > 0) {
2393
2394 # $backup_history[$btime]['nonce'] = $nonce
2395 foreach ($remotefiles as $file => $services) {
2396 if (!preg_match('/^'.$site_name.'backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $file, $matches)) continue;
2397 $nonce = $matches[2];
2398 $type = $matches[3];
2399 if ('db' == $type) {
2400 $index = 0;
2401 $type .= !empty($matches[4]) ? $matches[4] : '';
2402 } else {
2403 $index = (empty($matches[4])) ? '0' : (max((int)$matches[4]-1,0));
2404 }
2405 $itext = ($index == 0) ? '' : $index;
2406 $btime2 = strtotime($matches[1]);
2407 $btime = (!empty($gmt_offset)) ? $btime2 - $gmt_offset*3600 : $btime2;
2408
2409 if (isset($known_nonces[$nonce])) $btime = $known_nonces[$nonce];
2410 if ($btime <= 100) continue;
2411 # Remember that at this point, we already know that the file is not known about locally
2412 if (isset($backup_history[$btime])) {
2413 if (!isset($backup_history[$btime]['service']) || ((is_array($backup_history[$btime]['service']) && $backup_history[$btime]['service'] !== $services) || is_string($backup_history[$btime]['service']) && (1 != count($services) || $services[0] !== $backup_history[$btime]['service']))) {
2414 $changes = true;
2415 $backup_history[$btime]['service'] = $services;
2416 $backup_history[$btime]['nonce'] = $nonce;
2417 }
2418 if (!isset($backup_history[$btime][$type][$index])) {
2419 $changes = true;
2420 $backup_history[$btime][$type][$index] = $file;
2421 $backup_history[$btime]['nonce'] = $nonce;
2422 if (!empty($remotesizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remotesizes[$file];
2423 }
2424 } else {
2425 $changes = true;
2426 $backup_history[$btime]['service'] = $services;
2427 $backup_history[$btime][$type][$index] = $file;
2428 $backup_history[$btime]['nonce'] = $nonce;
2429 if (!empty($remotesizes[$file])) $backup_history[$btime][$type.$itext.'-size'] = $remotesizes[$file];
2430 $backup_history[$btime]['native'] = false;
2431 $messages['nonnative'] = array(
2432 'message' => __('One or more backups has been added from scanning remote storage; note that these backups will not be automatically deleted through the "retain" settings; if/when you wish to delete them then you must do so manually.', 'InfiniteWP'),
2433 'code' => 'nonnative',
2434 'desc' => '',
2435 'method' => ''
2436 );
2437 }
2438
2439 }
2440 }
2441
2442 if ($changes) IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history);
2443
2444 return $messages;
2445
2446 }
2447
2448 private function backup_finish($cancel_event, $do_cleanup, $allow_email, $resumption_no, $force_abort = false) {
2449 global $wpdb,$iwp_mmb_core;
2450
2451 if (!empty($this->semaphore)) $this->semaphore->unlock();
2452
2453 $delete_jobdata = false;
2454
2455 // The valid use of $do_cleanup is to indicate if in fact anything exists to clean up (if no job really started, then there may be nothing)
2456
2457 // 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.
2458 if (0 == $this->error_count() || $force_abort) {
2459 if ($do_cleanup) {
2460 $this->log("There were no errors in the uploads, so the 'resume' event ($cancel_event) is being unscheduled");
2461 # This apparently-worthless setting of metadata before deleting it is for the benefit of a WP install seen where wp_clear_scheduled_hook() and delete_transient() apparently did nothing (probably a faulty cache)
2462 $this->jobdata_set('jobstatus', 'finished');
2463 wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event, $this->nonce));
2464 # This should be unnecessary - even if it does resume, all should be detected as finished; but I saw one very strange case where it restarted, and repeated everything; so, this will help
2465 wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+1, $this->nonce));
2466 wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+2, $this->nonce));
2467 wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+3, $this->nonce));
2468 wp_clear_scheduled_hook('IWP_backup_resume', array($cancel_event+4, $this->nonce));
2469 $delete_jobdata = true;
2470 }
2471 } else {
2472 $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
2473 $this->jobdata_set('jobstatus', 'resumingforerrors');
2474 # If there were no errors before moving to the upload stage, on the first run, then bring the resumption back very close. Since this is only attempted on the first run, it is really only an efficiency thing for a quicker finish if there was an unexpected networking event. We don't want to do it straight away every time, as it may be that the cloud service is down - and might be up in 5 minutes time. This was added after seeing a case where resumption 0 got to run for 10 hours... and the resumption 7 that should have picked up the uploading of 1 archive that failed never occurred.
2475 if (isset($this->error_count_before_cloud_backup) && 0 === $this->error_count_before_cloud_backup) {
2476 if (0 == $resumption_no) {
2477 $this->reschedule(60);
2478 } else {
2479 // Added 27/Feb/2016 - though the cloud service seems to be down, we still don't want to wait too long
2480 $resume_interval = $this->jobdata_get('resume_interval');
2481
2482 // 15 minutes + 2 for each resumption (a modest back-off)
2483 $max_interval = 900 + $resumption_no * 120;
2484 if ($resume_interval > $max_interval) {
2485 $this->reschedule($max_interval);
2486 }
2487 }
2488 }
2489 }
2490
2491 // Send the results email if appropriate, which means:
2492 // - The caller allowed it (which is not the case in an 'empty' run)
2493 // - And: An email address was set (which must be so in email mode)
2494 // And one of:
2495 // - Debug mode
2496 // - There were no errors (which means we completed and so this is the final run - time for the final report)
2497 // - It was the tenth resumption; everything failed
2498 # Save the jobdata's state for the reporting - because it might get changed (e.g. incremental backup is scheduled)
2499 $jobdata_as_was = $this->jobdata;
2500
2501 // Make sure that the final status is shown
2502 if ($force_abort) {
2503 $send_an_email = true;
2504 $final_message = __('The backup was aborted by the user', 'InfiniteWP');
2505 } elseif (0 == $this->error_count()) {
2506 $send_an_email = true;
2507 $service = $this->jobdata_get('service');
2508 $remote_sent = (!empty($service) && ((is_array($service) && in_array('remotesend', $service)) || 'remotesend' === $service)) ? true : false;
2509 $userid = get_current_user_id();
2510 $backup_files = $this->jobdata_get('backup_files');
2511 $backup_database = $this->jobdata_get('backup_database');
2512 $what = 'full';
2513 if ($backup_files == 'no') {
2514 $what = 'db';
2515 }elseif($backup_database == 'no'){
2516 $what = 'files';
2517 }
2518 if (0 == $this->error_count('warning')) {
2519 $final_message = __('The backup apparently succeeded and is now complete', 'InfiniteWP');
2520 # Ensure it is logged in English. Not hugely important; but helps with a tiny number of really broken setups in which the options cacheing is broken
2521 if ('The backup apparently succeeded and is now complete' != $final_message) {
2522 $this->log('The backup apparently succeeded and is now complete');
2523 }
2524 delete_option('IWP_jobdata_'.$this->nonce);
2525 $iwp_mmb_core->iwp_update_option($option_name = 'IWP_backup_status',$option_value = '0');
2526 $GLOBALS['iwp_mmb_activities_log']->iwp_mmb_save_iwp_activities('backup', 'multiCallNow', 'direct', array('what' => $what), $userid);
2527 } else {
2528 $final_message = __('The backup apparently succeeded (with warnings) and is now complete','InfiniteWP');
2529 if ('The backup apparently succeeded (with warnings) and is now complete' != $final_message) {
2530 $this->log('The backup apparently succeeded (with warnings) and is now complete');
2531 }
2532 $GLOBALS['iwp_mmb_activities_log']->iwp_mmb_save_iwp_activities('backup', 'multiCallNow', 'direct', array('what' => $what), $userid);
2533 delete_option('IWP_jobdata_'.$this->nonce);
2534 $iwp_mmb_core->iwp_update_option($option_name = 'IWP_backup_status',$option_value = '0');
2535 }
2536 if ($remote_sent && !$force_abort) $final_message .= '. '.__('To complete your migration/clone, you should now log in to the remote site and restore the backup set.', 'InfiniteWP');
2537 if ($do_cleanup) $delete_jobdata = apply_filters('IWP_backup_complete', $delete_jobdata);
2538 } elseif (false == $this->newresumption_scheduled) {
2539 $send_an_email = true;
2540 wp_clear_scheduled_hook('IWP_backup_resume');
2541 $this->kill_new_backup(array('result_id'=>$this->nonce));
2542 $final_message = __('The backup attempt has finished, apparently unsuccessfully', 'InfiniteWP');
2543 } else {
2544 // There are errors, but a resumption will be attempted
2545 $final_message = __('The backup has not finished; a resumption is scheduled', 'InfiniteWP');
2546 }
2547
2548 global $IWP_backup;
2549
2550 if ($force_abort) $jobdata_as_was['aborted'] = true;
2551
2552 # Make sure this is the final message logged (so it remains on the dashboard)
2553 $this->log($final_message);
2554
2555 @fclose($this->logfile_handle);
2556 $this->logfile_handle = null;
2557
2558 // This is left until last for the benefit of the front-end UI, which then gets maximum chance to display the 'finished' status
2559 if ($delete_jobdata) {
2560 delete_option('IWP_jobdata_'.$this->nonce);
2561 $iwp_mmb_core->iwp_update_option($option_name = 'IWP_backup_status',$option_value = '0');
2562 }
2563
2564 }
2565
2566 // This function returns 'true' if mod_rewrite could be detected as unavailable; a 'false' result may mean it just couldn't find out the answer
2567 public function mod_rewrite_unavailable($check_if_in_use_first = true) {
2568 if (function_exists('apache_get_modules')) {
2569 global $wp_rewrite;
2570 $mods = apache_get_modules();
2571 if ((!$check_if_in_use_first || $wp_rewrite->using_mod_rewrite_permalinks()) && ((in_array('core', $mods) || in_array('http_core', $mods)) && !in_array('mod_rewrite', $mods))) {
2572 return true;
2573 }
2574 }
2575 return false;
2576 }
2577
2578 public function error_count($level = 'error') {
2579 $count = 0;
2580 foreach ($this->errors as $err) {
2581 if (('error' == $level && (is_string($err) || is_wp_error($err))) || (is_array($err) && $level == $err['level']) ) { $count++; }
2582 }
2583 return $count;
2584 }
2585
2586 public function list_errors() {
2587 echo '<ul style="list-style: disc inside;">';
2588 foreach ($this->errors as $err) {
2589 if (is_wp_error($err)) {
2590 foreach ($err->get_error_messages() as $msg) {
2591 echo '<li>'.htmlspecialchars($msg).'<li>';
2592 }
2593 } elseif (is_array($err) && ('error' == $err['level'] || 'warning' == $err['level'])) {
2594 echo "<li>".htmlspecialchars($err['message'])."</li>";
2595 } elseif (is_string($err)) {
2596 echo "<li>".htmlspecialchars($err)."</li>";
2597 } else {
2598 print "<li>".print_r($err,true)."</li>";
2599 }
2600 }
2601 echo '</ul>';
2602 }
2603
2604 private function save_last_backup($backup_array) {
2605 $success = ($this->error_count() == 0) ? 1 : 0;
2606 $last_backup = apply_filters('IWP_save_last_backup', array(
2607 'backup_time' => $this->backup_time,
2608 'backup_array' => $backup_array,
2609 'success' => $success,
2610 'errors' => $this->errors,
2611 'backup_nonce' => $this->nonce
2612 ));
2613 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_last_backup', $last_backup, false);
2614 }
2615
2616 # $handle must be either false or a WPDB class (or extension thereof). Other options are not yet fully supported.
2617 public function check_db_connection($handle = false, $logit = false, $reschedule = false) {
2618
2619 $type = false;
2620 if (false === $handle || is_a($handle, 'wpdb')) {
2621 $type='wpdb';
2622 } elseif (is_resource($handle)) {
2623 # Expected: string(10) "mysql link"
2624 $type=get_resource_type($handle);
2625 } elseif (is_object($handle) && is_a($handle, 'mysqli')) {
2626 $type='mysqli';
2627 }
2628
2629 if (false === $type) return -1;
2630
2631 $db_connected = -1;
2632
2633 if ('mysql link' == $type || 'mysqli' == $type) {
2634 if ('mysql link' == $type && @mysql_ping($handle)) return true;
2635 if ('mysqli' == $type && @mysqli_ping($handle)) return true;
2636
2637 for ( $tries = 1; $tries <= 5; $tries++ ) {
2638 # to do, if ever needed
2639 // if ( $this->db_connect( false ) ) return true;
2640 // sleep( 1 );
2641 }
2642
2643 } elseif ('wpdb' == $type) {
2644 if (false === $handle || (is_object($handle) && 'wpdb' == get_class($handle))) {
2645 global $wpdb;
2646 $handle = $wpdb;
2647 }
2648 if (method_exists($handle, 'check_connection') && (!defined('IWP_SUPPRESS_CONNECTION_CHECKS') || !IWP_SUPPRESS_CONNECTION_CHECKS)) {
2649 if (!$handle->check_connection(false)) {
2650 if ($logit) $this->log("The database went away, and could not be reconnected to");
2651 # Almost certainly a no-op
2652 if ($reschedule) $this->reschedule(60);
2653 $db_connected = false;
2654 } else {
2655 $db_connected = true;
2656 }
2657 }
2658 }
2659
2660 return $db_connected;
2661
2662 }
2663
2664 // This should be called whenever a file is successfully uploaded
2665 public function uploaded_file($file, $force = false) {
2666
2667 global $IWP_backup;
2668
2669 $db_connected = $this->check_db_connection(false, true, true);
2670
2671 $service = empty($IWP_backup->current_service) ? '' : $IWP_backup->current_service;
2672 $shash = $service.'-'.md5($file);
2673
2674 $this->jobdata_set("uploaded_".$shash, 'yes');
2675
2676 if ($force || !empty($IWP_backup->last_service)) {
2677 $hash = md5($file);
2678 $this->log("Recording as successfully uploaded: $file ($hash)");
2679 $this->jobdata_set('uploaded_lastreset', $this->current_resumption);
2680 $this->jobdata_set("uploaded_".$hash, 'yes');
2681 } else {
2682 $this->log("Recording as successfully uploaded: $file (".$IWP_backup->current_service.", more services to follow)");
2683 }
2684
2685 $upload_status = $this->jobdata_get('uploading_substatus');
2686 if (is_array($upload_status) && isset($upload_status['i'])) {
2687 $upload_status['i']++;
2688 $upload_status['p']=0;
2689 $this->jobdata_set('uploading_substatus', $upload_status);
2690 }
2691
2692 # Really, we could do this immediately when we realise the DB has gone away. This is just for the probably-impossible case that a DB write really can still succeed. But, we must abort before calling delete_local(), as the removal of the local file can cause it to be recreated if the DB is out of sync with the fact that it really is already uploaded
2693 if (false === $db_connected) {
2694 $this->record_still_alive();
2695 die;
2696 }
2697
2698 // Delete local files immediately if the option is set
2699 // Where we are only backing up locally, only the "prune" function should do deleting
2700 $service = $this->jobdata_get('service');
2701 if (!empty($IWP_backup->last_service) && ($service !== '' && ((is_array($service) && count($service)>0 && (count($service) > 1 || ($service[0] != '' && $service[0] != 'none'))) || (is_string($service) && $service !== 'none')))) {
2702 $this->delete_local($file);
2703 }
2704 }
2705
2706 public function is_uploaded($file, $service = '') {
2707 $hash = $service.(('' == $service) ? '' : '-').md5($file);
2708 return ($this->jobdata_get("uploaded_$hash") === "yes") ? true : false;
2709 }
2710
2711 private function delete_local($file) {
2712 $log = "Deleting local file: $file: ";
2713 if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_delete_local')) {
2714 $fullpath = $this->backups_dir_location().'/'.$file;
2715
2716 //check to make sure it exists before removing
2717 if(realpath($fullpath)){
2718 $deleted = unlink($fullpath);
2719 $this->log($log.(($deleted) ? 'OK' : 'failed'));
2720 return $deleted;
2721 }
2722 } else {
2723 $this->log($log."skipped: user has unchecked IWP_delete_local option");
2724 }
2725 return true;
2726 }
2727
2728 // This function is not needed for backup success, according to the design, but it helps with efficient scheduling
2729 private function reschedule_if_needed() {
2730 // If nothing is scheduled, then return
2731 if (empty($this->newresumption_scheduled)) return;
2732 $time_now = time();
2733 $time_away = $this->newresumption_scheduled - $time_now;
2734 // 45 is chosen because it is 15 seconds more than what is used to detect recent activity on files (file mod times). (If we use exactly the same, then it's more possible to slightly miss each other)
2735 if ($time_away >1 && $time_away <= 45) {
2736 $this->log('The scheduled resumption is within 45 seconds - will reschedule');
2737 // Push 45 seconds into the future
2738 // $this->reschedule(60);
2739 // Increase interval generally by 45 seconds, on the assumption that our prior estimates were innaccurate (i.e. not just 45 seconds *this* time)
2740 $this->increase_resume_and_reschedule(45);
2741 }
2742 }
2743
2744 public function reschedule($how_far_ahead, $first_call=false) {
2745 // Reschedule - remove presently scheduled event
2746 $next_resumption = $this->current_resumption + 1;
2747 wp_clear_scheduled_hook('IWP_backup_resume', array($next_resumption, $this->nonce));
2748 // Add new event
2749 # This next line may be too cautious; but until 14-Aug-2014, it was 300.
2750 # Update 20-Mar-2015 - lowered from 180
2751 if ($how_far_ahead < 120 && !$first_call) $how_far_ahead=120;
2752 $schedule_for = time() + $how_far_ahead;
2753 $this->log("Rescheduling resumption $next_resumption: moving to $how_far_ahead seconds from now ($schedule_for)");
2754 wp_schedule_single_event($schedule_for, 'IWP_backup_resume', array($next_resumption, $this->nonce));
2755 $this->newresumption_scheduled = $schedule_for;
2756 }
2757
2758 private function increase_resume_and_reschedule($howmuch = 120, $force_schedule = false) {
2759
2760 $resume_interval = max(intval($this->jobdata_get('resume_interval')), ($howmuch === 0) ? 120 : 300);
2761
2762 if (empty($this->newresumption_scheduled) && $force_schedule) {
2763 $this->log("A new resumption will be scheduled to prevent the job ending");
2764 }
2765
2766 $new_resume = $resume_interval + $howmuch;
2767 # It may be that we're increasing for the second (or more) time during a run, and that we already know that the new value will be insufficient, and can be increased
2768 if ($this->opened_log_time > 100 && microtime(true)-$this->opened_log_time > $new_resume) {
2769 $new_resume = ceil(microtime(true)-$this->opened_log_time)+45;
2770 $howmuch = $new_resume-$resume_interval;
2771 }
2772
2773 # This used to be always $new_resume, until 14-Aug-2014. However, people who have very long-running processes can end up with very long times between resumptions as a result.
2774 # Actually, let's not try this yet. I think it is safe, but think there is a more conservative solution available.
2775 #$how_far_ahead = min($new_resume, 600);
2776 $how_far_ahead = $new_resume;
2777 # If it is very long-running, then that would normally be known soon.
2778 # If the interval is already 12 minutes or more, then try the next resumption 10 minutes from now (i.e. sooner than it would have been). Thus, we are guaranteed to get at least 24 minutes of processing in the first 34.
2779 if ($this->current_resumption <= 1 && $new_resume > 720) $how_far_ahead = 600;
2780
2781 if (!empty($this->newresumption_scheduled) || $force_schedule) $this->reschedule($how_far_ahead);
2782 $this->jobdata_set('resume_interval', $new_resume);
2783
2784 $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: $resume_interval + $howmuch = $new_resume");
2785 }
2786
2787 // For detecting another run, and aborting if one was found
2788 public function check_recent_modification($file) {
2789 if (file_exists($file)) {
2790 $time_mod = (int)@filemtime($file);
2791 $time_now = time();
2792 if ($time_mod>100 && ($time_now-$time_mod)<30) {
2793 $this->terminate_due_to_activity($file, $time_now, $time_mod);
2794 }
2795 }
2796 }
2797
2798 public function get_exclude($whichone) {
2799 if ('uploads' == $whichone) {
2800 $exclude = explode(',', IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_uploads_exclude', IWP_DEFAULT_UPLOADS_EXCLUDE));
2801 } elseif ('others' == $whichone) {
2802 $exclude = explode(',', IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_others_exclude', IWP_DEFAULT_OTHERS_EXCLUDE));
2803 } else {
2804 $exclude = apply_filters('IWP_include_'.$whichone.'_exclude', array());
2805 }
2806 return (empty($exclude) || !is_array($exclude)) ? array() : $exclude;
2807 }
2808
2809 public function really_is_writable($dir) {
2810 // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
2811 if (!@is_writable($dir)) return false;
2812 // Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
2813 $rand_file = "$dir/test-".md5(rand().time()).".txt";
2814 while (file_exists($rand_file)) {
2815 $rand_file = "$dir/test-".md5(rand().time()).".txt";
2816 }
2817 $ret = @file_put_contents($rand_file, 'testing...');
2818 @unlink($rand_file);
2819 return ($ret > 0);
2820 }
2821
2822 public function wp_upload_dir() {
2823 if (is_multisite()) {
2824 global $current_site;
2825 switch_to_blog($current_site->blog_id);
2826 }
2827
2828 $wp_upload_dir = wp_upload_dir();
2829
2830 if (is_multisite()) restore_current_blog();
2831
2832 return $wp_upload_dir;
2833 }
2834
2835 public function backup_uploads_dirlist($logit = false) {
2836 # Create an array of directories to be skipped
2837 # Make the values into the keys
2838 $exclude = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_uploads_exclude', IWP_DEFAULT_UPLOADS_EXCLUDE);
2839 if ($logit) $this->log("Exclusion option setting (uploads): ".$exclude);
2840 $skip = array_flip(preg_split("/,/", $exclude));
2841 $wp_upload_dir = $this->wp_upload_dir();
2842 $uploads_dir = $wp_upload_dir['basedir'];
2843 return $this->compile_folder_list_for_backup($uploads_dir, array(), $skip);
2844 }
2845
2846 public function backup_others_dirlist($logit = false) {
2847 # Create an array of directories to be skipped
2848 # Make the values into the keys
2849 $exclude = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_include_others_exclude', IWP_DEFAULT_OTHERS_EXCLUDE);
2850 if ($logit) $this->log("Exclusion option setting (others): ".$exclude);
2851 $skip = array_flip(preg_split("/,/", $exclude));
2852 $file_entities = $this->get_backupable_file_entities(false);
2853
2854 # Keys = directory names to avoid; values = the label for that directory (used only in log files)
2855 #$avoid_these_dirs = array_flip($file_entities);
2856 $avoid_these_dirs = array();
2857 foreach ($file_entities as $type => $dirs) {
2858 if (is_string($dirs)) {
2859 $avoid_these_dirs[$dirs] = $type;
2860 } elseif (is_array($dirs)) {
2861 foreach ($dirs as $dir) {
2862 $avoid_these_dirs[$dir] = $type;
2863 }
2864 }
2865 }
2866 return $this->compile_folder_list_for_backup(WP_CONTENT_DIR, $avoid_these_dirs, $skip);
2867 }
2868
2869 public function backup_more_dirlist($whichdir = false) {
2870 # Create an array of directories to be skipped
2871 # Make the values into the keys
2872
2873
2874
2875 # Keys = directory names to avoid; values = the label for that directory (used only in log files)
2876 #$avoid_these_dirs = array_flip($file_entities);
2877 $avoid_these_dirs = array();
2878 $skip = array();
2879 $dir_list = $this->compile_folder_list_for_backup($whichdir, $avoid_these_dirs, $skip);
2880 $include = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_default_includes', IWP_DEFAULT_INCLUDES);
2881 foreach ($dir_list as $key => $value) {
2882 if (!in_array(str_replace(ABSPATH, '', $value), explode(',',$include))) {
2883 unset($dir_list[$key]);
2884 }
2885 }
2886 return $dir_list;
2887 }
2888
2889 // Add backquotes to tables and db-names in SQL queries. Taken from phpMyAdmin.
2890 public function backquote($a_name) {
2891 if (!empty($a_name) && '*' != $a_name) {
2892 if (is_array($a_name)) {
2893 $result = array();
2894 foreach ($a_name as $key => $val) {
2895 $result[$key] = '`'.$val.'`';
2896 }
2897 return $result;
2898 } else {
2899 return '`'.$a_name.'`';
2900 }
2901 } else {
2902 return $a_name;
2903 }
2904 }
2905
2906 public function strip_dirslash($string) {
2907 return preg_replace('#/+(,|$)#', '$1', $string);
2908 }
2909
2910 public function remove_empties($list) {
2911 if (!is_array($list)) return $list;
2912 foreach ($list as $ind => $entry) {
2913 if (empty($entry)) unset($list[$ind]);
2914 }
2915 return $list;
2916 }
2917
2918 // avoid_these_dirs and skip_these_dirs ultimately do the same thing; but avoid_these_dirs takes full paths whereas skip_these_dirs takes basenames; and they are logged differently (dirs in avoid are potentially dangerous to include; skip is just a user-level preference). They are allowed to overlap.
2919 public function compile_folder_list_for_backup($backup_from_inside_dir, $avoid_these_dirs, $skip_these_dirs) {
2920
2921 // Entries in $skip_these_dirs are allowed to end in *, which means "and anything else as a suffix". It's not a full shell glob, but it covers what is needed to-date.
2922
2923 $dirlist = array();
2924 $added = 0;
2925
2926 $this->log('Looking for candidates to back up in: '.$backup_from_inside_dir);
2927 $iwp_backup_dir = $this->backups_dir_location();
2928
2929 if (is_file($backup_from_inside_dir)) {
2930 array_push($dirlist, $backup_from_inside_dir);
2931 $added++;
2932 $this->log("finding files: $backup_from_inside_dir: adding to list ($added)");
2933 } elseif ($handle = opendir($backup_from_inside_dir)) {
2934
2935 while (false !== ($entry = readdir($handle))) {
2936 // $candidate: full path; $entry = one-level
2937 $candidate = $backup_from_inside_dir.'/'.$entry;
2938 if ($entry != "." && $entry != "..") {
2939 if (isset($avoid_these_dirs[$candidate])) {
2940 $this->log("finding files: $entry: skipping: this is the ".$avoid_these_dirs[$candidate]." directory");
2941 } elseif ($candidate == $iwp_backup_dir) {
2942 $this->log("finding files: $entry: skipping: this is the InfiniteWP directory");
2943 } elseif (isset($skip_these_dirs[$entry])) {
2944 $this->log("finding files: $entry: skipping: excluded by options");
2945 } else {
2946 $add_to_list = true;
2947 // Now deal with entries in $skip_these_dirs ending in * or starting with *
2948 foreach ($skip_these_dirs as $skip => $sind) {
2949 if ('*' == substr($skip, -1, 1) && '*' == substr($skip, 0, 1) && strlen($skip) > 2) {
2950 if (strpos($entry, substr($skip, 1, strlen($skip-2))) !== false) {
2951 $this->log("finding files: $entry: skipping: excluded by options (glob)");
2952 $add_to_list = false;
2953 }
2954 } elseif ('*' == substr($skip, -1, 1) && strlen($skip) > 1) {
2955 if (substr($entry, 0, strlen($skip)-1) == substr($skip, 0, strlen($skip)-1)) {
2956 $this->log("finding files: $entry: skipping: excluded by options (glob)");
2957 $add_to_list = false;
2958 }
2959 } elseif ('*' == substr($skip, 0, 1) && strlen($skip) > 1) {
2960 if (strlen($entry) >= strlen($skip)-1 && substr($entry, (strlen($skip)-1)*-1) == substr($skip, 1)) {
2961 $this->log("finding files: $entry: skipping: excluded by options (glob)");
2962 $add_to_list = false;
2963 }
2964 }
2965 }
2966 if ($add_to_list) {
2967 array_push($dirlist, $candidate);
2968 $added++;
2969 $skip_dblog = (($added > 50 && 0 != $added % 100) || ($added > 2000 && 0 != $added % 500));
2970 $this->log("finding files: $entry: adding to list ($added)", 'notice', false, $skip_dblog);
2971 }
2972 }
2973 }
2974 }
2975 @closedir($handle);
2976 } else {
2977 $this->log('ERROR: Could not read the directory: '.$backup_from_inside_dir);
2978 $this->log(__('Could not read the directory', 'InfiniteWP').': '.$backup_from_inside_dir, 'error');
2979 }
2980
2981 return $dirlist;
2982
2983 }
2984
2985 private function save_backup_history($backup_array) {
2986 if(is_array($backup_array)) {
2987 $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
2988 $backup_history = (is_array($backup_history)) ? $backup_history : array();
2989 $backup_array['nonce'] = $this->nonce;
2990 $backup_array['service'] = $this->jobdata_get('service');
2991 if (!empty($backup_array['service'][0]) && $backup_array['service'][0] != 'none') {
2992 $service = 'IWP_'.$backup_array['service'][0];
2993 $backup_array['service_setting'] = IWP_MMB_Backup_Options::get_iwp_backup_option($service);
2994 }
2995 if ('' != ($label = $this->jobdata_get('label', ''))) $backup_array['label'] = $label;
2996 if ('' != ($backup_name = $this->jobdata_get('backup_name', ''))) $backup_array['backup_name'] = $backup_name;
2997 $backup_array['created_by_version'] = $this->version;
2998 $backup_array['is_multisite'] = is_multisite() ? true : false;
2999 $backup_array['wp_content_url'] = content_url();
3000 $backup_array['wp_content_path'] = WP_CONTENT_DIR;
3001 $backup_array['old_url'] = get_option('siteurl');
3002 $backup_array['old_file_path'] = ABSPATH;
3003 $remotesend_info = $this->jobdata_get('remotesend_info');
3004 if (is_array($remotesend_info) && !empty($remotesend_info['url'])) $backup_array['remotesend_url'] = $remotesend_info['url'];
3005 if (false != ($autobackup = $this->jobdata_get('is_autobackup', false))) $backup_array['autobackup'] = true;
3006 $backup_history[$this->backup_time] = $backup_array;
3007 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, false);
3008 } else {
3009 $this->log('Could not save backup history because we have no backup array. Backup probably failed.');
3010 $this->log(__('Could not save backup history because we have no backup array. Backup probably failed.','InfiniteWP'), 'error');
3011 }
3012 }
3013
3014 public function is_db_encrypted($file) {
3015 return preg_match('/\.crypt$/i', $file);
3016 }
3017
3018 public function get_backup_history($timestamp = false) {
3019 $backup_history = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_backup_history');
3020 // The line below actually *introduces* a race condition
3021 // global $wpdb;
3022 // $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='IWP_backup_history'")));
3023 if (is_array($backup_history)) {
3024 krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop.
3025 } else {
3026 $backup_history = array();
3027 }
3028 if (!$timestamp) return $backup_history;
3029 return (isset($backup_history[$timestamp])) ? $backup_history[$timestamp] : array();
3030 }
3031
3032 public function terminate_due_to_activity($file, $time_now, $time_mod, $increase_resumption = true) {
3033 # We check-in, to avoid 'no check in last time!' detectors firing
3034 $this->record_still_alive();
3035 $file_size = file_exists($file) ? round(filesize($file)/1024,1). 'KB' : 'n/a';
3036 $this->log("Terminate: ".basename($file)." exists with activity within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".(floor($time_now-$time_mod)).", size=$file_size). This likely means that another InfiniteWP run is at work; so we will exit.");
3037 $increase_by = ($increase_resumption) ? 120 : 0;
3038 $this->increase_resume_and_reschedule($increase_by, true);
3039 if (!defined('IWP_ALLOW_RECENT_ACTIVITY') || true != IWP_ALLOW_RECENT_ACTIVITY) die;
3040 }
3041
3042 # Replace last occurence
3043 public function str_lreplace($search, $replace, $subject) {
3044 $pos = strrpos($subject, $search);
3045 if($pos !== false) $subject = substr_replace($subject, $replace, $pos, strlen($search));
3046 return $subject;
3047 }
3048
3049 public function str_replace_once($needle, $replace, $haystack) {
3050 $pos = strpos($haystack, $needle);
3051 return ($pos !== false) ? substr_replace($haystack,$replace,$pos,strlen($needle)) : $haystack;
3052 }
3053
3054 /*
3055 If files + db are on different schedules but are scheduled for the same time, then combine them
3056 $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => $recurrence, 'args' => $args, 'interval' => $schedules[$recurrence]['interval'] );
3057 See wp_schedule_single_event() and wp_schedule_event() in wp-includes/cron.php
3058 */
3059 public function schedule_event($event) {
3060
3061 static $scheduled = array();
3062
3063
3064 if (is_object($event) && ('IWP_backup' == $event->hook || 'IWP_backup_database' == $event->hook)) {
3065
3066 // Reset the option - but make sure it is saved first so that we can used it (since this hook may be called just before our actual cron task)
3067 $this->combine_jobs_around = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_combine_jobs_around');
3068
3069 IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_combine_jobs_around');
3070
3071 $scheduled[$event->hook] = true;
3072
3073 // This next fragment is wrong: there's only a 'second call' when saving all settings; otherwise, the WP scheduler might just be updating one event. So, there's some inefficieny as the option is wiped and set uselessly at least once when saving settings.
3074 // We only want to take action on the second call (otherwise, our information is out-of-date already)
3075 // If there is no second call, then that's fine - nothing to do
3076 //if (count($scheduled) < 2) {
3077 // return $event;
3078 //}
3079
3080 $backup_scheduled_for = ('IWP_backup' == $event->hook) ? $event->timestamp : wp_next_scheduled('IWP_backup');
3081 $db_scheduled_for = ('IWP_backup_database' == $event->hook) ? $event->timestamp : wp_next_scheduled('IWP_backup_database');
3082
3083 $diff = absint($backup_scheduled_for - $db_scheduled_for);
3084
3085 $margin = (defined('IWP_COMBINE_MARGIN') && is_numeric(IWP_COMBINE_MARGIN)) ? IWP_COMBINE_MARGIN : 600;
3086
3087 if ($backup_scheduled_for && $db_scheduled_for && $diff < $margin) {
3088 // We could change the event parameters; however, this would complicate other code paths (because the WP cron system uses a hash of the parameters as a key, and you must supply the exact parameters to look up events). So, we just set a marker that boot_backup() can pick up on.
3089 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_combine_jobs_around', min($backup_scheduled_for, $db_scheduled_for));
3090 }
3091
3092 }
3093
3094 return $event;
3095
3096 }
3097
3098 /*
3099 This function is both the backup scheduler and a filter callback for saving the option.
3100 It is called in the register_setting for the IWP_interval, which means when the
3101 admin settings are saved it is called.
3102 */
3103 public function schedule_backup($interval) {
3104 $previous_time = wp_next_scheduled('IWP_backup');
3105
3106 // Clear schedule so that we don't stack up scheduled backups
3107 wp_clear_scheduled_hook('IWP_backup');
3108 if ('manual' == $interval) return 'manual';
3109 $previous_interval = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval');
3110
3111 $valid_schedules = wp_get_schedules();
3112 if (empty($valid_schedules[$interval])) $interval = 'daily';
3113
3114 // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today.
3115 $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120;
3116 $first_time = apply_filters('IWP_schedule_firsttime_files', $default_time);
3117
3118 wp_schedule_event($first_time, $interval, 'IWP_backup');
3119
3120 return $interval;
3121 }
3122
3123 public function schedule_backup_database($interval) {
3124 $previous_time = wp_next_scheduled('IWP_backup_database');
3125
3126 // Clear schedule so that we don't stack up scheduled backups
3127 wp_clear_scheduled_hook('IWP_backup_database');
3128 if ('manual' == $interval) return 'manual';
3129
3130 $previous_interval = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_interval_database');
3131
3132 $valid_schedules = wp_get_schedules();
3133 if (empty($valid_schedules[$interval])) $interval = 'daily';
3134
3135 // Try to avoid changing the time is one was already scheduled. This is fairly conservative - we could do more, e.g. check if a backup already happened today.
3136 $default_time = ($interval == $previous_interval && $previous_time>0) ? $previous_time : time()+120;
3137
3138 $first_time = apply_filters('IWP_schedule_firsttime_db', $default_time);
3139 wp_schedule_event($first_time, $interval, 'IWP_backup_database');
3140
3141 return $interval;
3142 }
3143
3144 public function ftp_sanitise($ftp) {
3145 if (is_array($ftp) && !empty($ftp['host']) && preg_match('#ftp(es|s)?://(.*)#i', $ftp['host'], $matches)) {
3146 $ftp['host'] = untrailingslashit($matches[2]);
3147 }
3148 return $ftp;
3149 }
3150
3151 public function s3_sanitise($s3) {
3152 if (is_array($s3) && !empty($s3['path']) && '/' == substr($s3['path'], 0, 1)) {
3153 $s3['path'] = substr($s3['path'], 1);
3154 }
3155 return $s3;
3156 }
3157
3158 public function remove_local_directory($dir, $contents_only = false) {
3159 // PHP 5.3+ only
3160 //foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
3161 // $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());
3162 //}
3163 //return rmdir($dir);
3164
3165 if ($handle = @opendir($dir)) {
3166 while (false !== ($entry = readdir($handle))) {
3167 if ('.' !== $entry && '..' !== $entry) {
3168 if (is_dir($dir.'/'.$entry)) {
3169 $this->remove_local_directory($dir.'/'.$entry, false);
3170 } else {
3171 @unlink($dir.'/'.$entry);
3172 }
3173 }
3174 }
3175 @closedir($handle);
3176 }
3177
3178 return ($contents_only) ? true : rmdir($dir);
3179 }
3180
3181 // Returns without any trailing slash
3182 public function backups_dir_location($allow_cache = true) {
3183
3184 if ($allow_cache && !empty($this->backup_dir)) return $this->backup_dir;
3185
3186 if(!file_exists(IWP_BACKUP_DIR) && !is_dir(IWP_BACKUP_DIR)){
3187 $mkdir = @mkdir(IWP_BACKUP_DIR, 0755, true);
3188 if(!$mkdir){
3189 return array('error' => 'Permission denied; Make sure you have write permission for the wp-content folder.', 'error_code' => 'permission_denied_make_sure_you_have_write_permission_for_the_wp_content_folder');
3190 }
3191 }
3192 if(is_writable(IWP_BACKUP_DIR)){
3193 @file_put_contents(IWP_BACKUP_DIR . '/index.php', ''); //safe
3194
3195 }else{
3196 $chmod = chmod(IWP_BACKUP_DIR, 777);
3197 if(!is_writable(IWP_BACKUP_DIR)){
3198 return array('error' => IWP_BACKUP_DIR.' directory is not writable. Please set 755 or 777 file permission and try again.', 'error_code' => 'backup_dir_is_not_writable');
3199 }
3200 }
3201
3202 $this->backup_dir = IWP_BACKUP_DIR;
3203
3204 return IWP_BACKUP_DIR;
3205 }
3206 /**
3207 * This function creates the correct header when download files
3208 * @param string $fullpath This is the full path to the encrypted file
3209 * @param string $encryption This is the key (salting) used to decrypt the file
3210 * @return heder This will download the fila when via the browser
3211 */
3212 private function spool_crypted_file($fullpath, $encryption) {
3213 if ('' == $encryption) $encryption = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase');
3214 if ('' == $encryption) {
3215 header('Content-type: text/plain');
3216 _e("Decryption failed. The database file is encrypted, but you have no encryption key entered.", 'InfiniteWP');
3217 $this->log('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.', 'error');
3218 } else {
3219
3220
3221 //now decrypt the file and return array
3222 $decrypted_file = $this->decrypt($fullpath, $encryption, true);
3223
3224 //check to ensure there is a response back
3225 if (is_array($decrypted_file)) {
3226 header('Content-type: application/x-gzip');
3227 header("Content-Disposition: attachment; filename=\"".$decrypted_file['basename']."\";");
3228 header("Content-Length: ".filesize($decrypted_file['fullpath']));
3229 readfile($decrypted_file['fullpath']);
3230
3231 //need to remove the file as this is no longer needed on the local server
3232 unlink($decrypted_file['fullpath']);
3233 } else {
3234 header('Content-type: text/plain');
3235 echo __("Decryption failed. The most likely cause is that you used the wrong key.", 'InfiniteWP')." ".__('The decryption key used:', 'InfiniteWP').' '.$encryption;
3236
3237 }
3238 }
3239 }
3240
3241 public function get_mime_type_from_filename($filename, $allow_gzip = true) {
3242 if ('.zip' == substr($filename, -4, 4)) {
3243 return 'application/zip';
3244 } elseif ('.tar' == substr($filename, -4, 4)) {
3245 return 'application/x-tar';
3246 } elseif ('.tar.gz' == substr($filename, -7, 7)) {
3247 return 'application/x-tgz';
3248 } elseif ('.tar.bz2' == substr($filename, -8, 8)) {
3249 return 'application/x-bzip-compressed-tar';
3250 } elseif ($allow_gzip && '.gz' == substr($filename, -3, 3)) {
3251 // When we sent application/x-gzip as a content-type header to the browser, we found a case where the server compressed it a second time (since observed several times)
3252 return 'application/x-gzip';
3253 } else {
3254 return 'application/octet-stream';
3255 }
3256 }
3257
3258
3259 public function retain_range($input) {
3260 $input = (int)$input;
3261 return ($input > 0) ? min($input, 9999) : 1;
3262 }
3263
3264 public function just_one_email($input, $required = false) {
3265 $x = $this->just_one($input, 'saveemails', (empty($input) && false === $required) ? '' : get_bloginfo('admin_email'));
3266 if (is_array($x)) {
3267 foreach ($x as $ind => $val) {
3268 if (empty($val)) unset($x[$ind]);
3269 }
3270 if (empty($x)) $x = '';
3271 }
3272 return $x;
3273 }
3274
3275 public function just_one($input, $filter = 'savestorage', $rinput = false) {
3276 $oinput = $input;
3277 if (false === $rinput) $rinput = (is_array($input)) ? array_pop($input) : $input;
3278 if (is_string($rinput) && false !== strpos($rinput, ',')) $rinput = substr($rinput, 0, strpos($rinput, ','));
3279 return apply_filters('IWP_'.$filter, $rinput, $oinput);
3280 }
3281
3282 public function memory_check_current($memory_limit = false) {
3283 # Returns in megabytes
3284 if ($memory_limit == false) $memory_limit = ini_get('memory_limit');
3285 $memory_limit = rtrim($memory_limit);
3286 $memory_unit = $memory_limit[strlen($memory_limit)-1];
3287 if ((int)$memory_unit == 0 && $memory_unit !== '0') {
3288 $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
3289 } else {
3290 $memory_unit = '';
3291 }
3292 switch($memory_unit) {
3293 case '':
3294 $memory_limit = floor($memory_limit/1048576);
3295 break;
3296 case 'K':
3297 case 'k':
3298 $memory_limit = floor($memory_limit/1024);
3299 break;
3300 case 'G':
3301 $memory_limit = $memory_limit*1024;
3302 break;
3303 case 'M':
3304 //assumed size, no change needed
3305 break;
3306 }
3307 return $memory_limit;
3308 }
3309
3310 public function memory_check($memory, $check_using = false) {
3311 $memory_limit = $this->memory_check_current($check_using);
3312 return ($memory_limit >= $memory)?true:false;
3313 }
3314
3315 public function analyse_db_file($timestamp, $res, $db_file = false, $header_only = false) {
3316
3317 $mess = array(); $warn = array(); $err = array(); $info = array();
3318
3319 $wp_version = $this->get_wordpress_version();
3320 global $wpdb;
3321
3322 $iwp_backup_dir = $this->backups_dir_location();
3323
3324 if (false === $db_file) {
3325 # This attempts to raise the maximum packet size. This can't be done within the session, only globally. Therefore, it has to be done before the session starts; in our case, during the pre-analysis.
3326 $this->get_max_packet_size();
3327
3328 $backup = $this->get_backup_history($timestamp);
3329 if (!isset($backup['nonce']) || !isset($backup['db'])) return array($mess, $warn, $err, $info);
3330
3331 $db_file = (is_string($backup['db'])) ? $iwp_backup_dir.'/'.$backup['db'] : $iwp_backup_dir.'/'.$backup['db'][0];
3332 }
3333
3334 if (!is_readable($db_file)) return array($mess, $warn, $err, $info);
3335
3336 // Encrypted - decrypt it
3337 if ($this->is_db_encrypted($db_file)) {
3338
3339 $encryption = empty($res['IWP_encryptionphrase']) ? IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase') : $res['IWP_encryptionphrase'];
3340
3341 if (!$encryption) {
3342 if (class_exists('IWP_MMB_Addon_MoreDatabase')) {
3343 $err[] = sprintf(__('Error: %s', 'InfiniteWP'), __('Decryption failed. The database file is encrypted, but you have no encryption key entered.', 'InfiniteWP'));
3344 } else {
3345 $err[] = sprintf(__('Error: %s', 'InfiniteWP'), __('Decryption failed. The database file is encrypted.', 'InfiniteWP'));
3346 }
3347 return array($mess, $warn, $err, $info);
3348 }
3349
3350 $decrypted_file = $this->decrypt($db_file, $encryption);
3351
3352 if (is_array($decrypted_file)) {
3353 $db_file = $decrypted_file['fullpath'];
3354 } else {
3355 $err[] = __('Decryption failed. The most likely cause is that you used the wrong key.','InfiniteWP');
3356 return array($mess, $warn, $err, $info);
3357 }
3358
3359
3360 }
3361
3362 # Even the empty schema when gzipped comes to 1565 bytes; a blank WP 3.6 install at 5158. But we go low, in case someone wants to share single tables.
3363 if (filesize($db_file) < 1000) {
3364 $err[] = sprintf(__('The database is too small to be a valid WordPress database (size: %s Kb).','InfiniteWP'), round(filesize($db_file)/1024, 1));
3365 return array($mess, $warn, $err, $info);
3366 }
3367
3368 $is_plain = ('.gz' == substr($db_file, -3, 3)) ? false : true;
3369
3370 $dbhandle = ($is_plain) ? fopen($db_file, 'r') : $this->gzopen_for_read($db_file, $warn, $err);
3371 if (!is_resource($dbhandle)) {
3372 $err[] = __('Failed to open database file.', 'InfiniteWP');
3373 return array($mess, $warn, $err, $info);
3374 }
3375
3376 $info['timestamp'] = $timestamp;
3377
3378 # Analyse the file, print the results.
3379
3380 $line = 0;
3381 $old_siteurl = '';
3382 $old_home = '';
3383 $old_table_prefix = '';
3384 $old_siteinfo = array();
3385 $gathering_siteinfo = true;
3386 $old_wp_version = '';
3387 $old_php_version = '';
3388
3389 $tables_found = array();
3390
3391 // TODO: If the backup is the right size/checksum, then we could restore the $line <= 100 in the 'while' condition and not bother scanning the whole thing? Or better: sort the core tables to be first so that this usually terminates early
3392
3393 $wanted_tables = array('terms', 'term_taxonomy', 'term_relationships', 'commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'users', 'usermeta');
3394
3395 $migration_warning = false;
3396 $processing_create = false;
3397 $db_version = $wpdb->db_version();
3398
3399 // Don't set too high - we want a timely response returned to the browser
3400 // Until April 2015, this was always 90. But we've seen a few people with ~1GB databases (uncompressed), and 90s is not enough. Note that we don't bother checking here if it's compressed - having a too-large timeout when unexpected is harmless, as it won't be hit. On very large dbs, they're expecting it to take a while.
3401 // "120 or 240" is a first attempt at something more useful than just fixed at 90 - but should be sufficient (as 90 was for everyone without ~1GB databases)
3402 $default_dbscan_timeout = (filesize($db_file) < 31457280) ? 120 : 240;
3403 $dbscan_timeout = (defined('IWP_DBSCAN_TIMEOUT') && is_numeric(IWP_DBSCAN_TIMEOUT)) ? IWP_DBSCAN_TIMEOUT : $default_dbscan_timeout;
3404 @set_time_limit($dbscan_timeout);
3405
3406 while ((($is_plain && !feof($dbhandle)) || (!$is_plain && !gzeof($dbhandle))) && ($line<100 || (!$header_only && count($wanted_tables)>0))) {
3407 $line++;
3408 // Up to 1MB
3409 $buffer = ($is_plain) ? rtrim(fgets($dbhandle, 1048576)) : rtrim(gzgets($dbhandle, 1048576));
3410 // Comments are what we are interested in
3411 if (substr($buffer, 0, 1) == '#') {
3412 $processing_create = false;
3413 if ('' == $old_siteurl && preg_match('/^\# Backup of: (http(.*))$/', $buffer, $matches)) {
3414 $old_siteurl = untrailingslashit($matches[1]);
3415 $mess[] = __('Backup of:', 'InfiniteWP').' '.htmlspecialchars($old_siteurl).((!empty($old_wp_version)) ? ' '.sprintf(__('(version: %s)', 'InfiniteWP'), $old_wp_version) : '');
3416 // Check for should-be migration
3417 if ($old_siteurl != untrailingslashit(site_url())) {
3418 if (!$migration_warning) {
3419 $migration_warning = true;
3420 $powarn = apply_filters('IWP_dbscan_urlchange', sprintf(__('Warning: %s', 'InfiniteWP'), 'URL not matching'), $old_siteurl, $res);
3421 if (!empty($powarn)) $warn[] = $powarn;
3422 }
3423 // Explicitly set it, allowing the consumer to detect when the result was unknown
3424 $info['same_url'] = false;
3425
3426 if ($this->mod_rewrite_unavailable(false)) {
3427 $warn[] = sprintf(__('You are using the %s webserver, but do not seem to have the %s module loaded.', 'InfiniteWP'), 'Apache', 'mod_rewrite').' '.sprintf(__('You should enable %s to make any pretty permalinks (e.g. %s) work', 'InfiniteWP'), 'mod_rewrite', 'http://example.com/my-page/');
3428 }
3429
3430 } else {
3431 $info['same_url'] = true;
3432 }
3433 } elseif ('' == $old_home && preg_match('/^\# Home URL: (http(.*))$/', $buffer, $matches)) {
3434 $old_home = untrailingslashit($matches[1]);
3435 // Check for should-be migration
3436 if (!$migration_warning && $old_home != home_url()) {
3437 $migration_warning = true;
3438 $powarn = apply_filters('IWP_dbscan_urlchange', sprintf(__('Warning: %s', 'InfiniteWP'), 'URL not matching'), $old_siteurl, $res);
3439 if (!empty($powarn)) $warn[] = $powarn;
3440 }
3441 } elseif (!isset($info['created_by_version']) && preg_match('/^\# Created by InfiniteWP version ([\d\.]+)/', $buffer, $matches)) {
3442 $info['created_by_version'] = trim($matches[1]);
3443 } elseif ('' == $old_wp_version && preg_match('/^\# WordPress Version: ([0-9]+(\.[0-9]+)+)(-[-a-z0-9]+,)?(.*)$/', $buffer, $matches)) {
3444 $old_wp_version = $matches[1];
3445 if (!empty($matches[3])) $old_wp_version .= substr($matches[3], 0, strlen($matches[3])-1);
3446 if (version_compare($old_wp_version, $wp_version, '>')) {
3447 $warn[] = sprintf(__('You are importing from a newer version of WordPress (%s) into an older one (%s). There are no guarantees that WordPress can handle this.', 'InfiniteWP'), $old_wp_version, $wp_version);
3448 }
3449 if (preg_match('/running on PHP ([0-9]+\.[0-9]+)(\s|\.)/', $matches[4], $nmatches) && preg_match('/^([0-9]+\.[0-9]+)(\s|\.)/', PHP_VERSION, $cmatches)) {
3450 $old_php_version = $nmatches[1];
3451 $current_php_version = $cmatches[1];
3452 if (version_compare($old_php_version, $current_php_version, '>')) {
3453 $warn[] = sprintf(__('The site in this backup was running on a webserver with version %s of %s. ', 'InfiniteWP'), $old_php_version, 'PHP').' '.sprintf(__('This is significantly newer than the server which you are now restoring onto (version %s).', 'InfiniteWP'), PHP_VERSION).' '.sprintf(__('You should only proceed if you cannot update the current server and are confident (or willing to risk) that your plugins/themes/etc. are compatible with the older %s version.', 'InfiniteWP'), 'PHP').' '.sprintf(__('Any support requests to do with %s should be raised with your web hosting company.', 'InfiniteWP'), 'PHP');
3454 }
3455 }
3456 } elseif ('' == $old_table_prefix && (preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches) || preg_match('/^-- Table prefix: (\S+)$/i', $buffer, $matches))) {
3457 $old_table_prefix = $matches[1];
3458 } elseif (empty($info['label']) && preg_match('/^\# Label: (.*)$/', $buffer, $matches)) {
3459 $info['label'] = $matches[1];
3460 $mess[] = __('Backup label:', 'InfiniteWP').' '.htmlspecialchars($info['label']);
3461 } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) {
3462 if ('end' == $matches[1]) {
3463 $gathering_siteinfo = false;
3464 // Sanity checks
3465 if (isset($old_siteinfo['multisite']) && !$old_siteinfo['multisite'] && is_multisite()) {
3466 $warn[] = __('You are running on WordPress multisite - but your backup is not of a multisite site.', 'InfiniteWP').' '.__('It will be imported as a new site.', 'InfiniteWP').' <a href="https://InfiniteWP.com/information-on-importing-a-single-site-wordpress-backup-into-a-wordpress-network-i-e-multisite/">'.__('Please read this link for important information on this process.', 'InfiniteWP').'</a>';
3467
3468 if (!class_exists('IWP_MMBAddOn_MultiSite') || !class_exists('IWP_MMB_Addons_Migrator')) {
3469 $err[] = sprintf(__('Error: %s', 'InfiniteWP'), sprintf(__('To import an ordinary WordPress site into a multisite installation requires %s.', 'InfiniteWP'), 'InfiniteWP Premium'));
3470 return array($mess, $warn, $err, $info);
3471 }
3472 } elseif (isset($old_siteinfo['multisite']) && $old_siteinfo['multisite'] && !is_multisite()) {
3473 $warn[] = __('Warning:', 'InfiniteWP').' '.__('Your backup is of a WordPress multisite install; but this site is not. Only the first site of the network will be accessible.', 'InfiniteWP').' <a href="https://codex.wordpress.org/Create_A_Network">'.__('If you want to restore a multisite backup, you should first set up your WordPress installation as a multisite.', 'InfiniteWP').'</a>';
3474 }
3475 } elseif (preg_match('/^([^=]+)=(.*)$/', $matches[1], $kvmatches)) {
3476 $key = $kvmatches[1];
3477 $val = $kvmatches[2];
3478 if ('multisite' == $key) {
3479 $info['multisite'] = $val ? true : false;
3480 if ($val) $mess[] = '<strong>'.__('Site information:', 'InfiniteWP').'</strong> '.'backup is of a WordPress Network';
3481 }
3482 $old_siteinfo[$key]=$val;
3483 }
3484 } elseif (preg_match('/^\# Skipped tables: (.*)$/', $buffer, $matches)) {
3485 $skipped_tables = explode(',', $matches[1]);
3486 }
3487
3488 } elseif (preg_match('/^\s*create table \`?([^\`\(]*)\`?\s*\(/i', $buffer, $matches)) {
3489 $table = $matches[1];
3490 $tables_found[] = $table;
3491 if ($old_table_prefix) {
3492 // Remove prefix
3493 $table = $this->str_replace_once($old_table_prefix, '', $table);
3494 if (in_array($table, $wanted_tables)) {
3495 $wanted_tables = array_diff($wanted_tables, array($table));
3496 }
3497 }
3498 if (substr($buffer, -1, 1) != ';') $processing_create = true;
3499 } elseif ($processing_create) {
3500 if (substr($buffer, -1, 1) == ';') $processing_create = false;
3501 static $mysql_version_warned = false;
3502 if (!$mysql_version_warned && version_compare($db_version, '5.2.0', '<') && preg_match('/(CHARSET|COLLATE)[= ]utf8mb4/', $buffer)) {
3503 $mysql_version_warned = true;
3504 $err[] = sprintf(__('Error: %s', 'InfiniteWP'), sprintf(__('The database backup uses MySQL features not available in the old MySQL version (%s) that this site is running on.', 'InfiniteWP'), $db_version).' '.__('You must upgrade MySQL to be able to use this database.', 'InfiniteWP'));
3505 }
3506 }
3507 }
3508
3509 if ($is_plain) {
3510 @fclose($dbhandle);
3511 } else {
3512 @gzclose($dbhandle);
3513 }
3514
3515 /* $blog_tables = "CREATE TABLE $wpdb->terms (
3516 CREATE TABLE $wpdb->term_taxonomy (
3517 CREATE TABLE $wpdb->term_relationships (
3518 CREATE TABLE $wpdb->commentmeta (
3519 CREATE TABLE $wpdb->comments (
3520 CREATE TABLE $wpdb->links (
3521 CREATE TABLE $wpdb->options (
3522 CREATE TABLE $wpdb->postmeta (
3523 CREATE TABLE $wpdb->posts (
3524 $users_single_table = "CREATE TABLE $wpdb->users (
3525 $users_multi_table = "CREATE TABLE $wpdb->users (
3526 $usermeta_table = "CREATE TABLE $wpdb->usermeta (
3527 $ms_global_tables = "CREATE TABLE $wpdb->blogs (
3528 CREATE TABLE $wpdb->blog_versions (
3529 CREATE TABLE $wpdb->registration_log (
3530 CREATE TABLE $wpdb->site (
3531 CREATE TABLE $wpdb->sitemeta (
3532 CREATE TABLE $wpdb->signups (
3533 */
3534 if (!isset($skipped_tables)) $skipped_tables = array();
3535 $missing_tables = array();
3536 if ($old_table_prefix) {
3537 if (!$header_only) {
3538 foreach ($wanted_tables as $table) {
3539 if (!in_array($old_table_prefix.$table, $tables_found)) {
3540 $missing_tables[] = $table;
3541 }
3542 }
3543
3544 foreach ($missing_tables as $key => $value) {
3545 if (in_array($old_table_prefix.$value, $skipped_tables)) {
3546 unset($missing_tables[$key]);
3547 }
3548 }
3549
3550 if (count($missing_tables)>0) {
3551 $warn[] = sprintf(__('This database backup is missing core WordPress tables: %s', 'InfiniteWP'), implode(', ', $missing_tables));
3552 }
3553 if (count($skipped_tables)>0) {
3554 $warn[] = sprintf(__('This database backup has the following WordPress tables excluded: %s', 'InfiniteWP'), implode(', ', $skipped_tables));
3555 }
3556 }
3557 } else {
3558 if (empty($backup['meta_foreign'])) {
3559 $warn[] = __('InfiniteWP was unable to find the table prefix when scanning the database backup.', 'InfiniteWP');
3560 }
3561 }
3562
3563 // //need to make sure that we reset the file back to .crypt before clean temp files
3564 // $db_file = $decrypted_file['fullpath'].'.crypt';
3565 // unlink($decrypted_file['fullpath']);
3566
3567 return array($mess, $warn, $err, $info);
3568
3569 }
3570
3571 private function gzopen_for_read($file, &$warn, &$err) {
3572 if (!function_exists('gzopen') || !function_exists('gzread')) {
3573 $missing = '';
3574 if (!function_exists('gzopen')) $missing .= 'gzopen';
3575 if (!function_exists('gzread')) $missing .= ($missing) ? ', gzread' : 'gzread';
3576 $err[] = sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'InfiniteWP'), $missing).' '.sprintf(__('Your hosting company must enable these functions before %s can work.', 'InfiniteWP'), __('restoration', 'InfiniteWP'));
3577 return false;
3578 }
3579 if (false === ($dbhandle = gzopen($file, 'r'))) return false;
3580
3581 if (!function_exists('gzseek')) return $dbhandle;
3582
3583 if (false === ($bytes = gzread($dbhandle, 3))) return false;
3584 # Double-gzipped?
3585 if ('H4sI' != base64_encode($bytes)) {
3586 if (0 === gzseek($dbhandle, 0)) {
3587 return $dbhandle;
3588 } else {
3589 @gzclose($dbhandle);
3590 return gzopen($file, 'r');
3591 }
3592 }
3593 # Yes, it's double-gzipped
3594
3595 $what_to_return = false;
3596 $mess = __('The database file appears to have been compressed twice - probably the website you downloaded it from had a mis-configured webserver.', 'InfiniteWP');
3597 $messkey = 'doublecompress';
3598 $err_msg = '';
3599
3600 if (false === ($fnew = fopen($file.".tmp", 'w')) || !is_resource($fnew)) {
3601
3602 @gzclose($dbhandle);
3603 $err_msg = __('The attempt to undo the double-compression failed.', 'InfiniteWP');
3604
3605 } else {
3606
3607 @fwrite($fnew, $bytes);
3608 $emptimes = 0;
3609 while (!gzeof($dbhandle)) {
3610 $bytes = @gzread($dbhandle, 262144);
3611 if (empty($bytes)) {
3612 $emptimes++;
3613 $this->log("Got empty gzread ($emptimes times)");
3614 if ($emptimes>2) break;
3615 } else {
3616 @fwrite($fnew, $bytes);
3617 }
3618 }
3619
3620 gzclose($dbhandle);
3621 fclose($fnew);
3622 # On some systems (all Windows?) you can't rename a gz file whilst it's gzopened
3623 if (!rename($file.".tmp", $file)) {
3624 $err_msg = __('The attempt to undo the double-compression failed.', 'InfiniteWP');
3625 } else {
3626 $mess .= ' '.__('The attempt to undo the double-compression succeeded.', 'InfiniteWP');
3627 $messkey = 'doublecompressfixed';
3628 $what_to_return = gzopen($file, 'r');
3629 }
3630
3631 }
3632
3633 $warn[$messkey] = $mess;
3634 if (!empty($err_msg)) $err[] = $err_msg;
3635 return $what_to_return;
3636 }
3637
3638 # TODO: Remove legacy storage setting keys from here
3639 // These are used in 4 places (Feb 2016 - of course, you should re-scan the code to check if relying on this): showing current settings on the debug modal, wiping all current settings, getting a settings bundle to restore when migrating, and for relevant keys in POST-ed data when saving settings over AJAX
3640 public function get_settings_keys() {
3641 return array('IWP_autobackup_default', 'IWP_dropbox', 'IWP_googledrive', 'IWP_tmp_googledrive_access_token', 'IWP_dismissedautobackup', 'dismissed_general_notices_until', 'dismissed_season_notices_until', 'IWP_dismissedexpiry', 'IWP_dismisseddashnotice', 'IWP_interval', 'IWP_interval_increments', 'IWP_interval_database', 'IWP_retain', 'IWP_retain_db', 'IWP_encryptionphrase', 'IWP_service', 'IWP_googledrive_clientid', 'IWP_googledrive_secret', 'IWP_googledrive_remotepath', 'IWP_ftp', 'IWP_server_address', 'IWP_dir', 'IWP_email', 'IWP_delete_local', 'IWP_debug_mode', 'IWP_include_plugins', 'IWP_include_themes', 'IWP_include_uploads', 'IWP_include_others', 'IWP_include_wpcore', 'IWP_include_wpcore_exclude', 'IWP_include_more', 'IWP_include_blogs', 'IWP_include_mu-plugins',
3642 'IWP_include_others_exclude', 'IWP_include_uploads_exclude', 'IWP_lastmessage', 'IWP_googledrive_token', 'IWP_dropboxtk_request_token', 'IWP_dropboxtk_access_token', 'IWP_adminlocking', 'IWP_IWPvault', 'IWP_remotesites', 'IWP_migrator_localkeys', 'IWP_central_localkeys', 'IWP_retain_extrarules', 'IWP_googlecloud', 'IWP_include_more_path', 'IWP_split_every', 'IWP_ssl_nossl', 'IWP_backupdb_nonwp', 'IWP_extradbs', 'IWP_combine_jobs_around',
3643 'IWP_last_backup', 'IWP_starttime_files', 'IWP_starttime_db', 'IWP_startday_db', 'IWP_startday_files', 'IWP_sftp', 'IWP_s3', 'IWP_s3generic', 'IWP_dreamhost', 'IWP_s3generic_login', 'IWP_s3generic_pass', 'IWP_s3generic_remote_path', 'IWP_s3generic_endpoint', 'IWP_webdav', 'IWP_openstack', 'IWP_onedrive', 'IWP_azure', 'IWP_cloudfiles', 'IWP_cloudfiles_user', 'IWP_cloudfiles_apikey', 'IWP_cloudfiles_path', 'IWP_cloudfiles_authurl', 'IWP_ssl_useservercerts', 'IWP_ssl_disableverify', 'IWP_s3_login', 'IWP_s3_pass', 'IWP_s3_remote_path', 'IWP_dreamobjects_login', 'IWP_dreamobjects_pass', 'IWP_dreamobjects_remote_path', 'IWP_dreamobjects', 'IWP_report_warningsonly', 'IWP_report_wholebackup', 'IWP_log_syslog', 'IWP_extradatabases');
3644 }
3645
3646 /**
3647 * Returns the member of the array with key (int)0, as a new array. This function is used as a callback for array_map().
3648 *
3649 * @param Array $a - the array
3650 *
3651 * @return Array - with keys 'name' and 'type'
3652 */
3653 private function cb_get_name_base_type($a) {
3654 return array('name' => $a[0], 'type' => 'BASE TABLE');
3655 }
3656
3657 /**
3658 * Returns the members of the array with keys (int)0 and (int)1, as part of a new array.
3659 *
3660 * @param Array $a - the array
3661 *
3662 * @return Array - keys are 'name' and 'type'
3663 */
3664 private function cb_get_name_type($a) {
3665 return array('name' => $a[0], 'type' => $a[1]);
3666 }
3667
3668 /**
3669 * Returns the member of the array with key (string)'name'. This function is used as a callback for array_map().
3670 *
3671 * @param Array $a - the array
3672 *
3673 * @return Mixed - the value with key (string)'name'
3674 */
3675 private function cb_get_name($a) {
3676 return $a['name'];
3677 }
3678
3679 public function get_backup_stats()
3680 {
3681 global $wpdb;
3682
3683 $stats = array();
3684 $new_backup_method = $this->get_backup_history();
3685 $task_res = array();
3686 if (!empty($new_backup_method)) {
3687 foreach ($new_backup_method as $time => $value) {
3688 $task_res[$value['label']][$time]=$value;
3689 }
3690 }
3691 $stats = $task_res;
3692
3693
3694 return $stats;
3695
3696 }
3697
3698 public function getRunningBackupStatus($params){
3699 $result = get_option('IWP_backup_status');
3700 $job_id = $params['params']['backup_id'];
3701 $job_data = $this->jobdata_getarray($job_id);
3702 $cron_disable = false;
3703 if ($result == '1') {
3704 $cron_params = array();
3705 if (( defined('DISABLE_WP_CRON') && DISABLE_WP_CRON )) {
3706 $cron_disable = true;
3707 $cron_params = $this->get_cron($job_id);
3708 }
3709 $cron_do_action = $this->is_cron_do_action_need($job_id);
3710 return array('success'=>array('status' => 'partiallyCompleted', 'params' => $params['params'], 'jobdata'=>$job_data, 'cron_disable' => $cron_disable, 'cron_params' =>$cron_params, 'wp_content_url' => content_url(),'cron_do_action' =>$cron_do_action));
3711 } elseif ($result == '0') {
3712 $cron = $this->get_cron($job_id);
3713 if ($cron == false) {
3714 $running_backupID = get_option('IWP_running_backupID');
3715 if(!empty($running_backupID)){
3716 $server = strtolower($_SERVER['SERVER_SOFTWARE']);
3717 if(strpos($server, 'litespeed') !== false){
3718 return array('error' => 'Unable to start the backup using the Phoenix method. LiteSpeed server detected.try using the multicall method.', 'error_code' => 'iwp_running_backup_issue_litespeed');
3719 } else{
3720 return array('error' => 'Unable to start the backup using the Phoenix method. Try using the multicall method', 'error_code' => 'iwp_running_backup_issue');
3721 }
3722 }else{
3723 $last_backup = $this->last_backup_staus();
3724 if (empty($last_backup) || empty($last_backup['error'])) {
3725 $IWP_last_backup = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_last_backup');
3726 return array('success'=>array('status' => 'completed', 'last_backup' => $IWP_last_backup, 'wp_content_url' => content_url(), 'backup_id' => $job_id));
3727 }
3728 $errorMsg = 'Backup Failed';
3729 if (!empty($last_backup['error'])) {
3730 $errorMsg = $last_backup['error'];
3731 }
3732 return array('error' => array('error_code' => 'backup_failed', 'error' => $errorMsg, 'jobdata' => $job_data, 'wp_content_url' => content_url(), 'backup_id' => $job_id));
3733 }
3734 }
3735 if (!empty($cron)) {
3736 if (time()> $cron[0]) {
3737 wp_cron();
3738 }
3739 }
3740 $cron_params = array();
3741 if (( defined('DISABLE_WP_CRON') && DISABLE_WP_CRON )) {
3742 $cron_disable = true;
3743 $cron_params = $this->get_cron($job_id);
3744 }
3745 $cron_do_action = $this->is_cron_do_action_need($job_id);
3746 return array('success'=>array('status' => 'partiallyCompleted', 'params' => $params['params'], 'jobdata'=>$job_data, 'cron_data' => $cron, 'cron_disable' => $cron_disable, 'cron_params' =>$cron_params, 'wp_content_url' => content_url(),'cron_do_action' =>$cron_do_action) );
3747 }
3748
3749 }
3750
3751 public function ensure_phpseclib_old($classes = false, $class_paths = false) {
3752
3753 $this->no_deprecation_warnings_on_php7();
3754
3755 if ($classes) {
3756 $any_missing = false;
3757 if (is_string($classes)) $classes = array($classes);
3758 foreach ($classes as $cl) {
3759 if (!class_exists($cl)) $any_missing = true;
3760 }
3761 if (!$any_missing) return;
3762 }
3763
3764 if ($class_paths) {
3765 $phpseclib_dir = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/phpseclib/phpseclib/phpseclib';
3766 if (false === strpos(get_include_path(), $phpseclib_dir)) set_include_path(get_include_path().PATH_SEPARATOR.$phpseclib_dir);
3767 if (is_string($class_paths)) $class_paths = array($class_paths);
3768 foreach ($class_paths as $cp) {
3769 include_once($phpseclib_dir.'/'.$cp.'.php');
3770 }
3771 }
3772 }
3773
3774 public function ensure_phpseclib($classes = array()) {
3775
3776 $classes = (array) $classes;
3777
3778 $this->no_deprecation_warnings_on_php7();
3779
3780 $any_missing = false;
3781
3782 foreach ($classes as $cl) {
3783 if (!class_exists($cl)) $any_missing = true;
3784 }
3785
3786 if (!$any_missing) return true;
3787
3788 $ret = true;
3789
3790 // From phpseclib/phpseclib/phpseclib/bootstrap.php - we nullify it there, but log here instead
3791 if (extension_loaded('mbstring')) {
3792 // 2 - MB_OVERLOAD_STRING
3793 // @codingStandardsIgnoreLine
3794 if (ini_get('mbstring.func_overload') & 2) {
3795 // We go on to try anyway, in case the caller wasn't using an affected part of phpseclib
3796 // @codingStandardsIgnoreLine
3797 $ret = new WP_Error('mbstring_func_overload', 'Overloading of string functions using mbstring.func_overload is not supported by phpseclib.');
3798 }
3799 }
3800
3801 $phpseclib_dir = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/phpseclib/phpseclib/phpseclib';
3802 if (false === strpos(get_include_path(), $phpseclib_dir)) set_include_path(get_include_path().PATH_SEPARATOR.$phpseclib_dir);
3803 foreach ($classes as $cl) {
3804 $path = str_replace('_', '/', $cl);
3805 if (!class_exists($cl)) include_once($phpseclib_dir.'/'.$path.'.php');
3806 }
3807
3808 return $ret;
3809 }
3810
3811 public function fetch_log($backup_nonce = '', $log_pointer = 0, $output_format = 'html') {
3812 global $iwp_backup_core;
3813
3814 if (empty($backup_nonce)) {
3815 list($mod_time, $log_file, $nonce) = $iwp_backup_core->last_modified_log();
3816 } else {
3817 $nonce = $backup_nonce;
3818 }
3819
3820 if (!preg_match('/^[0-9a-f]+$/', $nonce)) die('Security check');
3821
3822 $log_content = '';
3823 $new_pointer = $log_pointer;
3824
3825 if (!empty($nonce)) {
3826 $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
3827
3828 $potential_log_file = $iwp_backup_dir."/log.".$nonce.".txt";
3829
3830 if (is_readable($potential_log_file)){
3831
3832 $templog_array = array();
3833 $log_file = fopen($potential_log_file, "r");
3834 if ($log_pointer > 0) fseek($log_file, $log_pointer);
3835
3836 while (($buffer = fgets($log_file, 4096)) !== false) {
3837 $templog_array[] = $buffer;
3838 }
3839 if (!feof($log_file)) {
3840 $templog_array[] = __('Error: unexpected file read fail', 'InfiniteWP');
3841 }
3842
3843 $new_pointer = ftell($log_file);
3844 $log_content = implode("", $templog_array);
3845
3846
3847 } else {
3848 $log_content .= __('The log file could not be read.', 'InfiniteWP');
3849 }
3850
3851 } else {
3852 $log_content .= __('The log file could not be read.', 'InfiniteWP');
3853 }
3854
3855 if ('html' == $output_format) $log_content = htmlspecialchars($log_content);
3856
3857 $ret_array = array(
3858 'log' => $log_content,
3859 'nonce' => $nonce,
3860 'pointer' => $new_pointer
3861 );
3862
3863 return $ret_array;
3864 }
3865
3866 public function get_cron($job_id = false) {
3867
3868 $cron = get_option('cron');
3869 if (!is_array($cron)) $cron = array();
3870 if (false === $job_id) return $cron;
3871
3872 foreach ($cron as $time => $job) {
3873 if (isset($job['IWP_backup_resume'])) {
3874 foreach ($job['IWP_backup_resume'] as $hook => $info) {
3875 if (isset($info['args'][1]) && $job_id == $info['args'][1]) {
3876 $jobdata = $this->jobdata_getarray($job_id);
3877 return (!is_array($jobdata)) ? false : array($time);
3878 }
3879 }
3880 }
3881 }
3882 }
3883
3884 public function get_cron_data($job_id = false) {
3885
3886 $cron = get_option('cron');
3887 if (!is_array($cron)) $cron = array();
3888 if (false === $job_id) return $cron;
3889
3890 foreach ($cron as $time => $job) {
3891 if (isset($job['IWP_backup_resume'])) {
3892 foreach ($job['IWP_backup_resume'] as $hook => $info) {
3893 if (isset($info['args'][1]) && $job_id == $info['args'][1]) {
3894 $jobdata = $this->jobdata_getarray($job_id);
3895 return (!is_array($jobdata)) ? false : $info['args'];
3896 }
3897 }
3898 }
3899 }
3900 }
3901
3902 public function last_backup_staus() {
3903 $last_backup = array();
3904 $IWP_last_backup = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_last_backup');
3905
3906 if ($IWP_last_backup) {
3907
3908 // Show errors + warnings
3909 if (is_array($IWP_last_backup['errors'])) {
3910 foreach ($IWP_last_backup['errors'] as $err) {
3911 $level = (is_array($err)) ? $err['level'] : 'error';
3912 $message = (is_array($err)) ? $err['message'] : $err;
3913
3914 if ('warning' == $level) {
3915 $last_backup['warning'] = $message;
3916 } else {
3917 $last_backup['error'] = $message;
3918 }
3919
3920 }
3921 }
3922
3923 }
3924
3925 return $last_backup;
3926
3927 }
3928
3929 public function backupable_file_entities_final($arr, $full_info) {
3930 $path = wp_normalize_path(ABSPATH);
3931 $os_name = '';
3932 if(function_exists('php_uname')){
3933 $os_name = php_uname();
3934 }elseif (defined('PHP_OS')) {
3935 $os_name = PHP_OS;
3936 }
3937 if (stristr($os_name, 'windows')) {
3938 $windows_normalized_abspath = $path.'wp-admin/../';
3939 if (@opendir($windows_normalized_abspath)) {
3940 $path = $windows_normalized_abspath;
3941 }
3942 }
3943 if (is_array($path)) {
3944 $path = array_map('untrailingslashit', $path);
3945 if (1 == count($path)) $path = array_shift($path);
3946 } else {
3947 $path = untrailingslashit($path);
3948 }
3949 if ($full_info) {
3950 $arr['more'] = array(
3951 'path' => $path,
3952 'description' => __('Any other file/directory on your server that you wish to back up', 'InfiniteWP'),
3953 'shortdescription' => __('More Files', 'InfiniteWP'),
3954 'restorable' => false
3955 );
3956 } else {
3957 $arr['more'] = $path;
3958 }
3959 return $arr;
3960 }
3961
3962 public function set_backup_task_option($params){
3963 if (empty($params)) {
3964 return false;
3965 }
3966 $exclude_others = '';
3967 $exclude_uploads = '';
3968 $IWP_service = false;
3969 update_option('IWP_delete_local', 1);
3970 if (!empty($params['args']['exclude'])) {
3971 if (defined('IWP_DEFAULT_OTHERS_EXCLUDE')) {
3972 $exclude_others = IWP_DEFAULT_OTHERS_EXCLUDE.',';
3973 }
3974 $exclude_others.= $params['args']['exclude'];
3975 $exclude_uploads.= $params['args']['exclude'];
3976 update_option('IWP_include_others_exclude', $exclude_others);
3977 update_option('IWP_include_uploads_exclude', $exclude_uploads);
3978 }
3979 if (!empty($params['args']['include'])) {
3980 if (defined('IWP_DEFAULT_INCLUDES')) {
3981 $include = IWP_DEFAULT_INCLUDES.',';
3982 }
3983 $include.= implode(",", $params['args']['include']);
3984 update_option('IWP_default_includes', $include);
3985 }
3986 if (!empty($params['args']['exclude_extensions']) && !defined('IWP_EXCLUDE_EXTENSIONS')) {
3987 define('IWP_EXCLUDE_EXTENSIONS', $params['args']['exclude_extensions']);
3988 }
3989 if (!empty($params['args']['exclude_file_size']) && !defined('IWP_SKIP_FILE_OVER_SIZE')) {
3990 $exclude_file_size = $params['args']['exclude_file_size'] * 1048576; // 10*1048576
3991 define('IWP_SKIP_FILE_OVER_SIZE',$exclude_file_size);
3992 }
3993
3994 if (!empty($params['args']['IWP_encryptionphrase'])) {
3995 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_encryptionphrase', $params['args']['IWP_encryptionphrase']);
3996 }else{
3997 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_encryptionphrase', false);
3998 }
3999
4000 if (!empty($params['account_info'])) {
4001 if (!empty($params['account_info']['iwp_ftp'])) {
4002 $ftp_details = $params['account_info']['iwp_ftp'];
4003 $opts = array(
4004 'user' => $ftp_details['ftp_username'],
4005 'pass' => $ftp_details['ftp_password'],
4006 'host' => $ftp_details['ftp_hostname'],
4007 'path' => $ftp_details['ftp_remote_folder'],
4008 'port' => $ftp_details['ftp_port'],
4009 'ftp_site_folder' => $ftp_details['ftp_site_folder'],
4010 'passive' => isset($ftp_details['ftp_passive'])?true:false,
4011 'key' => isset($ftp_details['ftp_key'])?$ftp_details['ftp_key']:'',
4012 );
4013 if (isset($ftp_details['use_sftp']) && $ftp_details['use_sftp']) {
4014 update_option('IWP_service', 'sftp');
4015 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_sftp', $opts);
4016 }else{
4017 update_option('IWP_service', 'ftp');
4018 if(!empty($ftp_details['ftp_ssl'])){
4019 $opts['host'] = $opts['host'].':'.$opts['port'];
4020 unset($opts['port']);
4021 IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_ssl_nossl');
4022 }else{
4023 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ssl_nossl', 1);
4024 }
4025 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ftp', $opts);
4026 }
4027 }elseif (!empty($params['account_info']['iwp_amazon_s3'])) {
4028 update_option('IWP_service', 's3');
4029 $s3_details = $params['account_info']['iwp_amazon_s3'];
4030 if (!empty($s3_details['as3_directory'])) {
4031 $path = trim($s3_details['as3_bucket'],'/').'/'.trim($s3_details['as3_directory'],'/');
4032 }else{
4033 $path = $s3_details['as3_bucket'];
4034 }
4035 $opts = array(
4036 'endpoint' => '',
4037 'accesskey' => $s3_details['as3_access_key'],
4038 'secretkey' => $s3_details['as3_secure_key'],
4039 'path' => $path,
4040 'as3_site_folder' => $s3_details['as3_site_folder'],
4041 'server_side_encryption' => $s3_details['server_side_encryption']?true:false
4042 );
4043 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_s3', $opts);
4044
4045 }elseif (!empty($params['account_info']['iwp_dropbox'])) {
4046 update_option('IWP_service', 'dropbox');
4047 $dropbox_details = $params['account_info']['iwp_dropbox'];
4048
4049 $opts = array(
4050 'appkey' => $dropbox_details['dropbox_app_key'],
4051 'secret' => $dropbox_details['dropbox_app_secure_key'],
4052 'tk_access_token' => $dropbox_details['dropbox_access_token'],
4053 'folder' => $dropbox_details['dropbox_destination'],
4054 'ownername' => '',
4055 'CSRF' => '',
4056 'dropbox_site_folder' => $dropbox_details['dropbox_site_folder']
4057 );
4058 if ( !empty($dropbox_details['dropbox_email']) ) {
4059 $opts['email'] = $dropbox_details['dropbox_email'];
4060 }
4061 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_dropbox', $opts);
4062 }elseif (!empty($params['account_info']['iwp_gdrive'])) {
4063 update_option('IWP_service', 'googledrive');
4064 $google_details = $params['account_info']['iwp_gdrive'];
4065 $opts = array(
4066 'clientid' => $google_details['clientID'],
4067 'secret' => $google_details['clientSecretKey'],
4068 'token' => $google_details['token']['refresh_token'],
4069 'tmp_access_token' => $google_details['token']['access_token'],
4070 'gdrive_site_folder' => $google_details['gdrive_site_folder'],
4071 'ownername' => ''
4072 );
4073 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_googledrive', $opts);
4074 }
4075 }else{
4076 delete_option('IWP_service');
4077 }
4078
4079 if (!empty($params['args']['limit'])) {
4080 update_option('IWP_retain', $params['args']['limit']);
4081 update_option('IWP_retain_db', $params['args']['limit']);
4082 }
4083
4084 if (!empty($params['args']['exclude_tables'])) {
4085 $exclude_tables = @implode(',', $params['args']['exclude_tables']);
4086 update_option('IWP_default_exclude_tables', $exclude_tables);
4087 }
4088 }
4089
4090 public function get_remote_file($services, $file, $timestamp, $restore = false) {
4091 global $iwp_backup_core;
4092
4093 $fullpath = $iwp_backup_core->backups_dir_location().'/'.$file;
4094
4095 $storage_objects_and_ids = $iwp_backup_core->get_storage_objects_and_ids($services);
4096
4097 $is_downloaded = false;
4098
4099 $iwp_backup_core->register_wp_http_option_hooks();
4100 $download = -1;
4101
4102 foreach ($services as $service) {
4103
4104 if (empty($service) || 'none' == $service) continue;
4105
4106 if ($restore) {
4107 $service_description = empty($iwp_backup_core->backup_methods[$service]) ? $service : $iwp_backup_core->backup_methods[$service];
4108 $iwp_backup_core->log(__("File is not locally present - needs retrieving from remote storage",'InfiniteWP')." ($service_description)", 'notice-restore');
4109 }
4110
4111 $object = $storage_objects_and_ids[$service]['object'];
4112
4113 if (!$object->supports_feature('multi_options')) {
4114 error_log("InfiniteWP_Admin::get_remote_file(): Multi options not supported by: ".$service);
4115 continue;
4116 }
4117
4118 $instance_ids = $storage_objects_and_ids[$service]['instance_settings'];
4119 $backups_instance_ids = isset($backup_history[$timestamp]['service_instance_ids'][$service]) ? $backup_history[$timestamp]['service_instance_ids'][$service] : array(false);
4120
4121 foreach ($backups_instance_ids as $instance_id) {
4122
4123 if (isset($instance_ids[$instance_id])) {
4124 $options = $instance_ids[$instance_id];
4125 } else {
4126 $options = $object->get_options();
4127 }
4128
4129 $object->set_options($options, false, $instance_id);
4130
4131 $download = $this->download_file($file, $object);
4132
4133 if (is_readable($fullpath) && false !== $download) {
4134 if ($restore) {
4135 $iwp_backup_core->log(__("OK", 'InfiniteWP'), 'notice-restore');
4136 } else {
4137 clearstatcache();
4138 if ( $download === 'partial') {
4139 $iwp_backup_core->log('Remote fetch was partially completed (file size: '.round(filesize($fullpath)/1024, 1).' KB)');
4140 }else {
4141 $iwp_backup_core->log('Remote fetch was successful (file size: '.round(filesize($fullpath)/1024, 1).' KB)');
4142 }
4143 }
4144 break 2;
4145 } else {
4146 if ($restore) {
4147 $iwp_backup_core->log(__("Error", 'InfiniteWP'), 'notice-restore');
4148 } else {
4149 clearstatcache();
4150 if (0 === @filesize($fullpath)) @unlink($fullpath);
4151 $iwp_backup_core->log('Remote fetch failed');
4152 }
4153 }
4154 }
4155 }
4156 $iwp_backup_core->register_wp_http_option_hooks(false);
4157 return $download;
4158 }
4159
4160 public function get_storage_objects_and_ids($services) {
4161
4162 $storage_objects_and_ids = array();
4163
4164 foreach ($services as $method) {
4165
4166 if ('none' === $method || '' == $method) continue;
4167
4168 $call_method = 'IWP_MMB_UploadModule_'.$method;
4169
4170 if (!class_exists($call_method)) include_once $GLOBALS['iwp_mmb_plugin_dir'].'/backup/'.$method.'.php';
4171
4172 if (class_exists($call_method)) {
4173
4174 $remote_storage = new $call_method;
4175
4176 if (!empty($method_objects[$method])) $storage_objects_and_ids[$method] = array();
4177
4178 $storage_objects_and_ids[$method]['object'] = $remote_storage;
4179
4180 if ($remote_storage->supports_feature('multi_options')) {
4181
4182 $settings = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_'.$method);
4183
4184 if (!is_array($settings)) $settings = array();
4185
4186 if (!isset($settings['version'])) $settings = $this->update_remote_storage_options_format($method);
4187
4188 if (is_wp_error($settings)) {
4189 error_log("InfiniteWP: failed to convert storage options format: $method");
4190 $settings = array('settings' => array());
4191 }
4192
4193 if (!empty($settings)) {
4194
4195 if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = array();
4196
4197 $storage_objects_and_ids[$method]['instance_settings'] = $settings;
4198
4199 }
4200 }
4201
4202 } else {
4203 error_log("InfiniteWP: no such storage class: $call_method");
4204 }
4205 }
4206
4207 return $storage_objects_and_ids;
4208
4209 }
4210
4211 public function download_file($file, $object) {
4212
4213 global $iwp_backup_core;
4214
4215 @set_time_limit(IWP_SET_TIME_LIMIT);
4216
4217 $service = $object->get_id();
4218
4219 $iwp_backup_core->log("Requested file from remote service: $service: $file");
4220
4221 if (method_exists($object, 'download')) {
4222
4223 try {
4224 return $object->download($file);
4225 } catch (Exception $e) {
4226 $log_message = 'Exception ('.get_class($e).') occurred during download: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
4227 $iwp_backup_core->log($log_message);
4228 error_log($log_message);
4229 $iwp_backup_core->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'InfiniteWP'), get_class($e), $e->getMessage()), 'error');
4230 return false;
4231 // @codingStandardsIgnoreLine
4232 } catch (Error $e) {
4233 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
4234 $iwp_backup_core->log($log_message);
4235 error_log($log_message);
4236 $iwp_backup_core->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'InfiniteWP'), get_class($e), $e->getMessage()), 'error');
4237 return false;
4238 }
4239 } else {
4240 $iwp_backup_core->log("Automatic backup restoration is not available with the method: $service.");
4241 $iwp_backup_core->log("$file: ".sprintf(__("The backup archive for this file could not be found. The remote storage method in use (%s) does not allow us to retrieve files. To perform any restoration using InfiniteWP", 'InfiniteWP'), $service)." (".$this->prune_iwp_dir_prefix($iwp_backup_core->backups_dir_location()).")", 'error');
4242 return false;
4243 }
4244
4245 }
4246 public function prune_iwp_dir_prefix($iwp_backup_dir) {
4247 if ('/' == substr($iwp_backup_dir, 0, 1) || "\\" == substr($iwp_backup_dir, 0, 1) || preg_match('/^[a-zA-Z]:/', $iwp_backup_dir)) {
4248 $wcd = trailingslashit(WP_CONTENT_DIR);
4249 if (strpos($iwp_backup_dir, $wcd) === 0) {
4250 $iwp_backup_dir = substr($iwp_backup_dir, strlen($wcd));
4251 }
4252 # Legacy
4253 // if (strpos($iwp_backup_dir, ABSPATH) === 0) {
4254 // $iwp_backup_dir = substr($iwp_backup_dir, strlen(ABSPATH));
4255 // }
4256 }
4257 return $iwp_backup_dir;
4258 }
4259
4260 public function do_iwp_download_backup($params = array()) {
4261
4262 $GLOBALS['IWP_BACKUP_RESTORE_INIT_TIME'] = microtime(1);
4263
4264 @set_time_limit(IWP_SET_TIME_LIMIT);
4265 global $iwp_backup_core;
4266 $timestamp = $params['resultID'];
4267 $taskName = $params['taskName'];
4268 $next_call = false;
4269 if (empty($params['job_nonce'])) {
4270 $next_call = true;
4271 }
4272 $job_nonce = dechex($timestamp).substr(md5($taskName), 0, 5);
4273 // You need a nonce before you can set job data. And we certainly don't yet have one.
4274 $nounce = $this->backup_time_nonce($job_nonce);
4275
4276 $debug_mode = true;
4277
4278 // Set the job type before logging, as there can be different logging destinations
4279 $running_download = $iwp_backup_core->jobdata_get ('download');
4280 if (empty($running_download)) {
4281 $iwp_backup_core->jobdata_set('download', $params);
4282 $iwp_backup_core->jobdata_set('job_time_ms', $iwp_backup_core->job_time_ms);
4283 }
4284 if ($next_call === true) {
4285 $iwp_backup_core->logfile_open($iwp_backup_core->nonce);
4286 }else{
4287 $iwp_backup_core->logfile_open($iwp_backup_core->nonce, true);
4288 }
4289
4290 $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
4291 if (!empty($params['isNewBackup'])) {
4292 $types_to_downlaod = $params['types_to_downlaod'];
4293 $types_to_downlaod[] = 'backup_file_basename';
4294 // Retrieve the information from our backup history
4295 $backup_history = $this->get_backup_history();
4296 // Base name
4297 foreach ($types_to_downlaod as $key => $type ) {
4298 $files = $backup_history[$timestamp][$type];
4299 if (is_array($files)) {
4300 foreach ($files as $index => $file_name) {
4301 $itext = empty($index) ? '' : $index;
4302 $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0;
4303 $file = $files[$index];
4304 $fullpath = $iwp_backup_dir.'/'.$file;
4305 if (!file_exists($fullpath)) {
4306 $findex = $index;
4307 break;
4308 } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4309 $findex = $index;
4310 break;
4311 }else{
4312 $file = '';
4313 }
4314 }
4315 }else{
4316 $file = $files;
4317 $fullpath = $iwp_backup_dir.'/'.$file;
4318 $findex = '';
4319 $itext = empty($findex) ? '' : $findex;
4320 $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0;
4321 if (!file_exists($fullpath)) {
4322 $findex = $index;
4323 break;
4324 } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4325 $findex = $index;
4326 break;
4327 }else{
4328 $file = '';
4329 }
4330
4331 }
4332 if (!empty($file)) {
4333 break;
4334 }
4335 }
4336 set_error_handler(array($iwp_backup_core, 'php_error'), E_ALL & ~E_STRICT);
4337
4338 $iwp_backup_core->log("Requested to obtain file: timestamp=$timestamp, type=$type, index=$findex");
4339
4340 $services = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false;
4341 if (!empty($backup_history[$timestamp]['service'][0]) && $backup_history[$timestamp]['service'][0] != 'none' && !empty($backup_history[$timestamp]['service_setting'])) {
4342 $service_setting = $backup_history[$timestamp]['service_setting'];
4343 $service = 'IWP_'.$backup_history[$timestamp]['service'][0];
4344 IWP_MMB_Backup_Options::update_iwp_backup_option($service, $service_setting);
4345 }
4346 if (is_string($services)) $services = array($services);
4347
4348 $iwp_backup_core->jobdata_set('service', $services);
4349
4350 }else{
4351 $tasks = $this->get_requested_task($timestamp);
4352 $tasks['taskResults'] = unserialize($tasks['taskResults']);
4353 $backup = $tasks['taskResults']['task_results'][$timestamp]; //darkCode testing purpose
4354 //$backup = $tasks['taskResults'];
4355 $requestParams = unserialize($tasks['requestParams']);
4356 $args = $requestParams['account_info'];
4357 $this->set_cloud_upload_setting($requestParams);
4358 if (isset($backup['ftp'])) {
4359 if (!empty($args['iwp_ftp']['use_sftp'])) {
4360 $services = array('sftp');
4361 $files = $backup['ftp'];
4362 }else{
4363 $services = array('ftp');
4364 $files = $backup['ftp'];
4365
4366 }
4367 $type = 'ftp';
4368 }elseif (isset($backup['amazons3'])) {
4369 $services = array('s3');
4370 $files = $backup['amazons3'];
4371 $type = 's3';
4372 }elseif (isset($backup['dropbox'])) {
4373 $services = array('dropbox');
4374 $files = $backup['dropbox'];
4375 $type = 'dropbox';
4376 }elseif (isset($backup['gDrive'])) {
4377 $services = array('googledrive');
4378 $files = $backup['gDriveOrgFileName'];
4379 $type = 'gDrive';
4380
4381 }elseif (isset($backup['server'])) {
4382 $files = $backup['file_path'];
4383 }
4384 $cloudInstance = $this->createCloudInstance($type, $args);
4385 if (is_array($files)) {
4386 foreach ($files as $index => $file_name) {
4387 $itext = empty($index) ? '' : $index;
4388 $backup_size = $this->getCloudBackupSize($type, $cloudInstance, $files[$index], $args);
4389 // $known_size = isset($backup['size']) ? $this->toBytes($backup['size']) : 0;
4390 $known_size = isset($backup_size) ? $backup_size : 0;
4391 $file = $files[$index];
4392 $fullpath = $iwp_backup_dir.'/'.$file;
4393 if (!file_exists($fullpath)) {
4394 $findex = $index;
4395 break;
4396 } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4397 $findex = $index;
4398 break;
4399 }else{
4400 $file = '';
4401 }
4402 }
4403 }else {
4404 $file = $files;
4405 $fullpath = $iwp_backup_dir.'/'.$file;
4406 $findex = '';
4407 $itext = empty($findex) ? '' : $findex;
4408 $backup_size = $this->getCloudBackupSize($type, $cloudInstance, $file, $args);
4409 // $known_size = isset($backup['size']) ? $this->toBytes($backup['size']) : 0;
4410 $known_size = isset($backup_size) ? $backup_size : 0;
4411 if (!file_exists($fullpath)) {
4412 $findex = $index;
4413 } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
4414 $findex = $index;
4415 }else{
4416 $file = '';
4417 }
4418
4419 }
4420 set_error_handler(array($iwp_backup_core, 'php_error'), E_ALL & ~E_STRICT);
4421 $iwp_backup_core->log("Requested to obtain file: Old History ID=$timestamp, type=full, index=all");
4422 $iwp_backup_core->jobdata_set('service', $services);
4423
4424 }
4425 // TODO: FIXME: Failed downloads may leave log files forever (though they are small)
4426
4427
4428 // Fetch it from the cloud, if we have not already got it
4429
4430 $needs_downloading = false;
4431 if (!file_exists($fullpath)) {
4432 //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
4433 $needs_downloading = true;
4434 $iwp_backup_core->log('File does not yet exist locally - needs downloading');
4435 } elseif ($known_size > 0 && filesize($fullpath)+10 < $known_size) {
4436 $iwp_backup_core->log("The file was found locally (".filesize($fullpath).") but did not match the size in the backup history ($known_size) - will resume downloading");
4437 $needs_downloading = true;
4438 } else{
4439 return array('success' => 'completed', 'already_closed' => $needs_downloading, 'backup_dir' => $iwp_backup_dir);
4440 }
4441
4442 // The AJAX responder that updates on progress wants to see this
4443 $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath");
4444
4445 if ($needs_downloading) {
4446
4447 // Update the "last modified" time to dissuade any other instances from thinking that no downloaders are active
4448 @touch($fullpath);
4449
4450 $msg = array(
4451 'result' => 'needs_download',
4452 'request' => array(
4453 'type' => $type,
4454 'timestamp' => $timestamp,
4455 'findex' => $findex
4456 )
4457 );
4458
4459 $return = $this->get_remote_file($services, $file, $timestamp);
4460 }
4461
4462 // Now, be ready to spool the thing to the browser
4463 if (is_file($fullpath) && is_readable($fullpath) && $return !== false) {
4464
4465 $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'downloaded:'.filesize($fullpath).":$fullpath");
4466
4467 $result = 'downloaded';
4468 // That message is then picked up by the AJAX listener
4469
4470 } else {
4471
4472 $iwp_backup_core->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'failed');
4473 $iwp_backup_core->jobdata_set('dlerrors_'.$timestamp.'_'.$type.'_'.$findex, $iwp_backup_core->errors);
4474 $iwp_backup_core->log('Remote fetch failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.');
4475
4476 $result = 'download_failed';
4477 }
4478
4479 restore_error_handler();
4480
4481 @fclose($iwp_backup_core->logfile_handle);
4482 if (!$debug_mode) @unlink($iwp_backup_core->logfile_name);
4483
4484 // The browser connection was possibly already closed, but not necessarily
4485 return array('success' => $result, 'already_closed' => $needs_downloading);
4486
4487 }
4488
4489 public function createCloudInstance($type, $args){
4490 global $iwp_backup_core;
4491 if ($type == 'dropbox') {
4492 extract($args['iwp_dropbox']);
4493 if(!isset($dropbox_email) && empty($dropbox_email)){
4494 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/API.php';
4495 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/Exception.php';
4496 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php';
4497 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/lib/Dropbox/OAuth/Consumer/Curl.php';
4498
4499 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/backup/dropbox.php';
4500
4501 $oauth = new IWP_Dropbox_OAuth_Consumer_Curl($dropbox_app_key, $dropbox_app_secure_key);
4502 $oauth->setToken($dropbox_access_token);
4503 $dropbox = new IWP_Dropbox_API($oauth);
4504
4505 }else{
4506 require_once $GLOBALS['iwp_mmb_plugin_dir'] . '/backup/dropbox.php';
4507
4508 try{
4509 set_iwp_dropbox_auth_setting($args['iwp_dropbox']);
4510 $helper = new IWP_MMB_UploadModule_dropbox();
4511 $dropbox = $helper->bootstrap();
4512 }
4513 catch(Exception $e){
4514 return false;
4515 }
4516 }
4517 return $dropbox;
4518 }elseif ($type == 'ftp') {
4519 extract($args['iwp_ftp']);
4520 if(isset($use_sftp) && $use_sftp==1) {
4521 $port = $ftp_port ? $ftp_port : 22; //default port is 22
4522 /*
4523 * SFTP section start here phpseclib library is used for this functionality
4524 */
4525 $path = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/phpseclib/phpseclib/phpseclib';
4526 set_include_path(get_include_path() . PATH_SEPARATOR . $path);
4527 include_once('Net/SFTP.php');
4528
4529
4530 $sftp = new Net_SFTP($ftp_hostname, $port);
4531 if(!$sftp) {
4532 return false;
4533 }
4534 $iwp_backup_core->ensure_phpseclib('Crypt_Blowfish', 'Crypt/Blowfish');
4535 if (!$sftp->login($ftp_username, $ftp_password)) {
4536 return false;
4537 } else {
4538 return $sftp;
4539 }
4540
4541 }
4542 $port = $ftp_port ? $ftp_port : 21; //default port is 21
4543 if (!empty($ftp_ssl)) {
4544 if (function_exists('ftp_ssl_connect')) {
4545 $conn_id = ftp_ssl_connect($ftp_hostname,$port);
4546 if ($conn_id === false) {
4547 return false;
4548 }
4549 } else {
4550 return false;
4551 }
4552 }
4553 else {
4554 if (function_exists('ftp_connect')) {
4555 $conn_id = ftp_connect($ftp_hostname,$port);
4556 if ($conn_id === false) {
4557 return false;
4558 }
4559 } else {
4560 return false;
4561 }
4562 }
4563
4564 $login = @ftp_login($conn_id, $ftp_username, $ftp_password);
4565 if ($login === false) {
4566 return false;
4567 }
4568
4569 if(!empty($ftp_passive)){
4570 @ftp_pasv($conn_id,true);
4571 }
4572 return $conn_id;
4573 }elseif ($type == 'gDrive') {
4574 require_once $GLOBALS['iwp_mmb_plugin_dir'].'/backup/googledrive.php';
4575 $obj = new IWP_MMB_UploadModule_googledrive();
4576 return $obj;
4577 }
4578 }
4579
4580 public function getCloudBackupSize($type, &$obj, $backup_file, $args){
4581 require_once($GLOBALS['iwp_mmb_plugin_dir']."/backup.class.multicall.php");
4582 $backup_instance = new IWP_MMB_Backup_Multicall();
4583 if ($type == 'dropbox') {
4584 extract($args['iwp_dropbox']);
4585 $oldRoot = 'Apps/InfiniteWP/';
4586 $dropbox_destination = $oldRoot.ltrim(trim($dropbox_destination), '/');
4587 $dropbox_destination = rtrim($dropbox_destination, '/');
4588 if (isset($dropbox_site_folder) && $dropbox_site_folder == true){
4589 $dropbox_destination .= '/'.$backup_instance->site_name;
4590 }
4591 $folders = explode('/',$dropbox_destination);
4592 if(!isset($path)){
4593 $path = '';
4594 }
4595 foreach ($folders as $key => $name) {
4596 $path.=trim($name).'/';
4597 }
4598 $destFile = trim($path, '/').'/';
4599 $filename = basename($backup_file);
4600 $destFile .= $filename;
4601 $dBoxMetaData = $obj -> metaData($destFile);
4602 if (empty($dBoxMetaData['body']->size)) {
4603 return false;
4604 }else{
4605 return $dBoxMetaData['body']->size;
4606 }
4607
4608 }elseif ($type == 's3') {
4609 extract($args['iwp_amazon_s3']);
4610 if(!isset($destination)){
4611 $destination = '';
4612 }
4613 if(!isset($path)){
4614 $path = '';
4615 }
4616 if (isset($as3_site_folder) && $as3_site_folder == true){
4617 $destination .= '/'.$backup_instance->site_name;
4618 }
4619 $folders = explode('/',$destination);
4620 foreach ($folders as $key => $name) {
4621 $path.=trim($name).'/';
4622 }
4623 $destFile = trim($path, '/').'/';
4624 $filename = basename($backup_file);
4625 $destFile .= $filename;
4626 if(1 || is_new_s3_compatible()){
4627 require_once $GLOBALS['iwp_mmb_plugin_dir'].'/lib/amazon/s3IWPBackup.php';
4628 if(!class_exists('S3Client')){
4629 require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/amazon/autoload.php');
4630 }
4631 $new_s3_obj = new IWP_MMB_S3_MULTICALL();
4632 if(!isset($size1)){
4633 $size1 = 0;
4634 }
4635 if(!isset($size2)){
4636 $size2 = 0;
4637 }
4638 return $new_s3_obj->postUploadS3Verification($backup_file, $destFile, $type, $as3_bucket, $as3_access_key, $as3_secure_key, $as3_bucket_region, $size1, $size2, $return_size = true);
4639 }
4640 else{
4641 return $backup_instance->postUploadS3VerificationBwdComp($backup_file, $destFile, $obj, $type, $as3_bucket, $as3_access_key, $as3_secure_key, $as3_bucket_region, $actual_file_size, $size1, $size2, $return_size = true);
4642 }
4643 }elseif ($type == 'ftp') {
4644 extract($args['iwp_ftp']);
4645
4646 $destination = trim($ftp_remote_folder, '/');
4647 if (isset($ftp_site_folder) && $ftp_site_folder == true){
4648 $destination .= '/'.$backup_instance->site_name;
4649 }
4650 if(!isset($path)){
4651 $path = '';
4652 }
4653 $folders = explode('/',$destination);
4654 foreach ($folders as $key => $name) {
4655 $path.=trim($name).'/';
4656 }
4657 $destFile = trim($path, '/').'/';
4658 $filename = basename($backup_file);
4659 $destFile .= $filename;
4660 if(isset($use_sftp) && $use_sftp==1) {
4661 $destFile = '/'.$destFile;
4662 $ftp_file_size = $obj->size($destFile);
4663 }else{
4664 ftp_chdir ($obj , dirname($destFile));
4665 $ftp_file_size = ftp_size($obj, basename($destFile));
4666 }
4667 if($ftp_file_size > 0)
4668 {
4669 return $ftp_file_size;
4670 }
4671 else
4672 {
4673 return false;
4674 }
4675 }elseif ($type == 'gDrive') {
4676 $return = $obj->get_backup_file_size($backup_file);
4677 if ($return >0 ) {
4678 return $return;
4679 }
4680 }
4681 }
4682
4683 public function get_requested_task($ID){
4684 global $wpdb;
4685 $table_name = $wpdb->base_prefix . "iwp_backup_status";
4686
4687 $rows = $wpdb->get_row($wpdb->prepare("SELECT * FROM ".$table_name." WHERE historyID = %d ORDER BY ID DESC LIMIT 1", $ID), ARRAY_A);
4688
4689 return $rows;
4690
4691 }
4692
4693 public function toBytes($str){
4694 $val = str_replace(array(' MB', ' KB'),'', $str);
4695 $last = strtolower($str[strlen($str)-2]);
4696 switch($last) {
4697 case 'g': $val *= 1024;
4698 case 'm': $val *= 1024;
4699 case 'k': $val *= 1024;
4700 }
4701 return $val;
4702 }
4703
4704 public function createBackupMetaFile($our_files){
4705 $site_name = str_replace(array(
4706 "_",
4707 "/",
4708 "~"
4709 ), array(
4710 "",
4711 "-",
4712 "-"
4713 ), rtrim(remove_http(get_bloginfo('url')), "/"));
4714 $backup_file_basename = $site_name.'_backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $this->backup_time), 'Y-m-d-Hi').'_'.$this->blog_name.'_'.$this->nonce.'_backup_meta_'.$this->get_wordpress_version().'.tmp';
4715 $our_files['wp_content_url'] = content_url();
4716 $our_files['wp_content_path'] = WP_CONTENT_DIR;
4717 $our_files['backup_meta_file'] = $backup_file_basename;
4718 $our_files['old_file_path'] = ABSPATH;
4719 $our_files['old_url'] = get_option('siteurl');
4720 $our_files['IWP_encryptionphrase'] = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase');
4721 $backup_dir = $this->backups_dir_location();
4722 $backup_meta_file = $backup_dir.'/'.$backup_file_basename;
4723 $meta_file_handle = fopen($backup_meta_file, 'w');
4724 if ($meta_file_handle == false) {
4725 return false;
4726 }
4727 @fwrite($meta_file_handle, "<?php"."\n".'$backup_meta_files ='."'".serialize($our_files)."';\n");
4728 fclose($meta_file_handle);
4729 return $backup_file_basename;
4730 }
4731
4732 public function delete_backup($opts) {
4733
4734 $backups = $this->get_backup_history();
4735 $timestamps = (string)$opts['result_id'];
4736
4737 $remote_delete_limit = (isset($opts['remote_delete_limit']) && $opts['remote_delete_limit'] > 0) ? (int)$opts['remote_delete_limit'] : PHP_INT_MAX;
4738
4739 $timestamps = explode(',', $timestamps);
4740 $delete_remote = empty($opts['delete_remote']) ? true : true;
4741
4742 // You need a nonce before you can set job data. And we certainly don't yet have one.
4743 // $this->backup_time_nonce();
4744 // // Set the job type before logging, as there can be different logging destinations
4745 // $this->jobdata_set('job_type', 'delete');
4746 // $this->jobdata_set('job_time_ms', $this->job_time_ms);
4747
4748 if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4749 $this->logfile_open($this->nonce);
4750 set_error_handler(array($this, 'php_error'), E_ALL & ~E_STRICT);
4751 }
4752
4753 $iwp_backup_dir = $this->backups_dir_location();
4754 $backupable_entities = $this->get_backupable_file_entities(true, true);
4755
4756 $local_deleted = 0;
4757 $remote_deleted = 0;
4758 $sets_removed = 0;
4759 foreach ($timestamps as $i => $timestamp) {
4760
4761 if (!isset($backups[$timestamp])) {
4762 return array('result' => 'error', 'message' => __('Backup set not found', 'InfiniteWP'));
4763 }
4764
4765 $nonce = isset($backups[$timestamp]['nonce']) ? $backups[$timestamp]['nonce'] : '';
4766
4767 $delete_from_service = array();
4768
4769 if ($delete_remote) {
4770 // Locate backup set
4771 if (isset($backups[$timestamp]['service'])) {
4772 // Convert to an array so that there is no uncertainty about how to process it
4773 $services = is_string($backups[$timestamp]['service']) ? array($backups[$timestamp]['service']) : $backups[$timestamp]['service'];
4774 if (is_array($services)) {
4775 foreach ($services as $service) {
4776 if ($service && $service != 'none' && $service != 'email') $delete_from_service[] = $service;
4777 }
4778 }
4779 }
4780
4781 if (isset($backups[$timestamp]['service_setting'])) {
4782 $service_setting = $backups[$timestamp]['service_setting'];
4783 if (isset($service_setting['dropbox_site_folder'])) {
4784 set_iwp_dropbox_auth_setting($service_setting);
4785 }
4786 }
4787 }
4788
4789 $files_to_delete = array();
4790 foreach ($backupable_entities as $key => $ent) {
4791 if (isset($backups[$timestamp][$key])) {
4792 $files_to_delete[$key] = $backups[$timestamp][$key];
4793 }
4794 }
4795 // Delete DB
4796 foreach ($backups[$timestamp] as $key => $value){
4797 if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) {
4798 $files_to_delete[$key] = $backups[$timestamp][$key];
4799 }
4800 }
4801
4802 // Also delete the log
4803 if ($nonce && !IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4804 $files_to_delete['log'] = "log.$nonce.txt";
4805 }
4806 if (!empty($backups[$timestamp]['backup_file_basename'])) {
4807 $files_to_delete['backup_file_basename'] = $backups[$timestamp]['backup_file_basename'];
4808 }
4809 $this->register_wp_http_option_hooks();
4810
4811 foreach ($files_to_delete as $key => $files) {
4812
4813 if (is_string($files)) {
4814 $was_string = true;
4815 $files = array($files);
4816 } else {
4817 $was_string = false;
4818 }
4819
4820 foreach ($files as $file) {
4821 if (is_file($iwp_backup_dir.'/'.$file) && @unlink($iwp_backup_dir.'/'.$file)) $local_deleted++;
4822 }
4823
4824 if ('log' != $key && count($delete_from_service) > 0) {
4825
4826 $storage_objects_and_ids = $this->get_storage_objects_and_ids($delete_from_service);
4827
4828 foreach ($delete_from_service as $service) {
4829
4830 if ('email' == $service || 'none' == $service || !$service) continue;
4831
4832 $deleted = -1;
4833
4834 $remote_obj = $storage_objects_and_ids[$service]['object'];
4835
4836 $instance_settings = $storage_objects_and_ids[$service]['instance_settings'];
4837 $this->backups_instance_ids = empty($backups[$timestamp]['service_instance_ids'][$service]) ? array() : $backups[$timestamp]['service_instance_ids'][$service];
4838
4839 uksort($instance_settings, array($this, 'instance_ids_sort'));
4840
4841 foreach ($instance_settings as $instance_id => $options) {
4842
4843 $remote_obj->set_options($service_setting, false, $instance_id);
4844
4845 foreach ($files as $index => $file) {
4846 if ($remote_deleted == $remote_delete_limit) {
4847 return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed);
4848 }
4849
4850 $deleted = $remote_obj->delete($file);
4851
4852 if (-1 === $deleted) {
4853 } elseif (false !== $deleted) {
4854 $remote_deleted++;
4855 }
4856
4857 $itext = $index ? (string)$index : '';
4858 if ($was_string) {
4859 unset($backups[$timestamp][$key]);
4860 if ('db' == strtolower(substr($key, 0, 2))) unset($backups[$timestamp][$key][$index.'-size']);
4861 } else {
4862 unset($backups[$timestamp][$key][$index]);
4863 unset($backups[$timestamp][$key.$itext.'-size']);
4864 if (empty($backups[$timestamp][$key])) unset($backups[$timestamp][$key]);
4865 }
4866 if (isset($backups[$timestamp]['checksums']) && is_array($backups[$timestamp]['checksums'])) {
4867 foreach (array_keys($backups[$timestamp]['checksums']) as $algo) {
4868 unset($backups[$timestamp]['checksums'][$algo][$key.$index]);
4869 }
4870 }
4871
4872 // If we don't save the array back, then the above section will fire again for the same files - and the remote storage will be requested to delete already-deleted files, which then means no time is actually saved by the browser-backend loop method.
4873 $this->save_history($backups);
4874 }
4875 }
4876 }
4877 }
4878 }
4879
4880 unset($backups[$timestamp]);
4881 $this->save_history($backups);
4882 $sets_removed++;
4883 }
4884
4885 return $this->remove_backup_set_cleanup(true, $backups, $local_deleted, $remote_deleted, $sets_removed);
4886
4887 }
4888
4889 public function remove_backup_set_cleanup($delete_complete, $backups, $local_deleted, $remote_deleted, $sets_removed) {
4890
4891 $this->register_wp_http_option_hooks(false);
4892
4893 $this->save_history($backups);
4894
4895 $this->log("Local files deleted: $local_deleted. Remote files deleted: $remote_deleted");
4896
4897 if ($delete_complete) {
4898 $set_message = __('Backup sets removed:', 'InfiniteWP');
4899 $local_message = __('Local files deleted:', 'InfiniteWP');
4900 $remote_message = __('Remote files deleted:', 'InfiniteWP');
4901
4902 if (IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
4903 restore_error_handler();
4904 }
4905
4906 return array('result' => 'success', 'set_message' => $set_message, 'local_message' => $local_message, 'remote_message' => $remote_message, 'backup_sets' => $sets_removed, 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted);
4907 } else {
4908
4909 return array('result' => 'continue', 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted, 'backup_sets' => $sets_removed);
4910 }
4911 }
4912
4913 public function save_history($backup_history, $use_cache = true) {
4914 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_backup_history', $backup_history, $use_cache);
4915 }
4916
4917 public function instance_ids_sort($a, $b) {
4918 if (in_array($a, $this->backups_instance_ids)) {
4919 if (in_array($b, $this->backups_instance_ids)) return 0;
4920 return -1;
4921 }
4922 return in_array($b, $this->backups_instance_ids) ? 1 : 0;
4923 }
4924
4925 public function activejobs_delete($job_id) {
4926
4927 if (preg_match("/^[0-9a-f]{12}$/", $job_id)) {
4928
4929 global $iwp_backup_core;
4930 $cron = get_option('cron');
4931 $found_it = false;
4932 $iwp_backup_dir = $iwp_backup_core->backups_dir_location();
4933 if (file_exists($iwp_backup_dir.'/log.'.$job_id.'.txt')) touch($iwp_backup_dir.'/deleteflag-'.$job_id.'.txt');
4934 foreach ($cron as $time => $job) {
4935 if (isset($job['IWP_backup_resume'])) {
4936 foreach ($job['IWP_backup_resume'] as $hook => $info) {
4937 if (isset($info['args'][1]) && $info['args'][1] == $job_id) {
4938 $args = $cron[$time]['IWP_backup_resume'][$hook]['args'];
4939 wp_unschedule_event($time, 'IWP_backup_resume', $args);
4940 if (!$found_it) return array('ok' => 'Y', 'c' => 'deleted', 'm' => __('Job deleted', 'InfiniteWP'));
4941 $found_it = true;
4942 }
4943 }
4944 }
4945 }
4946 }
4947
4948 if (!$found_it) return true;
4949
4950 }
4951
4952 public function kill_new_backup($params){
4953 global $iwp_mmb_core;
4954 $this->activejobs_delete($params['result_id']);
4955 $backups = $this->get_backup_history();
4956 $this->delete_backup_by_id($params['result_id']);
4957 delete_option('IWP_jobdata_'.$params['result_id']);
4958 $iwp_mmb_core->iwp_delete_option('IWP_backup_status');
4959 delete_option('IWP_semaphore_fd');
4960 delete_option('IWP_locked_fd');
4961 delete_option('IWP_unlocked_fd');
4962 delete_option('IWP_semaphore_d');
4963 delete_option('IWP_unlocked_d');
4964 delete_option('IWP_locked_d');
4965 wp_clear_scheduled_hook('IWP_backup_resume');
4966 /*if (!empty($backups)) {
4967 foreach ($backups as $key => $value) {
4968 if ($value['nonce'] == $params['result_id']) {
4969 $params['result_id'] = $key;
4970 }
4971 }*/
4972 return $this->delete_backup($params);
4973 //}
4974
4975 return true;
4976 }
4977 public function dropbox_modpath($file, $obj){
4978 $opts = $obj->get_options();
4979 $dropbox_site_folder = $opts['dropbox_site_folder'];
4980 $dropbox_destination = $opts['folder'];
4981 $path= '';
4982 if (isset($dropbox_site_folder) && $dropbox_site_folder == true){
4983 $site_name = iwp_getSiteName();
4984 $dropbox_destination .= '/' . $site_name . '/';
4985 }
4986 else{
4987 $dropbox_destination .= '/';
4988 }
4989 $oldRoot = 'Apps/InfiniteWP/';
4990 $dropbox_destination = $oldRoot.ltrim(trim($dropbox_destination), '/');
4991 $dropbox_destination = rtrim($dropbox_destination, '/');
4992 $folders = explode('/',$dropbox_destination);
4993 foreach ($folders as $key => $name) {
4994 $path.=trim($name).'/';
4995 }
4996 $dropbox_destination = $path;
4997 $dropbox_folder = untrailingslashit($dropbox_destination);
4998 if (strpos($file, $dropbox_folder) === false) {
4999 $dropbox_folder.= '/'.$file;
5000 }else{
5001 $dropbox_folder = $file;
5002 }
5003 return $dropbox_folder;
5004 }
5005
5006 public function get_timestamp_by_label($label){
5007 $new_backup_keys = array();
5008 $new_backups = $this->get_backup_history();
5009 if (!empty($new_backups)) {
5010 foreach ($new_backups as $timestamp => $value) {
5011 if ($label == $value['label']) {
5012 $new_backup_keys[$timestamp] = $value;
5013 }
5014 }
5015 ksort($new_backup_keys);
5016 }
5017
5018 return $new_backup_keys;
5019 }
5020
5021 public function set_cloud_upload_setting($params){
5022 if (!empty($params['account_info'])) {
5023 if (!empty($params['account_info']['iwp_ftp'])) {
5024 $ftp_details = $params['account_info']['iwp_ftp'];
5025 $opts = array(
5026 'user' => $ftp_details['ftp_username'],
5027 'pass' => $ftp_details['ftp_password'],
5028 'host' => $ftp_details['ftp_hostname'],
5029 'path' => $ftp_details['ftp_remote_folder'],
5030 'port' => $ftp_details['ftp_port'],
5031 'ftp_site_folder' => $ftp_details['ftp_site_folder'],
5032 'passive' => $ftp_details['ftp_passive']?true:false
5033 );
5034 if ($ftp_details['use_sftp']) {
5035 update_option('IWP_service', 'sftp');
5036 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_sftp', $opts);
5037 }else{
5038 update_option('IWP_service', 'ftp');
5039 if(!empty($ftp_details['ftp_ssl'])){
5040 $opts['host'] = $opts['host'].':'.$opts['port'];
5041 unset($opts['port']);
5042 IWP_MMB_Backup_Options::delete_iwp_backup_option('IWP_ssl_nossl');
5043 }else{
5044 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ssl_nossl', 1);
5045 }
5046 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_ftp', $opts);
5047 }
5048 }elseif (!empty($params['account_info']['iwp_amazon_s3'])) {
5049 $s3_details = $params['account_info']['iwp_amazon_s3'];
5050 if (!empty($s3_details['as3_directory'])) {
5051 $path = trim($s3_details['as3_bucket'],'/').'/'.trim($s3_details['as3_directory'],'/');
5052 }else{
5053 $path = $s3_details['as3_bucket'];
5054 }
5055 $opts = array(
5056 'endpoint' => '',
5057 'accesskey' => $s3_details['as3_access_key'],
5058 'secretkey' => $s3_details['as3_secure_key'],
5059 'path' => $path,
5060 'as3_site_folder' => $s3_details['as3_site_folder'],
5061 'server_side_encryption' => $s3_details['server_side_encryption']?true:false
5062 );
5063 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_s3', $opts);
5064
5065 }elseif (!empty($params['account_info']['iwp_dropbox'])) {
5066 $dropbox_details = $params['account_info']['iwp_dropbox'];
5067
5068 $opts = array(
5069 'appkey' => $dropbox_details['dropbox_app_key'],
5070 'secret' => $dropbox_details['dropbox_app_secure_key'],
5071 'tk_access_token' => $dropbox_details['dropbox_access_token'],
5072 'folder' => $dropbox_details['dropbox_destination'],
5073 'ownername' => '',
5074 'CSRF' => '',
5075 'dropbox_site_folder' => $dropbox_details['dropbox_site_folder']
5076 );
5077 if ( !empty($dropbox_details['dropbox_email']) ) {
5078 $opts['email'] = $dropbox_details['dropbox_email'];
5079 }
5080 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_dropbox', $opts);
5081 }elseif (!empty($params['account_info']['iwp_gdrive'])) {
5082 $google_details = $params['account_info']['iwp_gdrive'];
5083 $opts = array(
5084 'clientid' => $google_details['clientID'],
5085 'secret' => $google_details['clientSecretKey'],
5086 'token' => $google_details['token']['refresh_token'],
5087 'tmp_access_token' => $google_details['token']['access_token'],
5088 'gdrive_site_folder' => $google_details['gdrive_site_folder'],
5089 'ownername' => ''
5090 );
5091 IWP_MMB_Backup_Options::update_iwp_backup_option('IWP_googledrive', $opts);
5092 }
5093 }
5094 }
5095
5096 function delete_backup_by_id($backup_id){
5097 $iwp_backup_dir = $this->backups_dir_location();
5098
5099 if (!$handle = opendir($iwp_backup_dir)) return;
5100
5101 // See if there are any more files in the local directory than the ones already known about
5102 while (false !== ($entry = readdir($handle))) {
5103 if (strrpos($entry, $backup_id) /*&& strrpos($entry, 'log.') === false*/ && strrpos($entry, 'deleteflag-') === false) {
5104 @unlink($iwp_backup_dir.'/'.$entry);
5105 }
5106 }
5107 }
5108
5109 public function is_cron_do_action_need($job_id){
5110 $time = time();
5111 $cron_time = $this->get_cron($job_id);
5112 if (empty($cron_time[0]) || $time < $cron_time[0]) {
5113 return false;
5114 }
5115
5116 return true;
5117 }
5118
5119 public function iwp_pheonix_backup_cron_do_action($params){
5120 $job_id = $params['params']['backup_id'];
5121
5122 $is_cron_do_action_need = $this->is_cron_do_action_need($job_id);
5123 if ($is_cron_do_action_need == false) {
5124 return false;
5125 }
5126 $cron_data = $this->get_cron_data($job_id);
5127 if (!empty($cron_data)) {
5128 wp_clear_scheduled_hook('IWP_backup_resume', $cron_data);
5129 do_action( 'IWP_backup_resume', $cron_data[0], $cron_data[1] );
5130 }
5131
5132 }
5133
5134 public function restore_loop_break(){
5135 $endTime = microtime(true);
5136 $timeTaken = $endTime - $GLOBALS['IWP_BACKUP_RESTORE_INIT_TIME'];
5137 $cuttOffTime = defined('IWP_RESTORE_LOOP_BREAK_TIME')?IWP_RESTORE_LOOP_BREAK_TIME:25;
5138 if($timeTaken > $cuttOffTime){
5139 return true;
5140 }
5141
5142 return false;
5143 }
5144 }
5145