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

admin.php in UpdraftPlus: WP Backup & Migration Plugin 1.22.19, at admin.php

6,259 lines 295.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
4
5 // Admin-area code lives here. This gets called in admin_menu, earlier than admin_init
6
7 global $updraftplus_admin;
8 if (!is_a($updraftplus_admin, 'UpdraftPlus_Admin')) $updraftplus_admin = new UpdraftPlus_Admin();
9
10 class UpdraftPlus_Admin {
11
12 public $logged = array();
13
14 private $template_directories;
15
16 private $backups_instance_ids;
17
18 private $auth_instance_ids = array('dropbox' => array(), 'onedrive' => array(), 'googledrive' => array(), 'googlecloud' => array());
19
20 private $clone_php_versions = array('5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1');
21
22 private $storage_service_without_settings;
23
24 private $storage_service_with_partial_settings;
25
26 private $storage_module_option_errors = '';
27
28 private $no_settings_warning = false;
29
30 private $restore_in_progress_jobdata = array();
31
32 private $entities_to_restore = array();
33
34 /**
35 * Constructor
36 */
37 public function __construct() {
38 $this->admin_init();
39 }
40
41 /**
42 * Get the path to the UI templates directory
43 *
44 * @return String - a filesystem directory path
45 */
46 public function get_templates_dir() {
47 return apply_filters('updraftplus_templates_dir', UpdraftPlus_Manipulation_Functions::wp_normalize_path(UPDRAFTPLUS_DIR.'/templates'));
48 }
49
50 private function register_template_directories() {
51
52 $template_directories = array();
53
54 $templates_dir = $this->get_templates_dir();
55
56 if ($dh = opendir($templates_dir)) {
57 while (($file = readdir($dh)) !== false) {
58 if ('.' == $file || '..' == $file) continue;
59 if (is_dir($templates_dir.'/'.$file)) {
60 $template_directories[$file] = $templates_dir.'/'.$file;
61 }
62 }
63 closedir($dh);
64 }
65
66 // This is the optimal hook for most extensions to hook into
67 $this->template_directories = apply_filters('updraftplus_template_directories', $template_directories);
68
69 }
70
71 /**
72 * Output, or return, the results of running a template (from the 'templates' directory, unless a filter over-rides it). Templates are run with $updraftplus, $updraftplus_admin and $wpdb set.
73 *
74 * @param String $path - path to the template
75 * @param Boolean $return_instead_of_echo - by default, the template is echo-ed; set this to instead return it
76 * @param Array $extract_these - variables to inject into the template's run context
77 *
78 * @return Void|String
79 */
80 public function include_template($path, $return_instead_of_echo = false, $extract_these = array()) {
81 if ($return_instead_of_echo) ob_start();
82
83 if (preg_match('#^([^/]+)/(.*)$#', $path, $matches)) {
84 $prefix = $matches[1];
85 $suffix = $matches[2];
86 if (isset($this->template_directories[$prefix])) {
87 $template_file = $this->template_directories[$prefix].'/'.$suffix;
88 }
89 }
90
91 if (!isset($template_file)) $template_file = UPDRAFTPLUS_DIR.'/templates/'.$path;
92
93 $template_file = apply_filters('updraftplus_template', $template_file, $path);
94
95 do_action('updraftplus_before_template', $path, $template_file, $return_instead_of_echo, $extract_these);
96
97 if (!file_exists($template_file)) {
98 error_log("UpdraftPlus: template not found: $template_file");
99 echo __('Error:', 'updraftplus').' '.__('template not found', 'updraftplus')." ($path)";
100 } else {
101 extract($extract_these);
102 global $updraftplus, $wpdb;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
103 $updraftplus_admin = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
104 include $template_file;
105 }
106
107 do_action('updraftplus_after_template', $path, $template_file, $return_instead_of_echo, $extract_these);
108
109 if ($return_instead_of_echo) return ob_get_clean();
110 }
111
112 /**
113 * Add actions for any needed dashboard notices for remote storage services
114 *
115 * @param String|Array $services - a list of services, or single service
116 */
117 private function setup_all_admin_notices_global($services) {
118
119 global $updraftplus;
120
121 if ('googledrive' === $services || (is_array($services) && in_array('googledrive', $services))) {
122 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('googledrive');
123
124 if (is_wp_error($settings)) {
125 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
126 $this->storage_module_option_errors .= "Google Drive (".$settings->get_error_code()."): ".$settings->get_error_message();
127 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
128 $updraftplus->log_wp_error($settings, true, true);
129 } elseif (!empty($settings['settings'])) {
130 foreach ($settings['settings'] as $instance_id => $storage_options) {
131 if ((defined('UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP') && UPDRAFTPLUS_CUSTOM_GOOGLEDRIVE_APP) || !empty($storage_options['clientid'])) {
132 if (!empty($storage_options['clientid'])) {
133 $clientid = $storage_options['clientid'];
134 $token = empty($storage_options['token']) ? '' : $storage_options['token'];
135 }
136 if (!empty($clientid) && '' == $token) {
137 if (!in_array($instance_id, $this->auth_instance_ids['googledrive'])) $this->auth_instance_ids['googledrive'][] = $instance_id;
138 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'));
139 }
140 unset($clientid);
141 unset($token);
142 } else {
143 if (empty($storage_options['user_id'])) {
144 if (!in_array($instance_id, $this->auth_instance_ids['googledrive'])) $this->auth_instance_ids['googledrive'][] = $instance_id;
145 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googledrive'));
146 }
147 }
148 }
149 }
150 }
151 if ('googlecloud' === $services || (is_array($services) && in_array('googlecloud', $services))) {
152 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('googlecloud');
153
154 if (is_wp_error($settings)) {
155 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
156 $this->storage_module_option_errors .= "Google Cloud (".$settings->get_error_code()."): ".$settings->get_error_message();
157 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
158 $updraftplus->log_wp_error($settings, true, true);
159 } elseif (!empty($settings['settings'])) {
160 foreach ($settings['settings'] as $instance_id => $storage_options) {
161 if ((defined('UPDRAFTPLUS_CUSTOM_GOOGLECLOUD_APP') && UPDRAFTPLUS_CUSTOM_GOOGLECLOUD_APP) || !empty($storage_options['clientid'])) {
162 $clientid = $storage_options['clientid'];
163 $token = (empty($storage_options['token'])) ? '' : $storage_options['token'];
164
165 if (!empty($clientid) && empty($token)) {
166 if (!in_array($instance_id, $this->auth_instance_ids['googlecloud'])) $this->auth_instance_ids['googlecloud'][] = $instance_id;
167 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud'));
168 }
169 } else {
170 if (empty($storage_options['user_id'])) {
171 if (!in_array($instance_id, $this->auth_instance_ids['googlecloud'])) $this->auth_instance_ids['googlecloud'][] = $instance_id;
172 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud'))) add_action('all_admin_notices', array($this, 'show_admin_warning_googlecloud'));
173 }
174 }
175 }
176 }
177 }
178
179 if ('dropbox' === $services || (is_array($services) && in_array('dropbox', $services))) {
180 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('dropbox');
181
182 if (is_wp_error($settings)) {
183 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
184 $this->storage_module_option_errors .= "Dropbox (".$settings->get_error_code()."): ".$settings->get_error_message();
185 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
186 $updraftplus->log_wp_error($settings, true, true);
187 } elseif (!empty($settings['settings'])) {
188 foreach ($settings['settings'] as $instance_id => $storage_options) {
189 if (empty($storage_options['tk_access_token'])) {
190 if (!in_array($instance_id, $this->auth_instance_ids['dropbox'])) $this->auth_instance_ids['dropbox'][] = $instance_id;
191 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_dropbox'))) add_action('all_admin_notices', array($this, 'show_admin_warning_dropbox'));
192 }
193 }
194 }
195 }
196
197 if ('onedrive' === $services || (is_array($services) && in_array('onedrive', $services))) {
198 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('onedrive');
199
200 if (is_wp_error($settings)) {
201 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
202 $this->storage_module_option_errors .= "OneDrive (".$settings->get_error_code()."): ".$settings->get_error_message();
203 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
204 $updraftplus->log_wp_error($settings, true, true);
205 } elseif (!empty($settings['settings'])) {
206 foreach ($settings['settings'] as $instance_id => $storage_options) {
207 if ((defined('UPDRAFTPLUS_CUSTOM_ONEDRIVE_APP') && UPDRAFTPLUS_CUSTOM_ONEDRIVE_APP)) {
208 if (!empty($storage_options['clientid']) && !empty($storage_options['secret']) && empty($storage_options['refresh_token'])) {
209 if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id;
210 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'));
211 } elseif (empty($storage_options['refresh_token'])) {
212 if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id;
213 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'));
214 }
215 } else {
216 if (empty($storage_options['refresh_token'])) {
217 if (!in_array($instance_id, $this->auth_instance_ids['onedrive'])) $this->auth_instance_ids['onedrive'][] = $instance_id;
218 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive'));
219 }
220 }
221
222 if (isset($storage_options['endpoint_tld']) && 'de' === $storage_options['endpoint_tld']) {
223 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_onedrive_germany'))) add_action('all_admin_notices', array($this, 'show_admin_warning_onedrive_germany'));
224 }
225 }
226 }
227 }
228
229 if ('azure' === $services || (is_array($services) && in_array('azure', $services))) {
230 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('azure');
231
232 if (is_wp_error($settings)) {
233 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
234 $this->storage_module_option_errors .= "Azure (".$settings->get_error_code()."): ".$settings->get_error_message();
235 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
236 $updraftplus->log_wp_error($settings, true, true);
237 } elseif (!empty($settings['settings'])) {
238 foreach ($settings['settings'] as $instance_id => $storage_options) {
239 if (isset($storage_options['endpoint']) && 'blob.core.cloudapi.de' === $storage_options['endpoint']) {
240 if (false === has_action('all_admin_notices', array($this, 'show_admin_warning_azure_germany'))) add_action('all_admin_notices', array($this, 'show_admin_warning_azure_germany'));
241 }
242 }
243 }
244 }
245
246 if ('updraftvault' === $services || (is_array($services) && in_array('updraftvault', $services))) {
247 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault');
248
249 if (is_wp_error($settings)) {
250 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
251 $this->storage_module_option_errors .= "UpdraftVault (".$settings->get_error_code()."): ".$settings->get_error_message();
252 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
253 $updraftplus->log_wp_error($settings, true, true);
254 } elseif (!empty($settings['settings'])) {
255 foreach ($settings['settings'] as $instance_id => $storage_options) {
256 if (empty($storage_options['token']) && empty($storage_options['email'])) {
257 add_action('all_admin_notices', array($this, 'show_admin_warning_updraftvault'));
258 }
259 }
260 }
261 }
262
263 if ($this->disk_space_check(1048576*35) === false) add_action('all_admin_notices', array($this, 'show_admin_warning_diskspace'));
264
265 $all_services = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids($updraftplus->get_canonical_service_list());
266
267 $this->storage_service_without_settings = array();
268 $this->storage_service_with_partial_settings = array();
269
270 foreach ($all_services as $method => $sinfo) {
271 if (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) continue;
272 foreach ($sinfo['instance_settings'] as $opt) {
273 if (!$sinfo['object']->options_exist($opt)) {
274 if (isset($opt['auth_in_progress'])) {
275 $this->storage_service_with_partial_settings[$method] = $updraftplus->backup_methods[$method];
276 } else {
277 $this->storage_service_without_settings[] = $updraftplus->backup_methods[$method];
278 }
279 }
280 }
281 }
282
283 if (!empty($this->storage_service_with_partial_settings)) {
284 add_action('all_admin_notices', array($this, 'show_admin_warning_if_remote_storage_with_partial_setttings'));
285 }
286
287 if (!empty($this->storage_service_without_settings)) {
288 add_action('all_admin_notices', array($this, 'show_admin_warning_if_remote_storage_settting_are_empty'));
289 }
290
291 if ($updraftplus->is_restricted_hosting('only_one_backup_per_month')) {
292 add_action('all_admin_notices', array($this, 'show_admin_warning_one_backup_per_month'));
293 }
294 }
295
296 private function setup_all_admin_notices_udonly($service, $override = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use
297 global $updraftplus;
298
299 if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
300 @ini_set('display_errors', 1);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
301 if (defined('E_DEPRECATED')) {
302 @error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, PHPCompatibility.Constants.NewConstants.e_deprecatedFound -- Ok to ignore
303 } else {
304 @error_reporting(E_ALL & ~E_NOTICE);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
305 }
306 add_action('all_admin_notices', array($this, 'show_admin_debug_warning'));
307 }
308
309 if (null === UpdraftPlus_Options::get_updraft_option('updraft_interval')) {
310 add_action('all_admin_notices', array($this, 'show_admin_nosettings_warning'));
311 $this->no_settings_warning = true;
312 }
313
314 // Avoid false positives, by attempting to raise the limit (as happens when we actually do a backup)
315 if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
316 $max_execution_time = (int) @ini_get('max_execution_time');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
317 if ($max_execution_time>0 && $max_execution_time<20) {
318 add_action('all_admin_notices', array($this, 'show_admin_warning_execution_time'));
319 }
320
321 // LiteSpeed has a generic problem with terminating cron jobs
322 if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false) {
323 if (!is_file(ABSPATH.'.htaccess') || !preg_match('/noabort/i', file_get_contents(ABSPATH.'.htaccess'))) {
324 add_action('all_admin_notices', array($this, 'show_admin_warning_litespeed'));
325 }
326 }
327
328 if (version_compare($updraftplus->get_wordpress_version(), '3.2', '<')) add_action('all_admin_notices', array($this, 'show_admin_warning_wordpressversion'));
329
330 // DreamObjects west cluster shutdown warning
331 if ('dreamobjects' === $service || (is_array($service) && in_array('dreamobjects', $service))) {
332 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('dreamobjects');
333
334 if (is_wp_error($settings)) {
335 if (!isset($this->storage_module_option_errors)) $this->storage_module_option_errors = '';
336 $this->storage_module_option_errors .= "DreamObjects (".$settings->get_error_code()."): ".$settings->get_error_message();
337 add_action('all_admin_notices', array($this, 'show_admin_warning_multiple_storage_options'));
338 $updraftplus->log_wp_error($settings, true, true);
339 } elseif (!empty($settings['settings'])) {
340 foreach ($settings['settings'] as $storage_options) {
341 if ('objects-us-west-1.dream.io' == $storage_options['endpoint']) {
342 add_action('all_admin_notices', array($this, 'show_admin_warning_dreamobjects'));
343 }
344 }
345 }
346 }
347
348 // If the plugin was not able to connect to a UDC account due to lack of licences
349 if (isset($_GET['udc_connect']) && 0 == $_GET['udc_connect']) {
350 add_action('all_admin_notices', array($this, 'show_admin_warning_udc_couldnt_connect'));
351 }
352 }
353
354 /**
355 * Used to output the information for the next scheduled backup.
356 * moved to function for the ajax saves
357 */
358 public function next_scheduled_backups_output() {
359 // UNIX timestamp
360 $next_scheduled_backup = wp_next_scheduled('updraft_backup');
361 if ($next_scheduled_backup) {
362 // Convert to GMT
363 $next_scheduled_backup_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup);
364 // Convert to blog time zone
365 $next_scheduled_backup = get_date_from_gmt($next_scheduled_backup_gmt, 'D, F j, Y H:i');
366 // $next_scheduled_backup = date_i18n('D, F j, Y H:i', $next_scheduled_backup);
367 } else {
368 $next_scheduled_backup = __('Nothing currently scheduled', 'updraftplus');
369 $files_not_scheduled = true;
370 }
371
372 $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
373 if (UpdraftPlus_Options::get_updraft_option('updraft_interval_database', UpdraftPlus_Options::get_updraft_option('updraft_interval')) == UpdraftPlus_Options::get_updraft_option('updraft_interval')) {
374 if (isset($files_not_scheduled)) {
375 $next_scheduled_backup_database = $next_scheduled_backup;
376 $database_not_scheduled = true;
377 } else {
378 $next_scheduled_backup_database = __("At the same time as the files backup", 'updraftplus');
379 $next_scheduled_backup_database_same_time = true;
380 }
381 } else {
382 if ($next_scheduled_backup_database) {
383 // Convert to GMT
384 $next_scheduled_backup_database_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup_database);
385 // Convert to blog time zone
386 $next_scheduled_backup_database = get_date_from_gmt($next_scheduled_backup_database_gmt, 'D, F j, Y H:i');
387 // $next_scheduled_backup_database = date_i18n('D, F j, Y H:i', $next_scheduled_backup_database);
388 } else {
389 $next_scheduled_backup_database = __('Nothing currently scheduled', 'updraftplus');
390 $database_not_scheduled = true;
391 }
392 }
393
394 if (isset($files_not_scheduled) && isset($database_not_scheduled)) {
395 ?>
396 <span class="not-scheduled"><?php _e('Nothing currently scheduled', 'updraftplus'); ?></span>
397 <?php
398 } else {
399 echo empty($next_scheduled_backup_database_same_time) ? __('Files', 'updraftplus') : __('Files and database', 'updraftplus');
400 ?>
401 :
402 <span class="updraft_all-files">
403 <?php
404 echo $next_scheduled_backup;
405 ?>
406 </span>
407 <?php
408 if (empty($next_scheduled_backup_database_same_time)) {
409 _e('Database', 'updraftplus');
410 ?>
411 :
412 <span class="updraft_all-files">
413 <?php
414 echo $next_scheduled_backup_database;
415 ?>
416 </span>
417 <?php
418 }
419 }
420
421 }
422
423 /**
424 * Used to output the information for the next scheduled file backup.
425 * moved to function for the ajax saves
426 *
427 * @param Boolean $return_instead_of_echo Whether to return or echo the results. N.B. More than just the results to echo will be returned
428 * @return Void|String If $return_instead_of_echo parameter is true, It returns html string
429 */
430 public function next_scheduled_files_backups_output($return_instead_of_echo = false) {
431 if ($return_instead_of_echo) ob_start();
432 // UNIX timestamp
433 $next_scheduled_backup = wp_next_scheduled('updraft_backup');
434 if ($next_scheduled_backup) {
435 // Convert to blog time zone. wp_date() (WP 5.3+) also performs locale translation.
436 $next_scheduled_backup = function_exists('wp_date') ? wp_date('D, F j, Y H:i', $next_scheduled_backup) : get_date_from_gmt(gmdate('Y-m-d H:i:s', $next_scheduled_backup), 'D, F j, Y H:i');
437 $files_not_scheduled = false;
438 } else {
439 $next_scheduled_backup = __('Nothing currently scheduled', 'updraftplus');
440 $files_not_scheduled = true;
441 }
442
443 if ($files_not_scheduled) {
444 echo '<span>'.$next_scheduled_backup.'</span>';
445 } else {
446 echo '<span class="updraft_next_scheduled_date_time">'.$next_scheduled_backup.'</span>';
447 }
448
449 if ($return_instead_of_echo) return ob_get_clean();
450 }
451
452 /**
453 * Used to output the information for the next scheduled database backup.
454 * moved to function for the ajax saves
455 *
456 * @param Boolean $return_instead_of_echo Whether to return or echo the results. N.B. More than just the results to echo will be returned
457 * @return Void|String If $return_instead_of_echo parameter is true, It returns html string
458 */
459 public function next_scheduled_database_backups_output($return_instead_of_echo = false) {
460 if ($return_instead_of_echo) ob_start();
461
462 $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
463 if ($next_scheduled_backup_database) {
464 // Convert to GMT
465 $next_scheduled_backup_database_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup_database);
466 // Convert to blog time zone. wp_date() (WP 5.3+) also performs locale translation.
467 $next_scheduled_backup_database = function_exists('wp_date') ? wp_date('D, F j, Y H:i', $next_scheduled_backup_database) : get_date_from_gmt($next_scheduled_backup_database_gmt, 'D, F j, Y H:i');
468 $database_not_scheduled = false;
469 } else {
470 $next_scheduled_backup_database = __('Nothing currently scheduled', 'updraftplus');
471 $database_not_scheduled = true;
472 }
473
474 if ($database_not_scheduled) {
475 echo '<span>'.$next_scheduled_backup_database.'</span>';
476 } else {
477 echo '<span class="updraft_next_scheduled_date_time">'.$next_scheduled_backup_database.'</span>';
478 }
479
480 if ($return_instead_of_echo) return ob_get_clean();
481 }
482
483 /**
484 * Run upon the WP admin_init action
485 */
486 private function admin_init() {
487
488 add_action('admin_init', array($this, 'maybe_download_backup_from_email'));
489
490 add_action('core_upgrade_preamble', array($this, 'core_upgrade_preamble'));
491 add_action('admin_action_upgrade-plugin', array($this, 'admin_action_upgrade_pluginortheme'));
492 add_action('admin_action_upgrade-theme', array($this, 'admin_action_upgrade_pluginortheme'));
493
494 add_action('admin_head', array($this, 'admin_head'));
495 add_filter((is_multisite() ? 'network_admin_' : '').'plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
496 add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
497 add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler'));
498 add_action('wp_ajax_updraft_ajaxrestore', array($this, 'updraft_ajaxrestore'));
499 add_action('wp_ajax_nopriv_updraft_ajaxrestore', array($this, 'updraft_ajaxrestore'));
500 add_action('wp_ajax_updraft_ajaxrestore_continue', array($this, 'updraft_ajaxrestore'));
501 add_action('wp_ajax_nopriv_updraft_ajaxrestore_continue', array($this, 'updraft_ajaxrestore'));
502
503 add_action('wp_ajax_plupload_action', array($this, 'plupload_action'));
504 add_action('wp_ajax_plupload_action2', array($this, 'plupload_action2'));
505
506 add_action('wp_before_admin_bar_render', array($this, 'wp_before_admin_bar_render'));
507
508 // Add a new Ajax action for saving settings
509 add_action('wp_ajax_updraft_savesettings', array($this, 'updraft_ajax_savesettings'));
510
511 // Ajax for settings import and export
512 add_action('wp_ajax_updraft_importsettings', array($this, 'updraft_ajax_importsettings'));
513
514 add_filter('heartbeat_received', array($this, 'process_status_in_heartbeat'), 10, 2);
515
516 // UpdraftPlus templates
517 $this->register_template_directories();
518
519 global $updraftplus, $pagenow;
520 add_filter('updraftplus_dirlist_others', array($updraftplus, 'backup_others_dirlist'));
521 add_filter('updraftplus_dirlist_uploads', array($updraftplus, 'backup_uploads_dirlist'));
522
523 // First, the checks that are on all (admin) pages:
524
525 $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
526
527 if (UpdraftPlus_Options::user_can_manage()) {
528
529 $this->print_restore_in_progress_box_if_needed();
530
531 // Main dashboard page advert
532 // Since our nonce is printed, make sure they have sufficient credentials
533 if ('index.php' == $pagenow && current_user_can('update_plugins') && (!file_exists(UPDRAFTPLUS_DIR.'/udaddons') || (defined('UPDRAFTPLUS_FORCE_DASHNOTICE') && UPDRAFTPLUS_FORCE_DASHNOTICE))) {
534
535 $dismissed_until = UpdraftPlus_Options::get_updraft_option('updraftplus_dismisseddashnotice', 0);
536
537 $backup_dir = $updraftplus->backups_dir_location();
538 // N.B. Not an exact proxy for the installed time; they may have tweaked the expert option to move the directory
539 $installed = @filemtime($backup_dir.'/index.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
540 $installed_for = time() - $installed;
541
542 if (($installed && time() > $dismissed_until && $installed_for > 28*86400 && !defined('UPDRAFTPLUS_NOADS_B')) || (defined('UPDRAFTPLUS_FORCE_DASHNOTICE') && UPDRAFTPLUS_FORCE_DASHNOTICE)) {
543 add_action('all_admin_notices', array($this, 'show_admin_notice_upgradead'));
544 }
545 }
546
547 // Moved out for use with Ajax saving
548 $this->setup_all_admin_notices_global($service);
549 }
550
551 if (!class_exists('Updraft_Dashboard_News')) include_once(UPDRAFTPLUS_DIR.'/includes/class-updraft-dashboard-news.php');
552
553 $news_translations = array(
554 'product_title' => 'UpdraftPlus',
555 'item_prefix' => __('UpdraftPlus', 'updraftplus'),
556 'item_description' => __('UpdraftPlus News', 'updraftplus'),
557 'dismiss_tooltip' => __('Dismiss all UpdraftPlus news', 'updraftplus'),
558 'dismiss_confirm' => __('Are you sure you want to dismiss all UpdraftPlus news forever?', 'updraftplus'),
559 );
560
561 add_filter('woocommerce_in_plugin_update_message', array($this, 'woocommerce_in_plugin_update_message'));
562
563 new Updraft_Dashboard_News('https://feeds.feedburner.com/updraftplus/', 'https://updraftplus.com/news/', $news_translations);
564
565 // New-install admin tour
566 if ((!defined('UPDRAFTPLUS_ENABLE_TOUR') || UPDRAFTPLUS_ENABLE_TOUR) && (!defined('UPDRAFTPLUS_THIS_IS_CLONE') || !UPDRAFTPLUS_THIS_IS_CLONE)) {
567 include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-tour.php');
568 }
569
570 if ('index.php' == $GLOBALS['pagenow'] && UpdraftPlus_Options::user_can_manage()) {
571 add_action('admin_print_footer_scripts', array($this, 'admin_index_print_footer_scripts'));
572 }
573
574 $udp_saved_version = UpdraftPlus_Options::get_updraft_option('updraftplus_version');
575 if (!$udp_saved_version || $udp_saved_version != $updraftplus->version) {
576 if (!$udp_saved_version) {
577 // udp was newly installed, or upgraded from an older version
578 do_action('updraftplus_newly_installed', $updraftplus->version);
579 } else {
580 // udp was updated or downgraded
581 do_action('updraftplus_version_changed', $udp_saved_version, $updraftplus->version);
582 }
583 UpdraftPlus_Options::update_updraft_option('updraftplus_version', $updraftplus->version);
584 }
585
586 // Next, the actions that only come on the UpdraftPlus page
587 if (UpdraftPlus_Options::admin_page() != $pagenow || empty($_REQUEST['page']) || 'updraftplus' != $_REQUEST['page']) return;
588 $this->setup_all_admin_notices_udonly($service);
589
590 global $updraftplus_checkout_embed;
591 if (!class_exists('Updraft_Checkout_Embed')) include_once UPDRAFTPLUS_DIR.'/includes/checkout-embed/class-udp-checkout-embed.php';
592
593 // Create an empty list (usefull for testing, thanks to the filter bellow)
594 $checkout_embed_products = array();
595
596 // get products from JSON file.
597 $checkout_embed_product_file = UPDRAFTPLUS_DIR.'/includes/checkout-embed/products.json';
598 if (file_exists($checkout_embed_product_file)) {
599 $checkout_embed_products = json_decode(file_get_contents($checkout_embed_product_file));
600 }
601
602 $checkout_embed_products = apply_filters('updraftplus_checkout_embed_products', $checkout_embed_products);
603
604 if (!empty($checkout_embed_products)) {
605 $updraftplus_checkout_embed = new Updraft_Checkout_Embed(
606 'updraftplus', // plugin name
607 UpdraftPlus_Options::admin_page_url().'?page=updraftplus', // return url
608 $checkout_embed_products, // products list
609 UPDRAFTPLUS_URL.'/includes' // base_url
610 );
611 }
612
613 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'), 99999);
614
615 if (isset($_POST['action']) && 'updraft_wipesettings' == $_POST['action'] && isset($_POST['nonce']) && UpdraftPlus_Options::user_can_manage()) {
616 if (wp_verify_nonce($_POST['nonce'], 'updraftplus-wipe-setting-nonce')) $this->wipe_settings();
617 }
618 }
619
620 /**
621 * Runs upon the WP action woocommerce_in_plugin_update_message
622 *
623 * @param String $msg - the message that WooCommerce will print
624 *
625 * @return String - filtered value
626 */
627 public function woocommerce_in_plugin_update_message($msg) {
628 if (time() < UpdraftPlus_Options::get_updraft_option('dismissed_clone_wc_notices_until', 0)) return $msg;
629 return '<div class="updraft-ad-container"><br><strong>'.__('You can test upgrading your site on an instant copy using UpdraftClone credits', 'updraftplus').' - <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&amp;tab=migrate#updraft-navtab-migrate-content">'.__('go here to learn more', 'updraftplus').'</a></strong><a href="#" onclick="jQuery(\'.updraft-ad-container\').slideUp(); jQuery.post(ajaxurl, {action: \'updraft_ajax\', subaction: \'dismiss_clone_wc_notice\', nonce: \''. wp_create_nonce('updraftplus-credentialtest-nonce') .'\' });return false;"> - '. __('dismiss notice', 'updraftplus') .'</a></div>'.$msg;
630 }
631
632 /**
633 * Runs upon the WP action admin_print_footer_scripts if an entitled user is on the main WP dashboard page
634 */
635 public function admin_index_print_footer_scripts() {
636 if (time() < UpdraftPlus_Options::get_updraft_option('dismissed_clone_php_notices_until', 0)) return;
637 ?>
638 <script>
639 jQuery(function($) {
640 if ($('#dashboard-widgets #dashboard_php_nag').length < 1) return;
641 $('#dashboard-widgets #dashboard_php_nag .button-container').before('<div class="updraft-ad-container"><a href="<?php echo UpdraftPlus_Options::admin_page_url(); ?>?page=updraftplus&amp;tab=migrate#updraft-navtab-migrate-content"><?php echo esc_js(__('You can test running your site on a different PHP (or WordPress) version using UpdraftClone credits.', 'updraftplus')); ?></a> (<a href="#" onclick="jQuery(\'.updraft-ad-container\').slideUp(); jQuery.post(ajaxurl, {action: \'updraft_ajax\', subaction: \'dismiss_clone_php_notice\', nonce: \'<?php echo wp_create_nonce('updraftplus-credentialtest-nonce'); ?>\' });return false;"><?php echo esc_js(__('Dismiss notice', 'updraftplus')); ?></a>)</div>');
642 });
643 </script>
644 <?php
645 }
646
647 /**
648 * Sets up what is needed to allow an in-page backup to be run. Will enqueue scripts and output appropriate HTML (so, should be run when at a suitable place). Not intended for use on the UpdraftPlus settings page.
649 *
650 * @param string $title Text to use for the title of the modal
651 * @param callable $callback Callable function to output the contents of the updraft_inpage_prebackup element - i.e. what shows in the modal before a backup begins.
652 */
653 public function add_backup_scaffolding($title, $callback) {
654 $this->admin_enqueue_scripts();
655 ?>
656 <script>
657 // TODO: This is not the best way.
658 var updraft_credentialtest_nonce='<?php echo wp_create_nonce('updraftplus-credentialtest-nonce');?>';
659 </script>
660 <div id="updraft-poplog" >
661 <pre id="updraft-poplog-content" style="white-space: pre-wrap;"></pre>
662 </div>
663
664 <div id="updraft-backupnow-inpage-modal" title="UpdraftPlus - <?php echo $title; ?>">
665
666 <div id="updraft_inpage_prebackup" style="float:left; clear:both;">
667 <?php call_user_func($callback); ?>
668 </div>
669
670 <div id="updraft_inpage_backup">
671
672 <h2><?php echo $title;?></h2>
673
674 <div id="updraft_backup_started" class="updated" style="display:none; max-width: 560px; font-size:100%; line-height: 100%; padding:6px; clear:left;"></div>
675
676 <?php $this->render_active_jobs_and_log_table(true, false); ?>
677
678 </div>
679
680 </div>
681 <?php
682 }
683
684 /**
685 * Called via the ajax_restore actions to prepare the restore over AJAX
686 *
687 * @return void
688 */
689 public function updraft_ajaxrestore() {
690 $this->prepare_restore();
691 die();
692 }
693
694 /**
695 * Runs upon the WP action wp_before_admin_bar_render
696 */
697 public function wp_before_admin_bar_render() {
698 global $wp_admin_bar;
699
700 if (!UpdraftPlus_Options::user_can_manage()) return;
701
702 if (defined('UPDRAFTPLUS_ADMINBAR_DISABLE') && UPDRAFTPLUS_ADMINBAR_DISABLE) return;
703
704 if (false == apply_filters('updraftplus_settings_page_render', true)) return;
705
706 $option_location = UpdraftPlus_Options::admin_page_url();
707
708 $args = array(
709 'id' => 'updraft_admin_node',
710 'title' => apply_filters('updraftplus_admin_node_title', 'UpdraftPlus')
711 );
712 $wp_admin_bar->add_node($args);
713
714 $args = array(
715 'id' => 'updraft_admin_node_status',
716 'title' => str_ireplace('Back Up', 'Backup', __('Backup', 'updraftplus')).' / '.__('Restore', 'updraftplus'),
717 'parent' => 'updraft_admin_node',
718 'href' => $option_location.'?page=updraftplus&tab=backups'
719 );
720 $wp_admin_bar->add_node($args);
721
722 $args = array(
723 'id' => 'updraft_admin_node_migrate',
724 'title' => __('Migrate / Clone', 'updraftplus'),
725 'parent' => 'updraft_admin_node',
726 'href' => $option_location.'?page=updraftplus&tab=migrate'
727 );
728 $wp_admin_bar->add_node($args);
729
730 $args = array(
731 'id' => 'updraft_admin_node_settings',
732 'title' => __('Settings', 'updraftplus'),
733 'parent' => 'updraft_admin_node',
734 'href' => $option_location.'?page=updraftplus&tab=settings'
735 );
736 $wp_admin_bar->add_node($args);
737
738 $args = array(
739 'id' => 'updraft_admin_node_expert_content',
740 'title' => __('Advanced Tools', 'updraftplus'),
741 'parent' => 'updraft_admin_node',
742 'href' => $option_location.'?page=updraftplus&tab=expert'
743 );
744 $wp_admin_bar->add_node($args);
745
746 $args = array(
747 'id' => 'updraft_admin_node_addons',
748 'title' => __('Extensions', 'updraftplus'),
749 'parent' => 'updraft_admin_node',
750 'href' => $option_location.'?page=updraftplus&tab=addons'
751 );
752 $wp_admin_bar->add_node($args);
753
754 global $updraftplus;
755 if (!$updraftplus->have_addons) {
756 $args = array(
757 'id' => 'updraft_admin_node_premium',
758 'title' => 'UpdraftPlus Premium',
759 'parent' => 'updraft_admin_node',
760 'href' => $updraftplus->get_url('premium')
761 );
762 $wp_admin_bar->add_node($args);
763 }
764 }
765
766 /**
767 * Output HTML for a dashboard notice highlighting the benefits of upgrading to Premium
768 */
769 public function show_admin_notice_upgradead() {
770 $this->include_template('wp-admin/notices/thanks-for-using-main-dash.php');
771 }
772
773 /**
774 * Enqueue sufficient versions of jQuery and our own scripts
775 */
776 private function ensure_sufficient_jquery_and_enqueue() {
777 global $updraftplus;
778
779 $enqueue_version = $updraftplus->use_unminified_scripts() ? $updraftplus->version.'.'.time() : $updraftplus->version;
780 $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min';
781 $updraft_min_or_not = $updraftplus->get_updraftplus_file_version();
782
783
784 if (version_compare($updraftplus->get_wordpress_version(), '3.3', '<')) {
785 // Require a newer jQuery (3.2.1 has 1.6.1, so we go for something not too much newer). We use .on() in a way that is incompatible with < 1.7
786 wp_deregister_script('jquery');
787 $jquery_enqueue_version = $updraftplus->use_unminified_scripts() ? '1.7.2'.'.'.time() : '1.7.2';
788 wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery'.$min_or_not.'.js', false, $jquery_enqueue_version, false);
789 wp_enqueue_script('jquery');
790 // No plupload until 3.3
791 wp_enqueue_script('updraft-admin-common', UPDRAFTPLUS_URL.'/includes/updraft-admin-common'.$updraft_min_or_not.'.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-core', 'jquery-ui-accordion'), $enqueue_version, true);
792 } else {
793 wp_enqueue_script('updraft-admin-common', UPDRAFTPLUS_URL.'/includes/updraft-admin-common'.$updraft_min_or_not.'.js', array('jquery', 'jquery-ui-dialog', 'jquery-ui-core', 'jquery-ui-accordion', 'plupload-all'), $enqueue_version);
794 }
795
796 }
797
798 /**
799 * This is also called directly from the auto-backup add-on
800 */
801 public function admin_enqueue_scripts() {
802
803 global $updraftplus, $wp_locale, $updraftplus_checkout_embed;
804
805 $enqueue_version = $updraftplus->use_unminified_scripts() ? $updraftplus->version.'.'.time() : $updraftplus->version;
806 $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min';
807 $updraft_min_or_not = $updraftplus->get_updraftplus_file_version();
808
809 // Defeat other plugins/themes which dump their jQuery UI CSS onto our settings page
810 wp_deregister_style('jquery-ui');
811 $jquery_ui_version = version_compare($updraftplus->get_wordpress_version(), '5.6', '>=') ? '1.12.1' : '1.11.4';
812 $jquery_ui_css_enqueue_version = $updraftplus->use_unminified_scripts() ? $jquery_ui_version.'.0'.'.'.time() : $jquery_ui_version.'.0';
813 wp_enqueue_style('jquery-ui', UPDRAFTPLUS_URL."/includes/jquery-ui.custom-v$jquery_ui_version$updraft_min_or_not.css", array(), $jquery_ui_css_enqueue_version);
814
815 wp_enqueue_style('updraft-admin-css', UPDRAFTPLUS_URL.'/css/updraftplus-admin'.$updraft_min_or_not.'.css', array(), $enqueue_version);
816 // add_filter('style_loader_tag', array($this, 'style_loader_tag'), 10, 2);
817
818 $this->ensure_sufficient_jquery_and_enqueue();
819 $jquery_blockui_enqueue_version = $updraftplus->use_unminified_scripts() ? '2.71.0'.'.'.time() : '2.71.0';
820 wp_enqueue_script('jquery-blockui', UPDRAFTPLUS_URL.'/includes/blockui/jquery.blockUI'.$min_or_not.'.js', array('jquery'), $jquery_blockui_enqueue_version);
821
822 wp_enqueue_script('jquery-labelauty', UPDRAFTPLUS_URL.'/includes/labelauty/jquery-labelauty'.$updraft_min_or_not.'.js', array('jquery'), $enqueue_version);
823 wp_enqueue_style('jquery-labelauty', UPDRAFTPLUS_URL.'/includes/labelauty/jquery-labelauty'.$updraft_min_or_not.'.css', array(), $enqueue_version);
824 $serialize_js_enqueue_version = $updraftplus->use_unminified_scripts() ? '3.2.0'.'.'.time() : '3.2.0';
825 wp_enqueue_script('jquery.serializeJSON', UPDRAFTPLUS_URL.'/includes/jquery.serializeJSON/jquery.serializejson'.$min_or_not.'.js', array('jquery'), $serialize_js_enqueue_version);
826 wp_enqueue_script('handlebars', UPDRAFTPLUS_URL.'/includes/handlebars/handlebars'.$min_or_not.'.js', array(), $enqueue_version);
827 $this->enqueue_jstree();
828
829 $jqueryui_dialog_extended_version = $updraftplus->use_unminified_scripts() ? '1.0.4'.'.'.time() : '1.0.4';
830 wp_enqueue_script('jquery-ui.dialog.extended', UPDRAFTPLUS_URL.'/includes/jquery-ui.dialog.extended/jquery-ui.dialog.extended'.$updraft_min_or_not.'.js', array('jquery', 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-dialog'), $jqueryui_dialog_extended_version);
831
832 do_action('updraftplus_admin_enqueue_scripts');
833
834 $day_selector = '';
835 for ($day_index = 0; $day_index <= 6; $day_index++) {
836 // $selected = ($opt == $day_index) ? 'selected="selected"' : '';
837 $selected = '';
838 $day_selector .= "\n\t<option value='" . $day_index . "' $selected>" . $wp_locale->get_weekday($day_index) . '</option>';
839 }
840
841 $mday_selector = '';
842 for ($mday_index = 1; $mday_index <= 28; $mday_index++) {
843 // $selected = ($opt == $mday_index) ? 'selected="selected"' : '';
844 $selected = '';
845 $mday_selector .= "\n\t<option value='" . $mday_index . "' $selected>" . $mday_index . '</option>';
846 }
847 $backup_methods = $updraftplus->backup_methods;
848 $remote_storage_options_and_templates = UpdraftPlus_Storage_Methods_Interface::get_remote_storage_options_and_templates();
849 $main_tabs = $this->get_main_tabs_array();
850
851 $checkout_embed_5gb_trial_attribute = '';
852
853 if (is_a($updraftplus_checkout_embed, 'Updraft_Checkout_Embed')) {
854 $checkout_embed_5gb_trial_attribute = $updraftplus_checkout_embed->get_product('updraftplus-vault-storage-5-gb') ? 'data-embed-checkout="'.apply_filters('updraftplus_com_link', $updraftplus_checkout_embed->get_product('updraftplus-vault-storage-5-gb', UpdraftPlus_Options::admin_page_url().'?page=updraftplus&tab=settings')).'"' : '';
855 }
856
857 $hosting_company = $updraftplus->get_hosting_info();
858
859 wp_localize_script('updraft-admin-common', 'updraftlion', array(
860 'tab' => empty($_GET['tab']) ? 'backups' : $_GET['tab'],
861 'sendonlyonwarnings' => __('Send a report only when there are warnings/errors', 'updraftplus'),
862 'wholebackup' => __('When email storage method is enabled, and an email address is entered, also send the backup', 'updraftplus'),
863 'emailsizelimits' => esc_attr(sprintf(__('Be aware that mail servers tend to have size limits; typically around %s Mb; backups larger than any limits will likely not arrive.', 'updraftplus'), '10-20')),
864 'rescanning' => __('Rescanning (looking for backups that you have uploaded manually into the internal backup store)...', 'updraftplus'),
865 'dbbackup' => __('Only email the database backup', 'updraftplus'),
866 'rescanningremote' => __('Rescanning remote and local storage for backup sets...', 'updraftplus'),
867 'enteremailhere' => esc_attr(__('To send to more than one address, separate each address with a comma.', 'updraftplus')),
868 'excludedeverything' => __('If you exclude both the database and the files, then you have excluded everything!', 'updraftplus'),
869 'nofileschosen' => __('You have chosen to backup files, but no file entities have been selected', 'updraftplus'),
870 'notableschosen' => __('You have chosen to backup a database, but no tables have been selected', 'updraftplus'),
871 'nocloudserviceschosen' => __('You have chosen to send this backup to remote storage, but no remote storage locations have been selected', 'updraftplus'),
872 'restore_proceeding' => __('The restore operation has begun. Do not close your browser until it reports itself as having finished.', 'updraftplus'),
873 'unexpectedresponse' => __('Unexpected response:', 'updraftplus'),
874 'servererrorcode' => __('The web server returned an error code (try again, or check your web server logs)', 'updraftplus'),
875 'newuserpass' => __("The new user's RackSpace console password is (this will not be shown again):", 'updraftplus'),
876 'trying' => __('Trying...', 'updraftplus'),
877 'fetching' => __('Fetching...', 'updraftplus'),
878 'calculating' => __('calculating...', 'updraftplus'),
879 'begunlooking' => __('Begun looking for this entity', 'updraftplus'),
880 'stilldownloading' => __('Some files are still downloading or being processed - please wait.', 'updraftplus'),
881 'processing' => __('Processing files - please wait...', 'updraftplus'),
882 'emptyresponse' => __('Error: the server sent an empty response.', 'updraftplus'),
883 'warnings' => __('Warnings:', 'updraftplus'),
884 'errors' => __('Errors:', 'updraftplus'),
885 'jsonnotunderstood' => __('Error: the server sent us a response which we did not understand.', 'updraftplus'),
886 'errordata' => __('Error data:', 'updraftplus'),
887 'error' => __('Error:', 'updraftplus'),
888 'errornocolon' => __('Error', 'updraftplus'),
889 'existing_backups' => __('Existing backups', 'updraftplus'),
890 'fileready' => __('File ready.', 'updraftplus'),
891 'actions' => __('Actions', 'updraftplus'),
892 'deletefromserver' => __('Delete from your web server', 'updraftplus'),
893 'downloadtocomputer' => __('Download to your computer', 'updraftplus'),
894 'browse_contents' => __('Browse contents', 'updraftplus'),
895 'notunderstood' => __('Download error: the server sent us a response which we did not understand.', 'updraftplus'),
896 'requeststart' => __('Requesting start of backup...', 'updraftplus'),
897 'phpinfo' => __('PHP information', 'updraftplus'),
898 'delete_old_dirs' => __('Delete Old Directories', 'updraftplus'),
899 'raw' => __('Raw backup history', 'updraftplus'),
900 'notarchive' => __('This file does not appear to be an UpdraftPlus backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'updraftplus').' '.__('However, UpdraftPlus archives are standard zip/SQL files - so if you are sure that your file has the right format, then you can rename it to match that pattern.', 'updraftplus'),
901 'notarchive2' => '<p>'.__('This file does not appear to be an UpdraftPlus backup archive (such files are .zip or .gz files which have a name like: backup_(time)_(site name)_(code)_(type).(zip|gz)).', 'updraftplus').'</p> '.apply_filters('updraftplus_if_foreign_then_premium_message', '<p><a href="'.$updraftplus->get_url('premium').'">'.__('If this is a backup created by a different backup plugin, then UpdraftPlus Premium may be able to help you.', 'updraftplus').'</a></p>'),
902 'makesure' => __('(make sure that you were trying to upload a zip file previously created by UpdraftPlus)', 'updraftplus'),
903 'uploaderror' => __('Upload error:', 'updraftplus'),
904 'notdba' => __('This file does not appear to be an UpdraftPlus encrypted database archive (such files are .gz.crypt files which have a name like: backup_(time)_(site name)_(code)_db.crypt.gz).', 'updraftplus'),
905 'uploaderr' => __('Upload error', 'updraftplus'),
906 'followlink' => __('Follow this link to attempt decryption and download the database file to your computer.', 'updraftplus'),
907 'thiskey' => __('This decryption key will be attempted:', 'updraftplus'),
908 'unknownresp' => __('Unknown server response:', 'updraftplus'),
909 'ukrespstatus' => __('Unknown server response status:', 'updraftplus'),
910 'uploaded' => __('The file was uploaded.', 'updraftplus'),
911 // One of the translators has erroneously changed "Backup" into "Back up" (which means, "reverse" !)
912 'backupnow' => str_ireplace('Back Up', 'Backup', __('Backup Now', 'updraftplus')),
913 'cancel' => __('Cancel', 'updraftplus'),
914 'deletebutton' => __('Delete', 'updraftplus'),
915 'createbutton' => __('Create', 'updraftplus'),
916 'uploadbutton' => __('Upload', 'updraftplus'),
917 'youdidnotselectany' => __('You did not select any components to restore. Please select at least one, and then try again.', 'updraftplus'),
918 'restoreactivitylogfullscreen' => __('Full-screen', 'updraftplus'),
919 'restoreactivitylogscreenexit' => __('Exit full-screen', 'updraftplus'),
920 'proceedwithupdate' => __('Proceed with update', 'updraftplus'),
921 'close' => __('Close', 'updraftplus'),
922 'restore' => __('Restore', 'updraftplus'),
923 'downloadlogfile' => __('Download log file', 'updraftplus'),
924 'automaticbackupbeforeupdate' => __('Automatic backup before update', 'updraftplus'),
925 'unsavedsettings' => __('You have made changes to your settings, and not saved.', 'updraftplus'),
926 'saving' => __('Saving...', 'updraftplus'),
927 'connect' => __('Connect', 'updraftplus'),
928 'connecting' => __('Connecting...', 'updraftplus'),
929 'disconnect' => __('Disconnect', 'updraftplus'),
930 'disconnecting' => __('Disconnecting...', 'updraftplus'),
931 'counting' => __('Counting...', 'updraftplus'),
932 'updatequotacount' => __('Update quota count', 'updraftplus'),
933 'addingsite' => __('Adding...', 'updraftplus'),
934 'addsite' => __('Add site', 'updraftplus'),
935 // 'resetting' => __('Resetting...', 'updraftplus'),
936 'creating_please_allow' => __('Creating...', 'updraftplus').(function_exists('openssl_encrypt') ? '' : ' ('.__('your PHP install lacks the openssl module; as a result, this can take minutes; if nothing has happened by then, then you should either try a smaller key size, or ask your web hosting company how to enable this PHP module on your setup.', 'updraftplus').')'),
937 'sendtosite' => __('Send to site:', 'updraftplus'),
938 'checkrpcsetup' => sprintf(__('You should check that the remote site is online, not firewalled, does not have security modules that may be blocking access, has UpdraftPlus version %s or later active and that the keys have been entered correctly.', 'updraftplus'), '2.10.3'),
939 'pleasenamekey' => __('Please give this key a name (e.g. indicate the site it is for):', 'updraftplus'),
940 'key' => __('Key', 'updraftplus'),
941 'nokeynamegiven' => sprintf(__("Failure: No %s was given.", 'updraftplus'), __('key name', 'updraftplus')),
942 'deleting' => __('Deleting...', 'updraftplus'),
943 'enter_mothership_url' => __('Please enter a valid URL', 'updraftplus'),
944 'delete_response_not_understood' => __("We requested to delete the file, but could not understand the server's response", 'updraftplus'),
945 'testingconnection' => __('Testing connection...', 'updraftplus'),
946 'send' => __('Send', 'updraftplus'),
947 'migratemodalheight' => class_exists('UpdraftPlus_Addons_Migrator') ? 555 : 300,
948 'migratemodalwidth' => class_exists('UpdraftPlus_Addons_Migrator') ? 770 : 500,
949 'download' => _x('Download', '(verb)', 'updraftplus'),
950 'browse_download_link' => apply_filters('updraftplus_browse_download_link', '<a id="updraft_zip_download_notice" href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/landing/updraftplus-premium").'" target="_blank">'.__("With UpdraftPlus Premium, you can directly download individual files from here.", "updraftplus").'</a>'),
951 'unsavedsettingsbackup' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('You should save your changes to ensure that they are used for making your backup.', 'updraftplus'),
952 'unsaved_settings_export' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('Your export file will be of your displayed settings, not your saved ones.', 'updraftplus'),
953 'dayselector' => $day_selector,
954 'mdayselector' => $mday_selector,
955 'day' => __('day', 'updraftplus'),
956 'inthemonth' => __('in the month', 'updraftplus'),
957 'days' => __('day(s)', 'updraftplus'),
958 'hours' => __('hour(s)', 'updraftplus'),
959 'weeks' => __('week(s)', 'updraftplus'),
960 'forbackupsolderthan' => __('For backups older than', 'updraftplus'),
961 'ud_url' => UPDRAFTPLUS_URL,
962 'processing' => __('Processing...', 'updraftplus'),
963 'loading' => __('Loading...', 'updraftplus'),
964 'pleasefillinrequired' => __('Please fill in the required information.', 'updraftplus'),
965 'test_settings' => __('Test %s Settings', 'updraftplus'),
966 'testing_settings' => __('Testing %s Settings...', 'updraftplus'),
967 'settings_test_result' => __('%s settings test result:', 'updraftplus'),
968 'nothing_yet_logged' => __('Nothing yet logged', 'updraftplus'),
969 'import_select_file' => __('You have not yet selected a file to import.', 'updraftplus'),
970 'import_invalid_json_file' => __('Error: The chosen file is corrupt. Please choose a valid UpdraftPlus export file.', 'updraftplus'),
971 'updraft_settings_url' => UpdraftPlus_Options::admin_page_url().'?page=updraftplus',
972 'network_site_url' => network_site_url(),
973 'importing' => __('Importing...', 'updraftplus'),
974 'importing_data_from' => __('This will import data from:', 'updraftplus'),
975 'exported_on' => __('Which was exported on:', 'updraftplus'),
976 'continue_import' => __('Do you want to carry out the import?', 'updraftplus'),
977 'complete' => __('Complete', 'updraftplus'),
978 'backup_complete' => __('The backup has finished running', 'updraftplus'),
979 'backup_aborted' => __('The backup was aborted', 'updraftplus'),
980 'remote_delete_limit' => defined('UPDRAFTPLUS_REMOTE_DELETE_LIMIT') ? UPDRAFTPLUS_REMOTE_DELETE_LIMIT : 15,
981 'remote_files_deleted' => __('remote files deleted', 'updraftplus'),
982 'http_code' => __('HTTP code:', 'updraftplus'),
983 'makesure2' => __('The file failed to upload. Please check the following:', 'updraftplus')."\n\n - ".__('Any settings in your .htaccess or web.config file that affects the maximum upload or post size.', 'updraftplus')."\n - ".__('The available memory on the server.', 'updraftplus')."\n - ".__('That you are attempting to upload a zip file previously created by UpdraftPlus.', 'updraftplus')."\n\n".__('Further information may be found in the browser JavaScript console, and the server PHP error logs.', 'updraftplus'),
984 'zip_file_contents' => __('Browsing zip file', 'updraftplus'),
985 'zip_file_contents_info' => __('Select a file to view information about it', 'updraftplus'),
986 'search' => __('Search', 'updraftplus'),
987 'download_timeout' => __('Unable to download file. This could be caused by a timeout. It would be best to download the zip to your computer.', 'updraftplus'),
988 'loading_log_file' => __('Loading log file', 'updraftplus'),
989 'updraftplus_version' => $updraftplus->version,
990 'updraftcentral_wizard_empty_url' => __('Please enter the URL where your UpdraftCentral dashboard is hosted.'),
991 'updraftcentral_wizard_invalid_url' => __('Please enter a valid URL e.g http://example.com', 'updraftplus'),
992 'export_settings_file_name' => 'updraftplus-settings-'.sanitize_title(get_bloginfo('name')).'.json',
993 // For remote storage handlebarsjs template
994 'remote_storage_options' => $remote_storage_options_and_templates['options'],
995 'remote_storage_templates' => $remote_storage_options_and_templates['templates'],
996 'remote_storage_methods' => $backup_methods,
997 'instance_enabled' => __('Currently enabled', 'updraftplus'),
998 'instance_disabled' => __('Currently disabled', 'updraftplus'),
999 'local_upload_started' => __('Local backup upload has started; please check the log file to see the upload progress', 'updraftplus'),
1000 'local_upload_error' => __('You must select at least one remote storage destination to upload this backup set to.', 'updraftplus'),
1001 'already_uploaded' => __('(already uploaded)', 'updraftplus'),
1002 'onedrive_folder_url_warning' => __('Please specify the Microsoft OneDrive folder name, not the URL.', 'updraftplus'),
1003 'updraftcentral_cloud' => __('UpdraftCentral Cloud', 'updraftplus'),
1004 'udc_cloud_connected' => __('Connected. Requesting UpdraftCentral Key.', 'updraftplus'),
1005 'udc_cloud_key_created' => __('Key created. Adding site to UpdraftCentral Cloud.', 'updraftplus'),
1006 'login_successful' => __('Login successful.', 'updraftplus').' '.__('Please follow this link to open %s in a new window.', 'updraftplus'),
1007 'login_successful_short' => __('Login successful; reloading information.', 'updraftplus'),
1008 'registration_successful' => __('Registration successful.', 'updraftplus').' '.__('Please follow this link to open %s in a new window.', 'updraftplus'),
1009 'username_password_required' => __('Both email and password fields are required.', 'updraftplus'),
1010 'valid_email_required' => __('An email is required and needs to be in a valid format.', 'updraftplus'),
1011 'trouble_connecting' => __('Trouble connecting? Try using an alternative method in the advanced security options.', 'updraftplus'),
1012 'checking_tfa_code' => __('Verifying one-time password...', 'updraftplus'),
1013 'perhaps_login' => __('Perhaps you would want to login instead.', 'updraftplus'),
1014 'generating_key' => __('Please wait while the system generates and registers an encryption key for your website with UpdraftCentral Cloud.', 'updraftplus'),
1015 'updraftcentral_cloud_redirect' => __('Please wait while you are redirected to UpdraftCentral Cloud.', 'updraftplus'),
1016 'data_consent_required' => __('You need to read and accept the UpdraftCentral Cloud data and privacy policies before you can proceed.', 'updraftplus'),
1017 'close_wizard' => __('You can also close this wizard.', 'updraftplus'),
1018 'control_udc_connections' => __('For future control of all your UpdraftCentral connections, go to the "Advanced Tools" tab.', 'updraftplus'),
1019 'main_tabs_keys' => array_keys($main_tabs),
1020 'clone_version_warning' => __('Warning: you have selected a lower version than your currently installed version. This may fail if you have components that are incompatible with earlier versions.', 'updraftplus'),
1021 'clone_backup_complete' => __('The clone has been provisioned, and its data has been sent to it. Once the clone has finished deploying it, you will receive an email.', 'updraftplus'),
1022 'clone_backup_aborted' => __('The preparation of the clone data has been aborted.', 'updraftplus'),
1023 'current_clean_url' => esc_url(UpdraftPlus::get_current_clean_url()),
1024 'exclude_rule_remove_conformation_msg' => __('Are you sure you want to remove this exclusion rule?', 'updraftplus'),
1025 'exclude_select_file_or_folder_msg' => __('Please select a file/folder which you would like to exclude', 'updraftplus'),
1026 'exclude_select_folder_wildcards_msg' => __('Please select a folder in which the files/directories you would like to exclude are located'),
1027 'exclude_type_ext_msg' => __('Please enter a file extension, like zip', 'updraftplus'),
1028 'exclude_ext_error_msg' => __('Please enter a valid file extension', 'updraftplus'),
1029 'exclude_type_prefix_msg' => __('Please enter characters that begin the filename which you would like to exclude', 'updraftplus'),
1030 'exclude_prefix_error_msg' => __('Please enter a valid file name prefix', 'updraftplus'),
1031 'exclude_contain_error_msg' => __('Please enter part of the file name', 'updraftplus'),
1032 'duplicate_exclude_rule_error_msg' => __('The exclusion rule which you are trying to add already exists', 'updraftplus'),
1033 'clone_key_required' => __('UpdraftClone key is required.', 'updraftplus'),
1034 'files_new_backup' => __('Include your files in the backup', 'updraftplus'),
1035 'files_incremental_backup' => __('File backup options', 'updraftplus'),
1036 'ajax_restore_invalid_response' => __('HTML was detected in the response. You may have a security module on your webserver blocking the restoration operation.', 'updraftplus'),
1037 'emptyrestorepath' => __('You have not selected a restore path for your chosen backups', 'updraftplus'),
1038 'updraftvault_info' => '<h3>'.__('Try UpdraftVault!', 'updraftplus').'</h3>'
1039 .'<p>'.__('UpdraftVault is our remote storage which works seamlessly with UpdraftPlus.', 'updraftplus')
1040 .' <a href="'.apply_filters('updraftplus_com_link', 'https://updraftplus.com/updraftvault/').'" target="_blank">'.__('Find out more here.', 'updraftplus').'</a>'
1041 .'</p>'
1042 .'<p><a href="'.apply_filters('updraftplus_com_link', $updraftplus->get_url('shop_vault_5')).'" target="_blank" '.$checkout_embed_5gb_trial_attribute.' class="button button-primary">'.__('Try it - 1 month for $1!', 'updraftplus').'</a></p>',
1043 'login_udc_no_licences_short' => __('No UpdraftCentral licences were available. Continuing to connect to account.'),
1044 'credentials' => __('credentials', 'updraftplus'),
1045 'username' => __('Username', 'updraftplus'),
1046 'password' => __('Password', 'updraftplus'),
1047 'last_activity' => __('last activity: %d seconds ago', 'updraftplus'),
1048 'no_recent_activity' => __('no recent activity; will offer resumption after: %d seconds', 'updraftplus'),
1049 'restore_files_progress' => __('Restoring %s1 files out of %s2', 'updraftplus'),
1050 'restore_db_table_progress' => __('Restoring table: %s', 'updraftplus'),
1051 'restore_db_stored_routine_progress' => __('Restoring stored routine: %s', 'updraftplus'),
1052 'finished' => __('Finished', 'updraftplus'),
1053 'begun' => __('Begun', 'updraftplus'),
1054 'maybe_downloading_entities' => __('Downloading backup files if needed', 'updraftplus'),
1055 'preparing_backup_files' => __('Preparing backup files', 'updraftplus'),
1056 'ajax_restore_contact_failed' => __('Attempts by the browser to contact the website failed.', 'updraftplus'),
1057 'ajax_restore_error' => __('Restore error:', 'updraftplus'),
1058 'ajax_restore_404_detected' => '<div class="notice notice-warning" style="margin: 0px; padding: 5px;"><p><span class="dashicons dashicons-warning"></span> <strong>'. __('Warning:', 'updraftplus') . '</strong></p><p>' . __('Attempts by the browser to access some pages have returned a "not found (404)" error. This could mean that your .htaccess file has incorrect contents, is missing, or that your webserver is missing an equivalent mechanism.', 'updraftplus'). '</p><p>'.__('Missing pages:', 'updraftplus').'</p><ul class="updraft_missing_pages"></ul><a target="_blank" href="https://updraftplus.com/faqs/migrating-site-front-page-works-pages-give-404-error/">'.__('Follow this link for more information', 'updraftplus').'.</a></div>',
1059 'delete_error_log_prompt' => __('Please check the error log for more details', 'updraftplus'),
1060 'existing_backups_limit' => defined('UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT') ? UPDRAFTPLUS_EXISTING_BACKUPS_LIMIT : 100,
1061 'remote_scan_warning' => __('Warning: if you continue, you will add all backups stored in the configured remote storage directory (whichever site they were created by).'),
1062 'hosting_restriction_one_backup_permonth' => __("You have reached the monthly limit for the number of backups you can create at this time.", 'updraftplus').' '.__('Your hosting provider only allows you to take one backup per month.', 'updraftplus').' '.sprintf(__("Please contact your hosting company (%s) if you require further support.", 'updraftplus'), $hosting_company['name']),
1063 'hosting_restriction_one_incremental_perday' => __("You have reached the daily limit for the number of incremental backups you can create at this time.", 'updraftplus').' '.__("Your hosting provider only allows you to take one incremental backup per day.", 'updraftplus').' '.sprintf(__("Please contact your hosting company (%s) if you require further support.", 'updraftplus'), $hosting_company['name']),
1064 'hosting_restriction' => $updraftplus->is_hosting_backup_limit_reached(),
1065 'conditional_logic' => array(
1066 'day_of_the_week_options' => $updraftplus->list_days_of_the_week(),
1067 'logic_options' => array(
1068 array(
1069 'label' => __('on every backup', 'updraftplus'),
1070 'value' => '',
1071 ),
1072 array(
1073 'label' => __('if any of the following conditions are matched:', 'updraftplus'),
1074 'value' => 'any',
1075 ),
1076 array(
1077 'label' => __('if all of the following conditions are matched:', 'updraftplus'),
1078 'value' => 'all',
1079 ),
1080 ),
1081 'operand_options' => array(
1082 array(
1083 'label' => __('Day of the week', 'updraftplus'),
1084 'value' => 'day_of_the_week',
1085 ),
1086 array(
1087 'label' => __('Day of the month', 'updraftplus'),
1088 'value' => 'day_of_the_month',
1089 ),
1090 ),
1091 'operator_options' => array(
1092 array(
1093 'label' => __('is', 'updraftplus'),
1094 'value' => 'is',
1095 ),
1096 array(
1097 'label' => __('is not', 'updraftplus'),
1098 'value' => 'is_not',
1099 ),
1100 )
1101 ),
1102 'php_max_input_vars_detected_warning' => __('The number of restore options that will be sent exceeds the configured maximum in your PHP configuration (max_input_vars).', 'updraftplus').' '.__('If you proceed with the restoration then some of the restore options will be lost and you may get unexpected results. See the browser console log for more information.', 'updraftplus'),
1103 'remote_send_backup_info' => __('You can send an existing local backup to the remote site or create a new backup', 'updraftplus'),
1104 'send_to_another_site' => __('Send a backup to another site', 'updraftplus'),
1105 'send_existing_backup' => __('Send existing backup', 'updraftplus'),
1106 'send_new_backup' => __('Send a new backup', 'updraftplus'),
1107 'scanning_backups' => __('Searching for backups...', 'updraftplus'),
1108 'back' => __('back', 'updraftplus'),
1109 ));
1110 }
1111
1112 /**
1113 * Despite the name, this fires irrespective of what capabilities the user has (even none - so be careful)
1114 */
1115 public function core_upgrade_preamble() {
1116 // They need to be able to perform backups, and to perform updates
1117 if (!UpdraftPlus_Options::user_can_manage() || (!current_user_can('update_core') && !current_user_can('update_plugins') && !current_user_can('update_themes'))) return;
1118
1119 if (!class_exists('UpdraftPlus_Addon_Autobackup')) {
1120 if (defined('UPDRAFTPLUS_NOADS_B')) return;
1121 }
1122
1123 ?>
1124 <?php
1125 if (!class_exists('UpdraftPlus_Addon_Autobackup')) {
1126 if (!class_exists('UpdraftPlus_Notices')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-notices.php');
1127 global $updraftplus_notices;
1128 echo apply_filters('updraftplus_autobackup_blurb', $updraftplus_notices->do_notice('autobackup', 'autobackup', true));
1129 } else {
1130 echo apply_filters('updraftplus_autobackup_blurb', '');
1131 }
1132 ?>
1133 <script>
1134 jQuery(function() {
1135 jQuery('.updraft-ad-container').appendTo(jQuery('.wrap p').first());
1136 });
1137 </script>
1138 <?php
1139 }
1140
1141 /**
1142 * Run upon the WP admin_head action
1143 */
1144 public function admin_head() {
1145
1146 global $pagenow;
1147
1148 if (UpdraftPlus_Options::admin_page() != $pagenow || !isset($_REQUEST['page']) || 'updraftplus' != $_REQUEST['page'] || !UpdraftPlus_Options::user_can_manage()) return;
1149
1150 $chunk_size = min(wp_max_upload_size()-1024, 1048576*2);
1151
1152 // The multiple_queues argument is ignored in plupload 2.x (WP3.9+) - http://make.wordpress.org/core/2014/04/11/plupload-2-x-in-wordpress-3-9/
1153 // max_file_size is also in filters as of plupload 2.x, but in its default position is still supported for backwards-compatibility. Likewise, our use of filters.extensions below is supported by a backwards-compatibility option (the current way is filters.mime-types.extensions
1154
1155 $plupload_init = array(
1156 'runtimes' => 'html5,flash,silverlight,html4',
1157 'browse_button' => 'plupload-browse-button',
1158 'container' => 'plupload-upload-ui',
1159 'drop_element' => 'updraft-navtab-backups-content',
1160 'file_data_name' => 'async-upload',
1161 'multiple_queues' => true,
1162 'max_file_size' => '100Gb',
1163 'chunk_size' => $chunk_size.'b',
1164 'url' => admin_url('admin-ajax.php', 'relative'),
1165 'multipart' => true,
1166 'multi_selection' => true,
1167 'urlstream_upload' => true,
1168 // additional post data to send to our ajax hook
1169 'multipart_params' => array(
1170 '_ajax_nonce' => wp_create_nonce('updraft-uploader'),
1171 'action' => 'plupload_action'
1172 )
1173 );
1174
1175 // WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
1176 if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.swf')) {
1177 $plupload_init['flash_swf_url'] = includes_url('js/plupload/Moxie.swf');
1178 } else {
1179 $plupload_init['flash_swf_url'] = includes_url('js/plupload/plupload.flash.swf');
1180 }
1181
1182 if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.xap')) {
1183 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/Moxie.xap');
1184 } else {
1185 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/plupload.silverlight.swf');
1186 }
1187
1188 ?><script>
1189 var updraft_credentialtest_nonce = '<?php echo wp_create_nonce('updraftplus-credentialtest-nonce');?>';
1190 var updraftplus_settings_nonce = '<?php echo wp_create_nonce('updraftplus-settings-nonce');?>';
1191 var updraft_siteurl = '<?php echo esc_js(site_url('', 'relative'));?>';
1192 var updraft_plupload_config = <?php echo json_encode($plupload_init); ?>;
1193 var updraft_download_nonce = '<?php echo wp_create_nonce('updraftplus_download');?>';
1194 var updraft_accept_archivename = <?php echo apply_filters('updraftplus_accept_archivename_js', "[]");?>;
1195 <?php
1196 $plupload_init['browse_button'] = 'plupload-browse-button2';
1197 $plupload_init['container'] = 'plupload-upload-ui2';
1198 $plupload_init['drop_element'] = 'drag-drop-area2';
1199 $plupload_init['multipart_params']['action'] = 'plupload_action2';
1200 $plupload_init['filters'] = array(array('title' => __('Allowed Files'), 'extensions' => 'crypt'));
1201 ?>
1202 var updraft_plupload_config2 = <?php echo json_encode($plupload_init); ?>;
1203 var updraft_downloader_nonce = '<?php wp_create_nonce("updraftplus_download"); ?>'
1204 <?php
1205 $overdue = $this->howmany_overdue_crons();
1206 if ($overdue >= 4) {
1207 ?>
1208 jQuery(function() {
1209 setTimeout(function(){ updraft_check_overduecrons(); }, 11000);
1210 });
1211 <?php } ?>
1212 </script>
1213 <?php
1214 }
1215
1216 /**
1217 * Check if available disk space is at least the specified number of bytes
1218 *
1219 * @param Integer $space - number of bytes
1220 *
1221 * @return Integer|Boolean - true or false to indicate if available; of -1 if the result is unknown
1222 */
1223 private function disk_space_check($space) {
1224 // Allow checking by some other means (user request)
1225 if (null !== ($filtered_result = apply_filters('updraftplus_disk_space_check', null, $space))) return $filtered_result;
1226 global $updraftplus;
1227 $updraft_dir = $updraftplus->backups_dir_location();
1228 $disk_free_space = function_exists('disk_free_space') ? @disk_free_space($updraft_dir) : false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1229 if (false == $disk_free_space) return -1;
1230 return ($disk_free_space > $space) ? true : false;
1231 }
1232
1233 /**
1234 * Adds the settings link under the plugin on the plugin screen.
1235 *
1236 * @param Array $links Set of links for the plugin, before being filtered
1237 * @param String $file File name (relative to the plugin directory)
1238 * @return Array filtered results
1239 */
1240 public function plugin_action_links($links, $file) {
1241 if (is_array($links) && 'updraftplus/updraftplus.php' == $file) {
1242 $settings_link = '<a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus" class="js-updraftplus-settings">'.__("Settings", "updraftplus").'</a>';
1243 array_unshift($links, $settings_link);
1244 $settings_link = '<a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/").'" target="_blank">'.__("Premium / Pro Support", "updraftplus").'</a>';
1245 array_unshift($links, $settings_link);
1246 }
1247 return $links;
1248 }
1249
1250 public function admin_action_upgrade_pluginortheme() {
1251 if (isset($_GET['action']) && ('upgrade-plugin' == $_GET['action'] || 'upgrade-theme' == $_GET['action']) && !class_exists('UpdraftPlus_Addon_Autobackup') && !defined('UPDRAFTPLUS_NOADS_B')) {
1252
1253 if ('upgrade-plugin' == $_GET['action']) {
1254 if (!current_user_can('update_plugins')) return;
1255 } else {
1256 if (!current_user_can('update_themes')) return;
1257 }
1258
1259 $dismissed_until = UpdraftPlus_Options::get_updraft_option('updraftplus_dismissedautobackup', 0);
1260 if ($dismissed_until > time()) return;
1261
1262 if ('upgrade-plugin' == $_GET['action']) {
1263 $title = __('Update Plugin');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1264 $parent_file = 'plugins.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1265 $submenu_file = 'plugins.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1266 } else {
1267 $title = __('Update Theme');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1268 $parent_file = 'themes.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1269 $submenu_file = 'themes.php';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Passed though to wp-admin/admin-header.php
1270 }
1271
1272 include_once(ABSPATH.'wp-admin/admin-header.php');
1273
1274 if (!class_exists('UpdraftPlus_Notices')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-notices.php');
1275 global $updraftplus_notices;
1276 $updraftplus_notices->do_notice('autobackup', 'autobackup');
1277 }
1278 }
1279
1280 /**
1281 * Show an administrative warning message, which can appear only on the UpdraftPlus plugin page
1282 *
1283 * @param String $message the HTML for the message (already escaped)
1284 * @param String $class CSS class to use for the div
1285 */
1286 public function show_plugin_page_admin_warning($message, $class = 'updated') {
1287
1288 global $pagenow, $plugin_page;
1289
1290 if (UpdraftPlus_Options::admin_page() !== $pagenow || 'updraftplus' !== $plugin_page) return;
1291
1292 $this->show_admin_warning($message, $class);
1293 }
1294
1295 /**
1296 * Paint a div for a dashboard warning
1297 *
1298 * @param String $message - the HTML for the message (already escaped)
1299 * @param String $class - CSS class to use for the div
1300 */
1301 public function show_admin_warning($message, $class = 'updated') {
1302 echo '<div class="updraftmessage '.$class.'">'."<p>$message</p></div>";
1303 }
1304
1305 public function show_admin_warning_multiple_storage_options() {
1306 $this->show_admin_warning('<strong>UpdraftPlus:</strong> '.__('An error occurred when fetching storage module options: ', 'updraftplus').htmlspecialchars($this->storage_module_option_errors), 'error');
1307 }
1308
1309 public function show_admin_warning_unwritable() {
1310 // One of the translators has erroneously changed "Backup" into "Back up" (which means, "reverse" !)
1311 $unwritable_mess = htmlspecialchars(str_ireplace('Back Up', 'Backup', __("The 'Backup Now' button is disabled as your backup directory is not writable (go to the 'Settings' tab and find the relevant option).", 'updraftplus')));
1312 $this->show_admin_warning($unwritable_mess, "error");
1313 }
1314
1315 public function show_admin_nosettings_warning() {
1316 $this->show_admin_warning('<strong>'.__('Welcome to UpdraftPlus!', 'updraftplus').'</strong> '.str_ireplace('Back Up', 'Backup', __('To make a backup, just press the Backup Now button.', 'updraftplus')).' <a href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" id="updraft-navtab-settings2">'.__('To change any of the default settings of what is backed up, to configure scheduled backups, to send your backups to remote storage (recommended), and more, go to the settings tab.', 'updraftplus').'</a>', 'updated notice is-dismissible');
1317 }
1318
1319 public function show_admin_warning_execution_time() {
1320 $this->show_admin_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.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)', 'updraftplus'), (int) @ini_get('max_execution_time'), 90));// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1321 }
1322
1323 public function show_admin_warning_disabledcron() {
1324 $ret = '<div class="updraftmessage updated"><p>';
1325 $ret .= '<strong>'.__('Warning', 'updraftplus').':</strong> '.__('The scheduler is disabled in your WordPress install, via the DISABLE_WP_CRON setting. No backups can run (even &quot;Backup Now&quot;) unless either you have set up a facility to call the scheduler manually, or until it is enabled.', 'updraftplus').' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/faqs/my-scheduled-backups-and-pressing-backup-now-does-nothing-however-pressing-debug-backup-does-produce-a-backup/#disablewpcron/").'" target="_blank">'.__('Go here for more information.', 'updraftplus').'</a>';
1326 $ret .= '</p></div>';
1327 return $ret;
1328 }
1329
1330 public function show_admin_warning_diskspace() {
1331 $this->show_admin_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('You have less than %s of free disk space on the disk which UpdraftPlus is configured to use to create backups. UpdraftPlus could well run out of space. Contact your the operator of your server (e.g. your web hosting company) to resolve this issue.', 'updraftplus'), '35 MB'));
1332 }
1333
1334 public function show_admin_warning_wordpressversion() {
1335 $this->show_admin_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('UpdraftPlus does not officially support versions of WordPress before %s. It may work for you, but if it does not, then please be aware that no support is available until you upgrade WordPress.', 'updraftplus'), '3.2'));
1336 }
1337
1338 public function show_admin_warning_litespeed() {
1339 $this->show_admin_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your website is hosted using the %s web server.', 'updraftplus'), 'LiteSpeed').' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/faqs/i-am-having-trouble-backing-up-and-my-web-hosting-company-uses-the-litespeed-webserver/").'" target="_blank">'.__('Please consult this FAQ if you have problems backing up.', 'updraftplus').'</a>');
1340 }
1341
1342 public function show_admin_debug_warning() {
1343 $this->show_admin_warning('<strong>'.__('Notice', 'updraftplus').':</strong> '.__('UpdraftPlus\'s debug mode is on. You may see debugging notices on this page not just from UpdraftPlus, but from any other plugin installed. Please try to make sure that the notice you are seeing is from UpdraftPlus before you raise a support request.', 'updraftplus').'</a>');
1344 }
1345
1346 /**
1347 * Return an admin warning notice
1348 *
1349 * @param Integer $howmany
1350 *
1351 * @return String
1352 */
1353 public function show_admin_warning_overdue_crons($howmany) {
1354 $ret = '<div class="updraftmessage updated"><p>';
1355 $ret .= '<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('WordPress has a number (%d) of scheduled tasks which are overdue. Unless this is a development site, this means that the scheduler in your WordPress install is not working properly.', 'updraftplus'), $howmany).' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/faqs/scheduler-wordpress-installation-working/").'" target="_blank">'.__('Read this page for a guide to possible causes and how to fix it.', 'updraftplus').'</a>';
1356 $ret .= '</p></div>';
1357 return $ret;
1358 }
1359
1360 /**
1361 * Output authorisation links for any un-authorised Dropbox settings instances
1362 */
1363 public function show_admin_warning_dropbox() {
1364 $this->get_method_auth_link('dropbox');
1365 }
1366
1367 /**
1368 * Output authorisation links for any un-authorised OneDrive settings instances
1369 */
1370 public function show_admin_warning_onedrive() {
1371 $this->get_method_auth_link('onedrive');
1372 }
1373
1374 public function show_admin_warning_updraftvault() {
1375 $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:', 'updraftplus').'</strong> '.sprintf(__('%s has been chosen for remote storage, but you are not currently connected.', 'updraftplus'), 'UpdraftPlus Vault').' '.__('Go to the remote storage settings in order to connect.', 'updraftplus'), 'updated');
1376 }
1377
1378 /**
1379 * Output authorisation links for any un-authorised Google Drive settings instances
1380 */
1381 public function show_admin_warning_googledrive() {
1382 $this->get_method_auth_link('googledrive');
1383 }
1384
1385 /**
1386 * Output authorisation links for any un-authorised Google Cloud settings instances
1387 */
1388 public function show_admin_warning_googlecloud() {
1389 $this->get_method_auth_link('googlecloud');
1390 }
1391
1392 /**
1393 * Show DreamObjects cluster migration warning
1394 */
1395 public function show_admin_warning_dreamobjects() {
1396 $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:', 'updraftplus').'</strong> '.sprintf(__('The %s endpoint is scheduled to shut down on the 1st October 2018. You will need to switch to a different end-point and migrate your data before that date. %sPlease see this article for more information%s'), 'objects-us-west-1.dream.io', '<a href="https://help.dreamhost.com/hc/en-us/articles/360002135871-Cluster-migration-procedure" target="_blank">', '</a>'), 'updated');
1397 }
1398
1399 /**
1400 * Show notice if the account connection attempted to register with UDC Cloud but could not due to lack of licences
1401 */
1402 public function show_admin_warning_udc_couldnt_connect() {
1403 $this->show_admin_warning('<strong>'.__('Notice', 'updraftplus').':</strong> '.sprintf(__('Connection to your %1$s account was successful. However, we were not able to register this site with %2$s, as there are no available %2$s licences on the account.', 'updraftplus'), 'UpdraftPlus.com', 'UpdraftCentral Cloud'), 'updated');
1404 }
1405
1406 /**
1407 * Output warning of Microsoft Azure Germany shutdown
1408 */
1409 public function show_admin_warning_azure_germany() {
1410 $this->show_admin_warning('<strong>'.__('UpdraftPlus notice', 'updraftplus').':</strong> '.sprintf(__('Due to the shutdown of the %1$s endpoint, support for %1$s will be ending soon. You will need to migrate to the Global endpoint in your UpdraftPlus settings. For more information, please see: %2$s', 'updraftplus'), 'Azure Germany', '<a href="https://www.microsoft.com/en-us/cloud-platform/germany-cloud-regions" target="_blank">https://www.microsoft.com/en-us/cloud-platform/germany-cloud-regions</a>'), 'updated');
1411 }
1412
1413 /**
1414 * Output warning of Microsoft OneDrive Germany shutdown
1415 */
1416 public function show_admin_warning_onedrive_germany() {
1417 $this->show_admin_warning('<strong>'.__('UpdraftPlus notice', 'updraftplus').':</strong> '.sprintf(__('Due to the shutdown of the %1$s endpoint, support for %1$s will be ending soon. You will need to migrate to the Global endpoint in your UpdraftPlus settings. For more information, please see: %2$s', 'updraftplus'), 'OneDrive Germany', '<a href="https://www.microsoft.com/en-us/cloud-platform/germany-cloud-regions" target="_blank">https://www.microsoft.com/en-us/cloud-platform/germany-cloud-regions</a>'), 'updated');
1418 }
1419
1420 /**
1421 * This method will setup the storage object and get the authentication link ready to be output with the notice
1422 *
1423 * @param String $method - the remote storage method
1424 */
1425 public function get_method_auth_link($method) {
1426 $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($method));
1427
1428 $object = $storage_objects_and_ids[$method]['object'];
1429
1430 foreach ($this->auth_instance_ids[$method] as $instance_id) {
1431
1432 $object->set_instance_id($instance_id);
1433
1434 $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:', 'updraftplus').'</strong> '.$object->get_authentication_link(false, false), 'updated updraft_authenticate_'.$method);
1435 }
1436 }
1437
1438 /**
1439 * Start a download of a backup. This method is called via the AJAX action updraft_download_backup. May die instead of returning depending upon the mode in which it is called.
1440 */
1441 public function updraft_download_backup() {
1442 try {
1443 if (empty($_REQUEST['_wpnonce']) || !wp_verify_nonce($_REQUEST['_wpnonce'], 'updraftplus_download')) die;
1444
1445 if (empty($_REQUEST['timestamp']) || !is_numeric($_REQUEST['timestamp']) || empty($_REQUEST['type'])) exit;
1446
1447 $findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex'];
1448 $stage = empty($_REQUEST['stage']) ? '' : $_REQUEST['stage'];
1449 $file_path = empty($_REQUEST['filepath']) ? '' : $_REQUEST['filepath'];
1450
1451 // This call may not actually return, depending upon what mode it is called in
1452 $result = $this->do_updraft_download_backup($findexes, $_REQUEST['type'], $_REQUEST['timestamp'], $stage, false, $file_path);
1453
1454 // In theory, if a response was already sent, then Connection: close has been issued, and a Content-Length. However, in https://updraftplus.com/forums/topic/pclzip_err_bad_format-10-invalid-archive-structure/ a browser ignores both of these, and then picks up the second output and complains.
1455 if (empty($result['already_closed'])) echo json_encode($result);
1456 } catch (Exception $e) {
1457 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during download backup. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1458 error_log($log_message);
1459 echo json_encode(array(
1460 'fatal_error' => true,
1461 'fatal_error_message' => $log_message
1462 ));
1463 // @codingStandardsIgnoreLine
1464 } catch (Error $e) {
1465 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during download backup. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1466 error_log($log_message);
1467 echo json_encode(array(
1468 'fatal_error' => true,
1469 'fatal_error_message' => $log_message
1470 ));
1471 }
1472 die();
1473 }
1474
1475 /**
1476 * Ensure that a specified backup is present, downloading if necessary (or delete it, if the parameters so indicate). N.B. This function may die(), depending on the request being made in $stage
1477 *
1478 * @param Array $findexes - the index number of the backup archive requested
1479 * @param String $type - the entity type (e.g. 'plugins') being requested
1480 * @param Integer $timestamp - identifier for the backup being requested (UNIX epoch time)
1481 * @param Mixed $stage - the stage; valid values include (have not audited for other possibilities) at least 'delete' and 2.
1482 * @param Callable|Boolean $close_connection_callable - function used to close the connection to the caller; an array of data to return is passed. If false, then UpdraftPlus::close_browser_connection is called with a JSON version of the data.
1483 * @param String $file_path - an over-ride for where to download the file to (basename only)
1484 *
1485 * @return Array - sumary of the results. May also just die.
1486 */
1487 public function do_updraft_download_backup($findexes, $type, $timestamp, $stage, $close_connection_callable = false, $file_path = '') {
1488
1489 if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1490
1491 global $updraftplus;
1492
1493 if (!is_array($findexes)) $findexes = array($findexes);
1494
1495 $connection_closed = false;
1496
1497 // Check that it is a known entity type; if not, die
1498 if ('db' != substr($type, 0, 2)) {
1499 $backupable_entities = $updraftplus->get_backupable_file_entities(true);
1500 foreach ($backupable_entities as $t => $info) {
1501 if ($type == $t) $type_match = true;
1502 }
1503 if (empty($type_match)) return array('result' => 'error', 'code' => 'no_such_type');
1504 }
1505
1506 $debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode');
1507
1508 // Retrieve the information from our backup history
1509 $backup_history = UpdraftPlus_Backup_History::get_history();
1510
1511 foreach ($findexes as $findex) {
1512 // This is a bit ugly; these variables get placed back into $_POST (where they may possibly have come from), so that UpdraftPlus::log() can detect exactly where to log the download status.
1513 $_POST['findex'] = $findex;
1514 $_POST['type'] = $type;
1515 $_POST['timestamp'] = $timestamp;
1516
1517 // We already know that no possible entities have an MD5 clash (even after 2 characters)
1518 // Also, there's nothing enforcing a requirement that nonces are hexadecimal
1519 $job_nonce = dechex($timestamp).$findex.substr(md5($type), 0, 3);
1520
1521 // You need a nonce before you can set job data. And we certainly don't yet have one.
1522 $updraftplus->backup_time_nonce($job_nonce);
1523
1524 // Set the job type before logging, as there can be different logging destinations
1525 $updraftplus->jobdata_set('job_type', 'download');
1526 $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms);
1527
1528 // Base name
1529 $file = $backup_history[$timestamp][$type];
1530
1531 // Deal with multi-archive sets
1532 if (is_array($file)) $file = $file[$findex];
1533
1534 if (false !== strpos($file_path, '..')) {
1535 error_log("UpdraftPlus_Admin::do_updraft_download_backup : invalid file_path: $file_path");
1536 return array('result' => __('Error: invalid path', 'updraftplus'));
1537 }
1538
1539 if (!empty($file_path)) $file = $file_path;
1540
1541 // Where it should end up being downloaded to
1542 $fullpath = $updraftplus->backups_dir_location().'/'.$file;
1543
1544 if (!empty($file_path) && strpos(realpath($fullpath), realpath($updraftplus->backups_dir_location())) === false) {
1545 error_log("UpdraftPlus_Admin::do_updraft_download_backup : invalid fullpath: $fullpath");
1546 return array('result' => __('Error: invalid path', 'updraftplus'));
1547 }
1548
1549 if (2 == $stage) {
1550 $updraftplus->spool_file($fullpath);
1551 // We only want to remove if it was a temp file from the zip browser
1552 if (!empty($file_path)) @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1553 // Do not return - we do not want the caller to add any output
1554 die;
1555 }
1556
1557 if ('delete' == $stage) {
1558 @unlink($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1559 $updraftplus->log("The file has been deleted ($file)");
1560 return array('result' => 'deleted');
1561 }
1562
1563 // TODO: FIXME: Failed downloads may leave log files forever (though they are small)
1564 if ($debug_mode) $updraftplus->logfile_open($updraftplus->nonce);
1565
1566 set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
1567
1568 $updraftplus->log("Requested to obtain file: timestamp=$timestamp, type=$type, index=$findex");
1569
1570 $itext = empty($findex) ? '' : $findex;
1571 $known_size = isset($backup_history[$timestamp][$type.$itext.'-size']) ? $backup_history[$timestamp][$type.$itext.'-size'] : 0;
1572
1573 $services = isset($backup_history[$timestamp]['service']) ? $backup_history[$timestamp]['service'] : false;
1574
1575 $services = $updraftplus->get_canonical_service_list($services);
1576
1577 $updraftplus->jobdata_set('service', $services);
1578
1579 // Fetch it from the cloud, if we have not already got it
1580
1581 $needs_downloading = false;
1582
1583 if (!file_exists($fullpath) && empty($services)) {
1584 $updraftplus->log('This file does not exist locally, and there is no remote storage for this file.');
1585 } elseif (!file_exists($fullpath)) {
1586 // If the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
1587 $needs_downloading = true;
1588 $updraftplus->log('File does not yet exist locally - needs downloading');
1589 } elseif (is_array($services) && 1 == count($services) && 'email' == $services[0]) {
1590 $updraftplus->log(__('The email protocol does not allow a remote backup to be retrieved from an email that has been sent. Therefore, please download the attachment from the original backup email and upload it using the "Upload backup files" facility in the "Existing Backups" tab.', 'updraftplus'), 'error');
1591 $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'failed');
1592 $updraftplus->jobdata_set('dlerrors_'.$timestamp.'_'.$type.'_'.$findex, $updraftplus->errors);
1593 return array('result' => 'error', 'code' => 'no_email_download');
1594 } elseif ($known_size > 0 && filesize($fullpath) < $known_size) {
1595 $updraftplus->log("The file was found locally (".filesize($fullpath).") but did not match the size in the backup history ($known_size) - will resume downloading");
1596 $needs_downloading = true;
1597 } elseif ($known_size > 0 && filesize($fullpath) > $known_size) {
1598 $updraftplus->log("The file was found locally (".filesize($fullpath).") but the size is larger than what is recorded in the backup history ($known_size) - will try to continue but if errors are encountered then check that the backup is correct");
1599 } elseif ($known_size > 0) {
1600 $updraftplus->log('The file was found locally and matched the recorded size from the backup history ('.round($known_size/1024, 1).' KB)');
1601 } else {
1602 $updraftplus->log('No file size was found recorded in the backup history. We will assume the local one is complete.');
1603 $known_size = filesize($fullpath);
1604 }
1605
1606 // The AJAX responder that updates on progress wants to see this
1607 $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath");
1608
1609 if ($needs_downloading) {
1610
1611 // Update the "last modified" time to dissuade any other instances from thinking that no downloaders are active
1612 @touch($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1613
1614 $msg = array(
1615 'result' => 'needs_download',
1616 'request' => array(
1617 'type' => $type,
1618 'timestamp' => $timestamp,
1619 'findex' => $findex
1620 )
1621 );
1622
1623 if ($close_connection_callable && is_callable($close_connection_callable) && !$connection_closed) {
1624 $connection_closed = true;
1625 call_user_func($close_connection_callable, $msg);
1626 } elseif (!$connection_closed) {
1627 $connection_closed = true;
1628 $updraftplus->close_browser_connection(json_encode($msg));
1629 }
1630 UpdraftPlus_Storage_Methods_Interface::get_remote_file($services, $file, $timestamp);
1631 $needs_downloading = false;
1632 }
1633
1634 // Now, be ready to spool the thing to the browser
1635 if (is_file($fullpath) && is_readable($fullpath) && !$needs_downloading) {
1636
1637 // That message is then picked up by the AJAX listener
1638 $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'downloaded:'.filesize($fullpath).":$fullpath");
1639
1640 $result = 'downloaded';
1641
1642 } elseif ($needs_downloading) {
1643
1644 $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, 'failed');
1645 $updraftplus->jobdata_set('dlerrors_'.$timestamp.'_'.$type.'_'.$findex, $updraftplus->errors);
1646 $updraftplus->log('Remote fetch failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.');
1647
1648 $result = 'download_failed';
1649 } else {
1650 $result = 'no_local_file';
1651 }
1652
1653 restore_error_handler();
1654
1655 if ($debug_mode) @fclose($updraftplus->logfile_handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1656 if (!$debug_mode) @unlink($updraftplus->logfile_name);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1657 }
1658
1659 // The browser connection was possibly already closed, but not necessarily
1660 return array('result' => $result, 'already_closed' => $connection_closed);
1661 }
1662
1663 /**
1664 * This is used as a callback
1665 *
1666 * @param Mixed $msg The data to be JSON encoded and sent back
1667 */
1668 public function _updraftplus_background_operation_started($msg) {
1669 global $updraftplus;
1670 // The extra spaces are because of a bug seen on one server in handling of non-ASCII characters; see HS#11739
1671 $updraftplus->close_browser_connection(json_encode($msg).' ');
1672 }
1673
1674 public function updraft_ajax_handler() {
1675
1676 $nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
1677
1678 if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
1679
1680 $subaction = $_REQUEST['subaction'];
1681 // Mitigation in case the nonce leaked to an unauthorised user
1682 if ('dismissautobackup' == $subaction) {
1683 if (!current_user_can('update_plugins') && !current_user_can('update_themes')) return;
1684 } elseif ('dismissexpiry' == $subaction || 'dismissdashnotice' == $subaction) {
1685 if (!current_user_can('update_plugins')) return;
1686 } else {
1687 if (!UpdraftPlus_Options::user_can_manage()) return;
1688 }
1689
1690 // All others use _POST
1691 $data_in_get = array('get_log', 'get_fragment');
1692
1693 // UpdraftPlus_WPAdmin_Commands extends UpdraftPlus_Commands - i.e. all commands are in there
1694 if (!class_exists('UpdraftPlus_WPAdmin_Commands')) include_once(UPDRAFTPLUS_DIR.'/includes/class-wpadmin-commands.php');
1695 $commands = new UpdraftPlus_WPAdmin_Commands($this);
1696
1697 if (method_exists($commands, $subaction)) {
1698
1699 $data = in_array($subaction, $data_in_get) ? $_GET : $_POST;
1700
1701 // Undo WP's slashing of GET/POST data
1702 $data = UpdraftPlus_Manipulation_Functions::wp_unslash($data);
1703
1704 // TODO: Once all commands come through here and through updraft_send_command(), the data should always come from this attribute (once updraft_send_command() is modified appropriately).
1705 if (isset($data['action_data'])) $data = $data['action_data'];
1706 try {
1707 $results = call_user_func(array($commands, $subaction), $data);
1708 } catch (Exception $e) {
1709 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during '.$subaction.' subaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1710 error_log($log_message);
1711 echo json_encode(array(
1712 'fatal_error' => true,
1713 'fatal_error_message' => $log_message
1714 ));
1715 die;
1716 // @codingStandardsIgnoreLine
1717 } catch (Error $e) {
1718 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during '.$subaction.' subaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1719 error_log($log_message);
1720 echo json_encode(array(
1721 'fatal_error' => true,
1722 'fatal_error_message' => $log_message
1723 ));
1724 die;
1725 }
1726 if (is_wp_error($results)) {
1727 $results = array(
1728 'result' => false,
1729 'error_code' => $results->get_error_code(),
1730 'error_message' => $results->get_error_message(),
1731 'error_data' => $results->get_error_data(),
1732 );
1733 }
1734
1735 if (is_string($results)) {
1736 // A handful of legacy methods, and some which are directly the source for iframes, for which JSON is not appropriate.
1737 echo $results;
1738 } else {
1739 echo json_encode($results);
1740 }
1741 die;
1742 }
1743
1744 // Below are all the commands not ported over into class-commands.php or class-wpadmin-commands.php
1745
1746 if ('activejobs_list' == $subaction) {
1747 try {
1748 // N.B. Also called from autobackup.php
1749 // TODO: This should go into UpdraftPlus_Commands, once the add-ons have been ported to use updraft_send_command()
1750 echo json_encode($this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($_GET)));
1751 } catch (Exception $e) {
1752 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1753 error_log($log_message);
1754 echo json_encode(array(
1755 'fatal_error' => true,
1756 'fatal_error_message' => $log_message
1757 ));
1758 // @codingStandardsIgnoreLine
1759 } catch (Error $e) {
1760 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1761 error_log($log_message);
1762 echo json_encode(array(
1763 'fatal_error' => true,
1764 'fatal_error_message' => $log_message
1765 ));
1766 }
1767
1768 } elseif ('httpget' == $subaction) {
1769 try {
1770 // httpget
1771 $curl = empty($_REQUEST['curl']) ? false : true;
1772 echo $this->http_get(UpdraftPlus_Manipulation_Functions::wp_unslash($_REQUEST['uri']), $curl);
1773 // @codingStandardsIgnoreLine
1774 } catch (Error $e) {
1775 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during http get. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1776 error_log($log_message);
1777 echo json_encode(array(
1778 'fatal_error' => true,
1779 'fatal_error_message' => $log_message
1780 ));
1781 } catch (Exception $e) {
1782 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during http get. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1783 error_log($log_message);
1784 echo json_encode(array(
1785 'fatal_error' => true,
1786 'fatal_error_message' => $log_message
1787 ));
1788 }
1789
1790 } elseif ('doaction' == $subaction && !empty($_REQUEST['subsubaction']) && 'updraft_' == substr($_REQUEST['subsubaction'], 0, 8)) {
1791 $subsubaction = $_REQUEST['subsubaction'];
1792 try {
1793 // These generally echo and die - they will need further work to port to one of the command classes. Some may already have equivalents in UpdraftPlus_Commands, if they are used from UpdraftCentral.
1794 do_action(UpdraftPlus_Manipulation_Functions::wp_unslash($subsubaction), $_REQUEST);
1795 } catch (Exception $e) {
1796 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during doaction subaction with '.$subsubaction.' subsubaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1797 error_log($log_message);
1798 echo json_encode(array(
1799 'fatal_error' => true,
1800 'fatal_error_message' => $log_message
1801 ));
1802 die;
1803 // @codingStandardsIgnoreLine
1804 } catch (Error $e) {
1805 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during doaction subaction with '.$subsubaction.' subsubaction. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
1806 error_log($log_message);
1807 echo json_encode(array(
1808 'fatal_error' => true,
1809 'fatal_error_message' => $log_message
1810 ));
1811 die;
1812 }
1813 }
1814
1815 die;
1816
1817 }
1818
1819 /**
1820 * Run a credentials test for the indicated remote storage module
1821 *
1822 * @param Array $test_settings The test parameters, including the method itself indicated in the key 'method'
1823 * @param Boolean $return_instead_of_echo Whether to return or echo the results. N.B. More than just the results to echo will be returned
1824 * @return Array|Void - the results, if they are being returned (rather than echoed). Keys: 'output' (the output), 'data' (other data)
1825 */
1826 public function do_credentials_test($test_settings, $return_instead_of_echo = false) {
1827
1828 $method = (!empty($test_settings['method']) && preg_match("/^[a-z0-9]+$/", $test_settings['method'])) ? $test_settings['method'] : "";
1829
1830 $objname = "UpdraftPlus_BackupModule_$method";
1831
1832 $this->logged = array();
1833 // TODO: Add action for WP HTTP SSL stuff
1834 set_error_handler(array($this, 'get_php_errors'), E_ALL & ~E_STRICT);
1835
1836 if (!class_exists($objname)) include_once(UPDRAFTPLUS_DIR."/methods/$method.php");
1837
1838 $ret = '';
1839 $data = null;
1840
1841 // TODO: Add action for WP HTTP SSL stuff
1842 if (method_exists($objname, "credentials_test")) {
1843 $obj = new $objname;
1844 if ($return_instead_of_echo) ob_start();
1845 $data = $obj->credentials_test($test_settings);
1846 if ($return_instead_of_echo) $ret .= ob_get_clean();
1847 }
1848
1849 if (count($this->logged) >0) {
1850 $ret .= "\n\n".__('Messages:', 'updraftplus')."\n";
1851 foreach ($this->logged as $err) {
1852 $ret .= "* $err\n";
1853 }
1854 if (!$return_instead_of_echo) echo $ret;
1855 }
1856 restore_error_handler();
1857
1858 if ($return_instead_of_echo) return array('output' => $ret, 'data' => $data);
1859
1860 }
1861
1862 /**
1863 * Delete a backup set, whilst respecting limits on how much to delete in one go
1864 *
1865 * @uses remove_backup_set_cleanup()
1866 * @param Array $opts - deletion options; with keys backup_timestamp, delete_remote, [remote_delete_limit]
1867 * @return Array - as from remove_backup_set_cleanup()
1868 */
1869 public function delete_set($opts) {
1870
1871 global $updraftplus;
1872
1873 $backups = UpdraftPlus_Backup_History::get_history();
1874 $timestamps = (string) $opts['backup_timestamp'];
1875
1876 $remote_delete_limit = (isset($opts['remote_delete_limit']) && $opts['remote_delete_limit'] > 0) ? (int) $opts['remote_delete_limit'] : PHP_INT_MAX;
1877
1878 $timestamps = explode(',', $timestamps);
1879 $deleted_timestamps = '';
1880 $delete_remote = empty($opts['delete_remote']) ? false : true;
1881
1882 // You need a nonce before you can set job data. And we certainly don't yet have one.
1883 $updraftplus->backup_time_nonce();
1884 // Set the job type before logging, as there can be different logging destinations
1885 $updraftplus->jobdata_set('job_type', 'delete');
1886 $updraftplus->jobdata_set('job_time_ms', $updraftplus->job_time_ms);
1887
1888 if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
1889 $updraftplus->logfile_open($updraftplus->nonce);
1890 set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
1891 }
1892
1893 $updraft_dir = $updraftplus->backups_dir_location();
1894 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
1895
1896 $local_deleted = 0;
1897 $remote_deleted = 0;
1898 $sets_removed = 0;
1899
1900 $deletion_errors = array();
1901
1902 foreach ($timestamps as $i => $timestamp) {
1903
1904 if (!isset($backups[$timestamp])) {
1905 return array('result' => 'error', 'message' => __('Backup set not found', 'updraftplus'));
1906 }
1907
1908 $nonce = isset($backups[$timestamp]['nonce']) ? $backups[$timestamp]['nonce'] : '';
1909
1910 $delete_from_service = array();
1911
1912 if ($delete_remote) {
1913 // Locate backup set
1914 if (isset($backups[$timestamp]['service'])) {
1915 // Convert to an array so that there is no uncertainty about how to process it
1916 $services = is_string($backups[$timestamp]['service']) ? array($backups[$timestamp]['service']) : $backups[$timestamp]['service'];
1917 if (is_array($services)) {
1918 foreach ($services as $service) {
1919 if ($service && 'none' != $service && 'email' != $service) $delete_from_service[] = $service;
1920 }
1921 }
1922 }
1923 }
1924
1925 $files_to_delete = array();
1926 foreach ($backupable_entities as $key => $ent) {
1927 if (isset($backups[$timestamp][$key])) {
1928 $files_to_delete[$key] = $backups[$timestamp][$key];
1929 }
1930 }
1931 // Delete DB
1932 foreach ($backups[$timestamp] as $key => $value) {
1933 if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) {
1934 $files_to_delete[$key] = $backups[$timestamp][$key];
1935 }
1936 }
1937
1938 // Also delete the log
1939 if ($nonce && !UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
1940 $files_to_delete['log'] = "log.$nonce.txt";
1941 }
1942
1943 $updraftplus->register_wp_http_option_hooks();
1944
1945 foreach ($files_to_delete as $key => $files) {
1946
1947 if (is_string($files)) {
1948 $was_string = true;
1949 $files = array($files);
1950 } else {
1951 $was_string = false;
1952 }
1953
1954 foreach ($files as $file) {
1955 if (is_file($updraft_dir.'/'.$file) && @unlink($updraft_dir.'/'.$file)) $local_deleted++;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1956 }
1957
1958 if ('log' != $key && count($delete_from_service) > 0) {
1959
1960 $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids($delete_from_service);
1961
1962 foreach ($delete_from_service as $service) {
1963
1964 if ('email' == $service || 'none' == $service || !$service) continue;
1965
1966 $deleted = -1;
1967
1968 $remote_obj = $storage_objects_and_ids[$service]['object'];
1969
1970 $instance_settings = $storage_objects_and_ids[$service]['instance_settings'];
1971 $this->backups_instance_ids = empty($backups[$timestamp]['service_instance_ids'][$service]) ? array() : $backups[$timestamp]['service_instance_ids'][$service];
1972
1973 if (empty($instance_settings)) continue;
1974
1975 uksort($instance_settings, array($this, 'instance_ids_sort'));
1976
1977 foreach ($instance_settings as $instance_id => $options) {
1978
1979 $remote_obj->set_options($options, false, $instance_id);
1980
1981 if ($remote_obj->supports_feature('multi_delete')) {
1982 if ($remote_deleted == $remote_delete_limit) {
1983 $timestamps_list = implode(',', $timestamps);
1984
1985 return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors);
1986 }
1987
1988 $deleted = $remote_obj->delete($files);
1989
1990 if (true === $deleted) {
1991 $remote_deleted = $remote_deleted + count($files);
1992
1993 unset($backups[$timestamp][$key]);
1994
1995 // 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.
1996 UpdraftPlus_Backup_History::save_history($backups);
1997 } else {
1998 // Handle abstracted error codes/return fail status. Including handle array/objects returned
1999 if (is_object($deleted) || is_array($deleted)) $deleted = false;
2000
2001 if (!array_key_exists($instance_id, $deletion_errors)) {
2002 $deletion_errors[$instance_id] = array('error_code' => $deleted, 'service' => $service);
2003 }
2004 }
2005
2006 continue;
2007 }
2008 foreach ($files as $index => $file) {
2009 if ($remote_deleted == $remote_delete_limit) {
2010 $timestamps_list = implode(',', $timestamps);
2011
2012 return $this->remove_backup_set_cleanup(false, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors);
2013 }
2014
2015 $deleted = $remote_obj->delete($file);
2016
2017 if (true === $deleted) {
2018 $remote_deleted++;
2019 } else {
2020 // Handle abstracted error codes/return fail status. Including handle array/objects returned
2021 if (is_object($deleted) || is_array($deleted)) $deleted = false;
2022
2023 if (!array_key_exists($instance_id, $deletion_errors)) {
2024 $deletion_errors[$instance_id] = array('error_code' => $deleted, 'service' => $service);
2025 }
2026 }
2027
2028 $itext = $index ? (string) $index : '';
2029 if ($was_string) {
2030 unset($backups[$timestamp][$key]);
2031 if ('db' == strtolower(substr($key, 0, 2))) unset($backups[$timestamp][$key][$index.'-size']);
2032 } else {
2033 unset($backups[$timestamp][$key][$index]);
2034 unset($backups[$timestamp][$key.$itext.'-size']);
2035 if (empty($backups[$timestamp][$key])) unset($backups[$timestamp][$key]);
2036 }
2037 if (isset($backups[$timestamp]['checksums']) && is_array($backups[$timestamp]['checksums'])) {
2038 foreach (array_keys($backups[$timestamp]['checksums']) as $algo) {
2039 unset($backups[$timestamp]['checksums'][$algo][$key.$index]);
2040 }
2041 }
2042
2043 // 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.
2044 UpdraftPlus_Backup_History::save_history($backups);
2045 }
2046 }
2047 }
2048 }
2049 }
2050
2051 unset($backups[$timestamp]);
2052 unset($timestamps[$i]);
2053 if ('' != $deleted_timestamps) $deleted_timestamps .= ',';
2054 $deleted_timestamps .= $timestamp;
2055 UpdraftPlus_Backup_History::save_history($backups);
2056 $sets_removed++;
2057 }
2058
2059 $timestamps_list = implode(',', $timestamps);
2060
2061 return $this->remove_backup_set_cleanup(true, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps_list, $deleted_timestamps, $deletion_errors);
2062
2063 }
2064
2065 /**
2066 * This function sorts the array of instance ids currently saved so that any instance id that is in both the saved settings and the backup history move to the top of the array, as these are likely to work. Then values that don't appear in the backup history move to the bottom.
2067 *
2068 * @param String $a - the first instance id
2069 * @param String $b - the second instance id
2070 * @return Integer - returns an integer to indicate what position the $b value should be moved in
2071 */
2072 public function instance_ids_sort($a, $b) {
2073 if (in_array($a, $this->backups_instance_ids)) {
2074 if (in_array($b, $this->backups_instance_ids)) return 0;
2075 return -1;
2076 }
2077 return in_array($b, $this->backups_instance_ids) ? 1 : 0;
2078 }
2079
2080 /**
2081 * Called by self::delete_set() to finish up before returning (whether the complete deletion is finished or not)
2082 *
2083 * @param Boolean $delete_complete - whether the whole set is now gone (i.e. last round)
2084 * @param Array $backups - the backup history
2085 * @param Integer $local_deleted - how many backup archives were deleted from local storage
2086 * @param Integer $remote_deleted - how many backup archives were deleted from remote storage
2087 * @param Integer $sets_removed - how many complete sets were removed
2088 * @param String $timestamps - a csv of remaining timestamps
2089 * @param String $deleted_timestamps - a csv of deleted timestamps
2090 * @param Array $deletion_errors - an array of abstracted deletion errors, consisting of [error_code, service, instance]. For user notification purposes only, main error logging occurs at service.
2091 *
2092 * @return Array - information on the status, suitable for returning to the UI
2093 */
2094 public function remove_backup_set_cleanup($delete_complete, $backups, $local_deleted, $remote_deleted, $sets_removed, $timestamps, $deleted_timestamps, $deletion_errors = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $deletion_errors was used below but the code has been commented out. Can both be removed?
2095
2096 global $updraftplus;
2097
2098 $updraftplus->register_wp_http_option_hooks(false);
2099
2100 UpdraftPlus_Backup_History::save_history($backups);
2101
2102 $updraftplus->log("Local files deleted: $local_deleted. Remote files deleted: $remote_deleted");
2103
2104 /*
2105 Disable until next release
2106 $error_messages = array();
2107 $storage_details = array();
2108
2109 foreach ($deletion_errors as $instance => $entry) {
2110 $service = $entry['service'];
2111
2112 if (!array_key_exists($service, $storage_details)) {
2113 // As errors from multiple instances of a service can be present, store the service storage object for possible use later
2114 $new_service = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($service));
2115 $storage_details = array_merge($storage_details, $new_service);
2116 }
2117
2118 $intance_label = !empty($storage_details[$service]['instance_settings'][$instance]['instance_label']) ? $storage_details[$service]['instance_settings'][$instance]['instance_label'] : $service;
2119
2120 switch ($entry['error_code']) {
2121 case 'authentication_fail':
2122 $error_messages[] = sprintf(__("The authentication failed for '%s'.", 'updraftplus').' '.__('Please check your credentials.', 'updraftplus'), $intance_label);
2123 break;
2124 case 'service_unavailable':
2125 $error_messages[] = sprintf(__("We were unable to access '%s'.", 'updraftplus').' '.__('Service unavailable.', 'updraftplus'), $intance_label);
2126 break;
2127 case 'container_access_error':
2128 $error_messages[] = sprintf(__("We were unable to access the folder/container for '%s'.", 'updraftplus').' '.__('Please check your permissions.', 'updraftplus'), $intance_label);
2129 break;
2130 case 'file_access_error':
2131 $error_messages[] = sprintf(__("We were unable to access a file on '%s'.", 'updraftplus').' '.__('Please check your permissions.', 'updraftplus'), $intance_label);
2132 break;
2133 case 'file_delete_error':
2134 $error_messages[] = sprintf(__("We were unable to delete a file on '%s'.", 'updraftplus').' '.__('The file may no longer exist or you may not have permission to delete.', 'updraftplus'), $intance_label);
2135 break;
2136 default:
2137 $error_messages[] = sprintf(__("An error occurred while attempting to delete from '%s'.", 'updraftplus'), $intance_label);
2138 break;
2139 }
2140 }
2141 */
2142
2143 // $error_message_string = implode("\n", $error_messages);
2144 $error_message_string = '';
2145
2146 if ($delete_complete) {
2147 $set_message = __('Backup sets removed:', 'updraftplus');
2148 $local_message = __('Local files deleted:', 'updraftplus');
2149 $remote_message = __('Remote files deleted:', 'updraftplus');
2150
2151 if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
2152 restore_error_handler();
2153 }
2154
2155 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, 'error_messages' => $error_message_string);
2156 } else {
2157
2158 return array('result' => 'continue', 'backup_local' => $local_deleted, 'backup_remote' => $remote_deleted, 'backup_sets' => $sets_removed, 'timestamps' => $timestamps, 'deleted_timestamps' => $deleted_timestamps, 'error_messages' => $error_message_string);
2159 }
2160 }
2161
2162 /**
2163 * Get the history status HTML and other information
2164 *
2165 * @param Boolean $rescan - whether to rescan local storage
2166 * @param Boolean $remotescan - whether to also rescan remote storage
2167 * @param Boolean $debug - whether to return debugging information also
2168 * @param Integer $backup_count - a count of the total backups we want to display on the front end for use by UpdraftPlus_Backup_History::existing_backup_table()
2169 *
2170 * @return Array - the information requested
2171 */
2172 public function get_history_status($rescan, $remotescan, $debug = false, $backup_count = 0) {
2173
2174 global $updraftplus;
2175
2176 if ($rescan) $messages = UpdraftPlus_Backup_History::rebuild($remotescan, false, $debug);
2177 $backup_history = UpdraftPlus_Backup_History::get_history();
2178 $output = UpdraftPlus_Backup_History::existing_backup_table($backup_history, $backup_count);
2179
2180 $data = array();
2181
2182 if (!empty($messages) && is_array($messages)) {
2183 $noutput = '';
2184 foreach ($messages as $msg) {
2185 if (empty($msg['code']) || 'file-listing' != $msg['code']) {
2186 $noutput .= '<li>'.(empty($msg['desc']) ? '' : $msg['desc'].': ').'<em>'.$msg['message'].'</em></li>';
2187 }
2188 if (!empty($msg['data'])) {
2189 $key = $msg['method'].'-'.$msg['service_instance_id'];
2190 $data[$key] = $msg['data'];
2191 }
2192 }
2193 if ($noutput) {
2194 $output = '<div style="margin-left: 100px; margin-top: 10px;"><ul style="list-style: disc inside;">'.$noutput.'</ul></div>'.$output;
2195 }
2196 }
2197
2198 $logs_exist = (false !== strpos($output, 'downloadlog'));
2199 if (!$logs_exist) {
2200 list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
2201 if ($mod_time) $logs_exist = true;
2202 }
2203
2204 return apply_filters('updraftplus_get_history_status_result', array(
2205 'n' => __('Existing backups', 'updraftplus').' <span class="updraft_existing_backups_count">'.count($backup_history).'</span>',
2206 't' => $output, // table
2207 'data' => $data,
2208 'cksum' => md5($output),
2209 'logs_exist' => $logs_exist,
2210 'web_server_disk_space' => UpdraftPlus_Filesystem_Functions::web_server_disk_space(true),
2211 ));
2212 }
2213
2214 /**
2215 * Stop an active backup job
2216 *
2217 * @param String $job_id - job ID of the job to stop
2218 *
2219 * @return Array - information on the outcome of the attempt
2220 */
2221 public function activejobs_delete($job_id) {
2222
2223 if (preg_match("/^[0-9a-f]{12}$/", $job_id)) {
2224
2225 global $updraftplus;
2226 $cron = get_option('cron', array());
2227 $found_it = false;
2228
2229 $jobdata = $updraftplus->jobdata_getarray($job_id);
2230
2231 if (!empty($jobdata['clone_job']) && !empty($jobdata['clone_id']) && !empty($jobdata['secret_token'])) {
2232 $clone_id = $jobdata['clone_id'];
2233 $secret_token = $jobdata['secret_token'];
2234 $updraftplus->get_updraftplus_clone()->clone_failed_delete(array('clone_id' => $clone_id, 'secret_token' => $secret_token));
2235 }
2236
2237 $updraft_dir = $updraftplus->backups_dir_location();
2238 if (file_exists($updraft_dir.'/log.'.$job_id.'.txt')) touch($updraft_dir.'/deleteflag-'.$job_id.'.txt');
2239
2240 foreach ($cron as $time => $job) {
2241 if (!isset($job['updraft_backup_resume'])) continue;
2242 foreach ($job['updraft_backup_resume'] as $hook => $info) {
2243 if (isset($info['args'][1]) && $info['args'][1] == $job_id) {
2244 $args = $cron[$time]['updraft_backup_resume'][$hook]['args'];
2245 wp_unschedule_event($time, 'updraft_backup_resume', $args);
2246 if (!$found_it) return array('ok' => 'Y', 'c' => 'deleted', 'm' => __('Job deleted', 'updraftplus'));
2247 $found_it = true;
2248 }
2249 }
2250 }
2251 }
2252
2253 if (!$found_it) return array('ok' => 'N', 'c' => 'not_found', 'm' => __('Could not find that job - perhaps it has already finished?', 'updraftplus'));
2254
2255 }
2256
2257 /**
2258 * Input: an array of items
2259 * Each item is in the format: <base>,<timestamp>,<type>(,<findex>)
2260 * The 'base' is not for us: we just pass it straight back
2261 *
2262 * @param array $downloaders Array of Items to download
2263 * @return array
2264 */
2265 public function get_download_statuses($downloaders) {
2266 global $updraftplus;
2267 $download_status = array();
2268 foreach ($downloaders as $downloader) {
2269 // prefix, timestamp, entity, index
2270 if (preg_match('/^([^,]+),(\d+),([-a-z]+|db[0-9]+),(\d+)$/', $downloader, $matches)) {
2271 $findex = (empty($matches[4])) ? '0' : $matches[4];
2272 $updraftplus->nonce = dechex($matches[2]).$findex.substr(md5($matches[3]), 0, 3);
2273 $updraftplus->jobdata_reset();
2274 $status = $this->download_status($matches[2], $matches[3], $matches[4]);
2275 if (is_array($status)) {
2276 $status['base'] = $matches[1];
2277 $status['timestamp'] = $matches[2];
2278 $status['what'] = $matches[3];
2279 $status['findex'] = $findex;
2280 $download_status[] = $status;
2281 }
2282 }
2283 }
2284 return $download_status;
2285 }
2286
2287 /**
2288 * Get, as HTML output, a list of active jobs
2289 *
2290 * @param Array $request - details on the request being made (e.g. extra info to include)
2291 *
2292 * @return String
2293 */
2294 public function get_activejobs_list($request) {
2295
2296 global $updraftplus;
2297
2298 $download_status = empty($request['downloaders']) ? array() : $this->get_download_statuses(explode(':', $request['downloaders']));
2299
2300 if (!empty($request['oneshot'])) {
2301 $job_id = get_site_option('updraft_oneshotnonce', false);
2302 // print_active_job() for one-shot jobs that aren't in cron
2303 $active_jobs = (false === $job_id) ? '' : $this->print_active_job($job_id, true);
2304 } elseif (!empty($request['thisjobonly'])) {
2305 // print_active_jobs() is for resumable jobs where we want the cron info to be included in the output
2306 $active_jobs = $this->print_active_jobs($request['thisjobonly']);
2307 } else {
2308 $active_jobs = $this->print_active_jobs();
2309 }
2310 $logupdate_array = array();
2311 if (!empty($request['log_fetch'])) {
2312 if (isset($request['log_nonce'])) {
2313 $log_nonce = $request['log_nonce'];
2314 $log_pointer = isset($request['log_pointer']) ? absint($request['log_pointer']) : 0;
2315 $logupdate_array = $this->fetch_log($log_nonce, $log_pointer);
2316 }
2317 }
2318 $res = array(
2319 // We allow the front-end to decide what to do if there's nothing logged - we used to (up to 1.11.29) send a pre-defined message
2320 'l' => htmlspecialchars(UpdraftPlus_Options::get_updraft_lastmessage()),
2321 'j' => $active_jobs,
2322 'ds' => $download_status,
2323 'u' => $logupdate_array,
2324 'automatic_updates' => $updraftplus->is_automatic_updating_enabled()
2325 );
2326
2327 $res['hosting_restriction'] = $updraftplus->is_hosting_backup_limit_reached();
2328
2329 return $res;
2330 }
2331
2332 /**
2333 * Start a new backup
2334 *
2335 * @param Array $request
2336 * @param Boolean|Callable $close_connection_callable
2337 */
2338 public function request_backupnow($request, $close_connection_callable = false) {
2339 global $updraftplus;
2340
2341 $abort_before_booting = false;
2342 $backupnow_nocloud = !empty($request['backupnow_nocloud']);
2343
2344 $request['incremental'] = !empty($request['incremental']);
2345
2346 $entities = !empty($request['onlythisfileentity']) ? explode(',', $request['onlythisfileentity']) : array();
2347
2348 $remote_storage_instances = array();
2349
2350 // if only_these_cloud_services is not an array then all connected remote storage locations are being backed up to and we don't need to do this
2351 if (!empty($request['only_these_cloud_services']) && is_array($request['only_these_cloud_services'])) {
2352 $remote_storage_locations = $request['only_these_cloud_services'];
2353
2354 foreach ($remote_storage_locations as $key => $value) {
2355 /*
2356 This name key inside the value array is the remote storage method name prefixed by 31 characters (updraft_include_remote_service_) so we need to remove them to get the actual name, then the value key inside the value array has the instance id.
2357 */
2358 $remote_storage_instances[substr($value['name'], 31)][$key] = $value['value'];
2359 }
2360 }
2361
2362 $incremental = $request['incremental'] ? apply_filters('updraftplus_prepare_incremental_run', false, $entities) : false;
2363
2364 // The call to backup_time_nonce() allows us to know the nonce in advance, and return it
2365 $nonce = $updraftplus->backup_time_nonce();
2366
2367 $msg = array(
2368 'nonce' => $nonce,
2369 'm' => apply_filters('updraftplus_backupnow_start_message', '<strong>'.__('Start backup', 'updraftplus').':</strong> '.htmlspecialchars(__('OK. You should soon see activity in the "Last log message" field below.', 'updraftplus')), $nonce)
2370 );
2371
2372 if (!empty($request['backup_nonce']) && 'current' != $request['backup_nonce']) $msg['nonce'] = $request['backup_nonce'];
2373
2374 if (!empty($request['incremental']) && !$incremental) {
2375 $msg = array(
2376 'error' => __('No suitable backup set (that already contains a full backup of all the requested file component types) was found, to add increments to. Aborting this backup.', 'updraftplus')
2377 );
2378 $abort_before_booting = true;
2379 }
2380
2381 if ($close_connection_callable && is_callable($close_connection_callable)) {
2382 call_user_func($close_connection_callable, $msg);
2383 } else {
2384 $updraftplus->close_browser_connection(json_encode($msg));
2385 }
2386
2387 if ($abort_before_booting) die;
2388
2389 $options = array('nocloud' => $backupnow_nocloud, 'use_nonce' => $nonce);
2390 if (!empty($request['onlythisfileentity']) && is_string($request['onlythisfileentity'])) {
2391 // Something to see in the 'last log' field when it first appears, before the backup actually starts
2392 $updraftplus->log(__('Start backup', 'updraftplus'));
2393 $options['restrict_files_to_override'] = explode(',', $request['onlythisfileentity']);
2394 }
2395
2396 if ($request['incremental'] && !$incremental) {
2397 $updraftplus->log('An incremental backup was requested but no suitable backup found to add increments to; will proceed with a new backup');
2398 $request['incremental'] = false;
2399 }
2400
2401 if (!empty($request['extradata'])) $options['extradata'] = $request['extradata'];
2402
2403 if (!empty($remote_storage_instances)) $options['remote_storage_instances'] = $remote_storage_instances;
2404
2405 $options['always_keep'] = !empty($request['always_keep']);
2406
2407 $event = empty($request['backupnow_nofiles']) ? (empty($request['backupnow_nodb']) ? 'updraft_backupnow_backup_all' : 'updraft_backupnow_backup') : 'updraft_backupnow_backup_database';
2408
2409 do_action($event, apply_filters('updraft_backupnow_options', $options, $request));
2410 }
2411
2412 /**
2413 * Get the contents of a log file
2414 *
2415 * @param String $backup_nonce - the backup id; or empty, for the most recently modified
2416 * @param Integer $log_pointer - the byte count to fetch from
2417 * @param String $output_format - the format to return in; allowed as 'html' (which will escape HTML entities in what is returned) and 'raw'
2418 *
2419 * @return String
2420 */
2421 public function fetch_log($backup_nonce = '', $log_pointer = 0, $output_format = 'html') {
2422 global $updraftplus;
2423
2424 if (empty($backup_nonce)) {
2425 list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
2426 } else {
2427 $nonce = $backup_nonce;
2428 }
2429
2430 if (!preg_match('/^[0-9a-f]+$/', $nonce)) die('Security check');
2431
2432 $log_content = '';
2433 $new_pointer = $log_pointer;
2434
2435 if (!empty($nonce)) {
2436 $updraft_dir = $updraftplus->backups_dir_location();
2437
2438 $potential_log_file = $updraft_dir."/log.".$nonce.".txt";
2439
2440 if (is_readable($potential_log_file)) {
2441
2442 $templog_array = array();
2443 $log_file = fopen($potential_log_file, "r");
2444 if ($log_pointer > 0) fseek($log_file, $log_pointer);
2445
2446 while (($buffer = fgets($log_file, 4096)) !== false) {
2447 $templog_array[] = $buffer;
2448 }
2449 if (!feof($log_file)) {
2450 $templog_array[] = __('Error: unexpected file read fail', 'updraftplus');
2451 }
2452
2453 $new_pointer = ftell($log_file);
2454 $log_content = implode("", $templog_array);
2455
2456
2457 } else {
2458 $log_content .= __('The log file could not be read.', 'updraftplus');
2459 }
2460
2461 } else {
2462 $log_content .= __('The log file could not be read.', 'updraftplus');
2463 }
2464
2465 if ('html' == $output_format) $log_content = htmlspecialchars($log_content);
2466
2467 $ret_array = array(
2468 'log' => $log_content,
2469 'nonce' => $nonce,
2470 'pointer' => $new_pointer
2471 );
2472
2473 return $ret_array;
2474 }
2475
2476 /**
2477 * Get a count for the number of overdue cron jobs
2478 *
2479 * @return Integer - how many cron jobs are overdue
2480 */
2481 public function howmany_overdue_crons() {
2482 $how_many_overdue = 0;
2483 if (function_exists('_get_cron_array') || (is_file(ABSPATH.WPINC.'/cron.php') && include_once(ABSPATH.WPINC.'/cron.php') && function_exists('_get_cron_array'))) {
2484 $crons = _get_cron_array();
2485 if (is_array($crons)) {
2486 $timenow = time();
2487 foreach ($crons as $jt => $job) {
2488 if ($jt < $timenow) $how_many_overdue++;
2489 }
2490 }
2491 }
2492 return $how_many_overdue;
2493 }
2494
2495 public function get_php_errors($errno, $errstr, $errfile, $errline) {
2496 global $updraftplus;
2497 if (0 == error_reporting()) return true;
2498 $logline = $updraftplus->php_error_to_logline($errno, $errstr, $errfile, $errline);
2499 if (false !== $logline) $this->logged[] = $logline;
2500 // Don't pass it up the chain (since it's going to be output to the user always)
2501 return true;
2502 }
2503
2504 private function download_status($timestamp, $type, $findex) {
2505 global $updraftplus;
2506 $response = array('m' => $updraftplus->jobdata_get('dlmessage_'.$timestamp.'_'.$type.'_'.$findex).'<br>');
2507 if ($file = $updraftplus->jobdata_get('dlfile_'.$timestamp.'_'.$type.'_'.$findex)) {
2508 if ('failed' == $file) {
2509 $response['e'] = __('Download failed', 'updraftplus').'<br>';
2510 $response['failed'] = true;
2511 $errs = $updraftplus->jobdata_get('dlerrors_'.$timestamp.'_'.$type.'_'.$findex);
2512 if (is_array($errs) && !empty($errs)) {
2513 $response['e'] .= '<ul class="disc">';
2514 foreach ($errs as $err) {
2515 if (is_array($err)) {
2516 $response['e'] .= '<li>'.htmlspecialchars($err['message']).'</li>';
2517 } else {
2518 $response['e'] .= '<li>'.htmlspecialchars($err).'</li>';
2519 }
2520 }
2521 $response['e'] .= '</ul>';
2522 }
2523 } elseif (preg_match('/^downloaded:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) {
2524 $response['p'] = 100;
2525 $response['f'] = $matches[2];
2526 $response['s'] = (int) $matches[1];
2527 $response['t'] = (int) $matches[1];
2528 $response['m'] = __('File ready.', 'updraftplus');
2529 if ('db' != substr($type, 0, 2)) $response['can_show_contents'] = true;
2530 } elseif (preg_match('/^downloading:(\d+):(.*)$/', $file, $matches) && file_exists($matches[2])) {
2531 // Convert to bytes
2532 $response['f'] = $matches[2];
2533 $total_size = (int) max($matches[1], 1);
2534 $cur_size = filesize($matches[2]);
2535 $response['s'] = $cur_size;
2536 $file_age = time() - filemtime($matches[2]);
2537 if ($file_age > 20) $response['a'] = time() - filemtime($matches[2]);
2538 $response['t'] = $total_size;
2539 $response['m'] .= __("Download in progress", 'updraftplus').' ('.round($cur_size/1024).' / '.round(($total_size/1024)).' KB)';
2540 $response['p'] = round(100*$cur_size/$total_size);
2541 } else {
2542 $response['m'] .= __('No local copy present.', 'updraftplus');
2543 $response['p'] = 0;
2544 $response['s'] = 0;
2545 $response['t'] = 1;
2546 }
2547 }
2548 return $response;
2549 }
2550
2551 /**
2552 * Used with the WP filter upload_dir to adjust where uploads go to when uploading a backup
2553 *
2554 * @param Array $uploads - pre-filter array
2555 *
2556 * @return Array - filtered array
2557 */
2558 public function upload_dir($uploads) {
2559 global $updraftplus;
2560 $updraft_dir = $updraftplus->backups_dir_location();
2561 if (is_writable($updraft_dir)) $uploads['path'] = $updraft_dir;
2562 return $uploads;
2563 }
2564
2565 /**
2566 * We do actually want to over-write
2567 *
2568 * @param String $dir Directory
2569 * @param String $name Name
2570 * @param String $ext File extension
2571 *
2572 * @return String
2573 */
2574 public function unique_filename_callback($dir, $name, $ext) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
2575 return $name.$ext;
2576 }
2577
2578 public function sanitize_file_name($filename) {
2579 // WordPress 3.4.2 on multisite (at least) adds in an unwanted underscore
2580 return preg_replace('/-db(.*)\.gz_\.crypt$/', '-db$1.gz.crypt', $filename);
2581 }
2582
2583 /**
2584 * Runs upon the WordPress action plupload_action
2585 */
2586 public function plupload_action() {
2587
2588 global $updraftplus;
2589 if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2590
2591 if (!UpdraftPlus_Options::user_can_manage()) return;
2592 check_ajax_referer('updraft-uploader');
2593
2594 $updraft_dir = $updraftplus->backups_dir_location();
2595 if (!@UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2596 echo json_encode(array('e' => sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $updraft_dir).' '.__('You will find more information about this in the Settings section.', 'updraftplus')));
2597 exit;
2598 }
2599
2600 add_filter('upload_dir', array($this, 'upload_dir'));
2601 add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
2602 // handle file upload
2603
2604 $farray = array('test_form' => true, 'action' => 'plupload_action');
2605
2606 $farray['test_type'] = false;
2607 $farray['ext'] = 'x-gzip';
2608 $farray['type'] = 'application/octet-stream';
2609
2610 if (!isset($_POST['chunks'])) {
2611 $farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
2612 }
2613
2614 $status = wp_handle_upload(
2615 $_FILES['async-upload'],
2616 $farray
2617 );
2618 remove_filter('upload_dir', array($this, 'upload_dir'));
2619 remove_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
2620
2621 if (isset($status['error'])) {
2622 echo json_encode(array('e' => $status['error']));
2623 exit;
2624 }
2625
2626 // If this was the chunk, then we should instead be concatenating onto the final file
2627 if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
2628
2629 $final_file = basename($_POST['name']);
2630
2631 if (!rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp')) {
2632 @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2633 echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('This file could not be uploaded', 'updraftplus'))));
2634 exit;
2635 }
2636
2637 $status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
2638
2639 }
2640
2641 $response = array();
2642 if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1) && isset($final_file)) {
2643 if (!preg_match('/^log\.[a-f0-9]{12}\.txt/i', $final_file) && !preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-([\-a-z]+)([0-9]+)?(\.(zip|gz|gz\.crypt))?$/i', $final_file, $matches)) {
2644 $accept = apply_filters('updraftplus_accept_archivename', array());
2645 if (is_array($accept)) {
2646 foreach ($accept as $acc) {
2647 if (preg_match('/'.$acc['pattern'].'/i', $final_file)) {
2648 $response['dm'] = sprintf(__('This backup was created by %s, and can be imported.', 'updraftplus'), $acc['desc']);
2649 }
2650 }
2651 }
2652 if (empty($response['dm'])) {
2653 if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2654 echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), __('Bad filename format - this does not look like a file created by UpdraftPlus', 'updraftplus'))));
2655 exit;
2656 }
2657 } else {
2658 $backupable_entities = $updraftplus->get_backupable_file_entities(true);
2659 $type = isset($matches[3]) ? $matches[3] : '';
2660 if (!preg_match('/^log\.[a-f0-9]{12}\.txt/', $final_file) && 'db' != $type && !isset($backupable_entities[$type])) {
2661 if (isset($status['file'])) @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2662 echo json_encode(array('e' => sprintf(__('Error: %s', 'updraftplus'), sprintf(__('This looks like a file created by UpdraftPlus, but this install does not know about this type of object: %s. Perhaps you need to install an add-on?', 'updraftplus'), htmlspecialchars($type)))));
2663 exit;
2664 }
2665 }
2666
2667 // Final chunk? If so, then stich it all back together
2668 if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && !empty($final_file)) {
2669 if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) {
2670 for ($i = 0; $i < $_POST['chunks']; $i++) {
2671 $rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
2672 if ($rh = fopen($rf, 'rb+')) {
2673
2674 // April 1st 2020 - Due to a bug during uploads to Dropbox some backups had string "null" appended to the end which caused warnings, this removes the string "null" from these backups
2675 fseek($rh, -4, SEEK_END);
2676 $data = fgets($rh, 5);
2677
2678 if ("null" === $data) {
2679 ftruncate($rh, filesize($rf) - 4);
2680 }
2681
2682 fseek($rh, 0, SEEK_SET);
2683
2684 while ($line = fread($rh, 262144)) {
2685 fwrite($wh, $line);
2686 }
2687 fclose($rh);
2688 @unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2689 }
2690 }
2691 fclose($wh);
2692 $status['file'] = $updraft_dir.'/'.$final_file;
2693 if ('.tar' == substr($final_file, -4, 4)) {
2694 if (file_exists($status['file'].'.gz')) unlink($status['file'].'.gz');
2695 if (file_exists($status['file'].'.bz2')) unlink($status['file'].'.bz2');
2696 } elseif ('.tar.gz' == substr($final_file, -7, 7)) {
2697 if (file_exists(substr($status['file'], 0, strlen($status['file'])-3))) unlink(substr($status['file'], 0, strlen($status['file'])-3));
2698 if (file_exists(substr($status['file'], 0, strlen($status['file'])-3).'.bz2')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.bz2');
2699 } elseif ('.tar.bz2' == substr($final_file, -8, 8)) {
2700 if (file_exists(substr($status['file'], 0, strlen($status['file'])-4))) unlink(substr($status['file'], 0, strlen($status['file'])-4));
2701 if (file_exists(substr($status['file'], 0, strlen($status['file'])-4).'.gz')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.gz');
2702 }
2703 }
2704 }
2705
2706 }
2707
2708 // send the uploaded file url in response
2709 $response['m'] = $status['url'];
2710 echo json_encode($response);
2711 exit;
2712 }
2713
2714 /**
2715 * Database decrypter - runs upon the WP action plupload_action2
2716 */
2717 public function plupload_action2() {
2718
2719 if (function_exists('set_time_limit')) @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2720 global $updraftplus;
2721
2722 if (!UpdraftPlus_Options::user_can_manage()) return;
2723 check_ajax_referer('updraft-uploader');
2724
2725 $updraft_dir = $updraftplus->backups_dir_location();
2726 if (!is_writable($updraft_dir)) exit;
2727
2728 add_filter('upload_dir', array($this, 'upload_dir'));
2729 add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
2730 // handle file upload
2731
2732 $farray = array('test_form' => true, 'action' => 'plupload_action2');
2733
2734 $farray['test_type'] = false;
2735 $farray['ext'] = 'crypt';
2736 $farray['type'] = 'application/octet-stream';
2737
2738 if (isset($_POST['chunks'])) {
2739 // $farray['ext'] = 'zip';
2740 // $farray['type'] = 'application/zip';
2741 } else {
2742 $farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
2743 }
2744
2745 $status = wp_handle_upload(
2746 $_FILES['async-upload'],
2747 $farray
2748 );
2749 remove_filter('upload_dir', array($this, 'upload_dir'));
2750 remove_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
2751
2752 if (isset($status['error'])) die('ERROR: '.$status['error']);
2753
2754 // If this was the chunk, then we should instead be concatenating onto the final file
2755 if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
2756 $final_file = basename($_POST['name']);
2757 rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp');
2758 $status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
2759 }
2760
2761 if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) {
2762 if (!preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?\.(gz\.crypt)$/i', $final_file)) {
2763
2764 @unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2765 echo 'ERROR:'.__('Bad filename format - this does not look like an encrypted database file created by UpdraftPlus', 'updraftplus');
2766 exit;
2767 }
2768
2769 // Final chunk? If so, then stich it all back together
2770 if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && isset($final_file)) {
2771 if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) {
2772 for ($i=0; $i<$_POST['chunks']; $i++) {
2773 $rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
2774 if ($rh = fopen($rf, 'rb')) {
2775 while ($line = fread($rh, 32768)) {
2776 fwrite($wh, $line);
2777 }
2778 fclose($rh);
2779 @unlink($rf);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2780 }
2781 }
2782 fclose($wh);
2783 }
2784 }
2785
2786 }
2787
2788 // send the uploaded file url in response
2789 if (isset($final_file)) echo 'OK:'.$final_file;
2790 exit;
2791 }
2792
2793 /**
2794 * Show footer review message and link.
2795 *
2796 * @return string
2797 */
2798 public function display_footer_review_message() {
2799 $message = sprintf(
2800 __('Enjoyed %s? Please leave us a %s rating. We really appreciate your support!', 'updraftplus'),
2801 '<b>UpdraftPlus</b>',
2802 '<a href="https://www.g2.com/products/updraftplus/reviews" target="_blank">&starf;&starf;&starf;&starf;&starf;</a>'
2803 );
2804 return $message;
2805 }
2806
2807 /**
2808 * Include the settings header template
2809 */
2810 public function settings_header() {
2811 $this->include_template('wp-admin/settings/header.php');
2812 }
2813
2814 /**
2815 * Include the settings footer template
2816 */
2817 public function settings_footer() {
2818 $this->include_template('wp-admin/settings/footer.php');
2819 }
2820
2821 /**
2822 * Output the settings page content. Will also run a restore if $_REQUEST so indicates.
2823 */
2824 public function settings_output() {
2825
2826 if (false == ($render = apply_filters('updraftplus_settings_page_render', true))) {
2827 do_action('updraftplus_settings_page_render_abort', $render);
2828 return;
2829 }
2830
2831 do_action('updraftplus_settings_page_init');
2832
2833 global $updraftplus;
2834
2835 /**
2836 * We use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credential for the WP_Filesystem. to do this WP outputs a form, but we don't pass our parameters via that. So the values are passed back in as GET parameters.
2837 */
2838 if (isset($_REQUEST['action']) && (('updraft_restore' == $_REQUEST['action'] && isset($_REQUEST['backup_timestamp'])) || ('updraft_restore_continue' == $_REQUEST['action'] && !empty($_REQUEST['job_id'])))) {
2839 $this->prepare_restore();
2840 return;
2841 }
2842
2843 if (isset($_REQUEST['action']) && 'updraft_delete_old_dirs' == $_REQUEST['action']) {
2844 $nonce = empty($_REQUEST['updraft_delete_old_dirs_nonce']) ? '' : $_REQUEST['updraft_delete_old_dirs_nonce'];
2845 if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
2846 $this->delete_old_dirs_go();
2847 return;
2848 }
2849
2850 if (!empty($_REQUEST['action']) && 'updraftplus_broadcastaction' == $_REQUEST['action'] && !empty($_REQUEST['subaction'])) {
2851 $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
2852 if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
2853 do_action($_REQUEST['subaction']);
2854 return;
2855 }
2856
2857 if (isset($_GET['error'])) {
2858 // This is used by Microsoft OneDrive authorisation failures (May 15). I am not sure what may have been using the 'error' GET parameter otherwise - but it is harmless.
2859 if (!empty($_GET['error_description'])) {
2860 $this->show_admin_warning(htmlspecialchars($_GET['error_description']).' ('.htmlspecialchars($_GET['error']).')', 'error');
2861 } else {
2862 $this->show_admin_warning(htmlspecialchars($_GET['error']), 'error');
2863 }
2864 }
2865
2866 if (isset($_GET['message'])) $this->show_admin_warning(htmlspecialchars($_GET['message']));
2867
2868 if (isset($_GET['action']) && 'updraft_create_backup_dir' == $_GET['action'] && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'create_backup_dir')) {
2869 $created = $this->create_backup_dir();
2870 if (is_wp_error($created)) {
2871 echo '<p>'.__('Backup directory could not be created', 'updraftplus').'...<br>';
2872 echo '<ul class="disc">';
2873 foreach ($created->get_error_messages() as $msg) {
2874 echo '<li>'.htmlspecialchars($msg).'</li>';
2875 }
2876 echo '</ul></p>';
2877 } elseif (false !== $created) {
2878 echo '<p>'.__('Backup directory successfully created.', 'updraftplus').'</p><br>';
2879 }
2880 echo '<b>'.__('Actions', 'updraftplus').':</b> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus">'.__('Return to UpdraftPlus configuration', 'updraftplus').'</a>';
2881 return;
2882 }
2883
2884 if (substr($updraftplus->version, 0, 1) === '2') {
2885 /**
2886 * Add filter for display footer review message and link.
2887 */
2888 add_filter('admin_footer_text', array($this, 'display_footer_review_message'));
2889 }
2890
2891 echo '<div id="updraft_backup_started" class="updated updraft-hidden" style="display:none;"></div>';
2892
2893 // This opens a div
2894 $this->settings_header();
2895 ?>
2896
2897 <div id="updraft-hidethis">
2898 <p>
2899 <strong><?php _e('Warning:', 'updraftplus'); ?> <?php _e("If you can still read these words after the page finishes loading, then there is a JavaScript or jQuery problem in the site.", 'updraftplus'); ?></strong>
2900
2901 <?php if (false !== strpos(basename(UPDRAFTPLUS_URL), ' ')) { ?>
2902 <strong><?php _e('The UpdraftPlus directory in wp-content/plugins has white-space in it; WordPress does not like this. You should rename the directory to wp-content/plugins/updraftplus to fix this problem.', 'updraftplus');?></strong>
2903 <?php } else { ?>
2904 <a href="<?php echo apply_filters('updraftplus_com_link', "https://updraftplus.com/do-you-have-a-javascript-or-jquery-error/");?>" target="_blank"><?php _e('Go here for more information.', 'updraftplus'); ?></a>
2905 <?php } ?>
2906 </p>
2907 </div>
2908
2909 <?php
2910
2911 $include_deleteform_div = true;
2912
2913 // Opens a div, which needs closing later
2914 if (isset($_GET['updraft_restore_success'])) {
2915
2916 if (get_template() === 'optimizePressTheme' || is_plugin_active('optimizePressPlugin') || is_plugin_active_for_network('optimizePressPlugin')) {
2917 $this->show_admin_warning("<a href='https://optimizepress.zendesk.com/hc/en-us/articles/203699826-Update-URL-References-after-moving-domain' target='_blank'>" . __("OptimizePress 2.0 encodes its contents, so search/replace does not work.", "updraftplus") . ' ' . __("To fix this problem go here.", "updraftplus") . "</a>", "notice notice-warning");
2918 }
2919 $success_advert = (isset($_GET['pval']) && 0 == $_GET['pval'] && !$updraftplus->have_addons) ? '<p>'.__('For even more features and personal support, check out ', 'updraftplus').'<strong><a href="'.$updraftplus->get_url('premium').'" target="_blank">UpdraftPlus Premium</a>.</strong></p>' : "";
2920
2921 echo "<div class=\"updated backup-restored\"><span><strong>".__('Your backup has been restored.', 'updraftplus').'</strong></span><br>';
2922 // Unnecessary - will be advised of this below
2923 // if (2 == $_GET['updraft_restore_success']) echo ' '.__('Your old (themes, uploads, plugins, whatever) directories have been retained with "-old" appended to their name. Remove them when you are satisfied that the backup worked properly.');
2924 echo $success_advert;
2925 $include_deleteform_div = false;
2926
2927 }
2928
2929 if ($this->scan_old_dirs(true)) $this->print_delete_old_dirs_form(true, $include_deleteform_div);
2930
2931 // Close the div opened by the earlier section
2932 if (isset($_GET['updraft_restore_success'])) echo '</div>';
2933
2934 if (empty($success_advert) && empty($this->no_settings_warning)) {
2935
2936 if (!class_exists('UpdraftPlus_Notices')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftplus-notices.php');
2937 global $updraftplus_notices;
2938
2939 $backup_history = UpdraftPlus_Backup_History::get_history();
2940 $review_dismiss = UpdraftPlus_Options::get_updraft_option('dismissed_review_notice', 0);
2941 $backup_dir = $updraftplus->backups_dir_location();
2942 // N.B. Not an exact proxy for the installed time; they may have tweaked the expert option to move the directory
2943 $installed = @filemtime($backup_dir.'/index.html');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2944 $installed_for = time() - $installed;
2945
2946 $advert = false;
2947 if (!empty($backup_history) && $installed && time() > $review_dismiss && $installed_for > 28*86400 && $installed_for < 84*86400) {
2948 $advert = 'rate';
2949 }
2950
2951 $updraftplus_notices->do_notice($advert);
2952 }
2953
2954 if (!$updraftplus->memory_check(64)) {
2955 // HS8390 - A case where UpdraftPlus::memory_check_current() returns -1
2956 $memory_check_current = $updraftplus->memory_check_current();
2957 if ($memory_check_current > 0) {
2958 ?>
2959 <div class="updated memory-limit"><?php _e('Your PHP memory limit (set by your web hosting company) is very low. UpdraftPlus attempted to raise it but was unsuccessful. This plugin may struggle with a memory limit of less than 64 Mb - especially if you have very large files uploaded (though on the other hand, many sites will be successful with a 32Mb limit - your experience may vary).', 'updraftplus');?> <?php _e('Current limit is:', 'updraftplus');?> <?php echo $updraftplus->memory_check_current(); ?> MB</div>
2960 <?php }
2961 }
2962
2963
2964 if (!empty($updraftplus->errors)) {
2965 echo '<div class="error updraft_list_errors">';
2966 $updraftplus->list_errors();
2967 echo '</div>';
2968 }
2969
2970 $backup_history = UpdraftPlus_Backup_History::get_history();
2971 if (empty($backup_history)) {
2972 UpdraftPlus_Backup_History::rebuild();
2973 $backup_history = UpdraftPlus_Backup_History::get_history();
2974 }
2975
2976 $tabflag = 'backups';
2977 $main_tabs = $this->get_main_tabs_array();
2978
2979 if (isset($_REQUEST['tab'])) {
2980 $request_tab = sanitize_text_field($_REQUEST['tab']);
2981 $valid_tabflags = array_keys($main_tabs);
2982 if (in_array($request_tab, $valid_tabflags)) {
2983 $tabflag = $request_tab;
2984 } else {
2985 $tabflag = 'backups';
2986 }
2987 }
2988
2989 $this->include_template('wp-admin/settings/tab-bar.php', false, array('main_tabs' => $main_tabs, 'backup_history' => $backup_history, 'tabflag' => $tabflag));
2990 ?>
2991
2992 <div id="updraft-poplog" >
2993 <pre id="updraft-poplog-content"></pre>
2994 </div>
2995
2996 <?php
2997 $this->include_template('wp-admin/settings/delete-and-restore-modals.php');
2998 ?>
2999
3000 <div id="updraft-navtab-backups-content" <?php if ('backups' != $tabflag) echo 'class="updraft-hidden"'; ?> style="<?php if ('backups' != $tabflag) echo 'display:none;'; ?>">
3001 <?php
3002 $is_opera = (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') || false !== strpos($_SERVER['HTTP_USER_AGENT'], 'OPR/'));
3003 $tmp_opts = array('include_opera_warning' => $is_opera);
3004 $this->include_template('wp-admin/settings/tab-backups.php', false, array('backup_history' => $backup_history, 'options' => $tmp_opts));
3005 $this->include_template('wp-admin/settings/upload-backups-modal.php');
3006 ?>
3007 </div>
3008
3009 <div id="updraft-navtab-migrate-content"<?php if ('migrate' != $tabflag) echo ' class="updraft-hidden"'; ?> style="<?php if ('migrate' != $tabflag) echo 'display:none;'; ?>">
3010 <?php
3011 if (has_action('updraftplus_migrate_tab_output')) {
3012 do_action('updraftplus_migrate_tab_output');
3013 } else {
3014 $this->include_template('wp-admin/settings/migrator-no-migrator.php');
3015 }
3016 ?>
3017 </div>
3018
3019 <div id="updraft-navtab-settings-content" <?php if ('settings' != $tabflag) echo 'class="updraft-hidden"'; ?> style="<?php if ('settings' != $tabflag) echo 'display:none;'; ?>">
3020 <h2 class="updraft_settings_sectionheading"><?php _e('Backup Contents And Schedule', 'updraftplus');?></h2>
3021 <?php UpdraftPlus_Options::options_form_begin(); ?>
3022 <?php $this->settings_formcontents(); ?>
3023 </form>
3024 <?php
3025 $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
3026 if (!is_array($our_keys)) $our_keys = array();
3027
3028 // Hide the UpdraftCentral Cloud wizard If the user already has a key created for either
3029 // updraftplus.com or self hosted version.
3030 if (empty($our_keys)) {
3031 ?>
3032 <div id="updraftcentral_cloud_connect_container" class="updraftcentral_cloud_connect hidden-in-updraftcentral">
3033 <?php
3034
3035 $email = '';
3036
3037 // Checking email from "Premium / Extensions" tab
3038 if (defined('UDADDONS2_SLUG')) {
3039 global $updraftplus_addons2;
3040
3041 if (is_a($updraftplus_addons2, 'UpdraftPlusAddons2') && is_callable(array($updraftplus_addons2, 'get_option'))) {
3042 $options = $updraftplus_addons2->get_option(UDADDONS2_SLUG.'_options');
3043
3044 if (!empty($options['email'])) {
3045 $email = htmlspecialchars($options['email']);
3046 }
3047 }
3048 }
3049
3050 // Check the vault's email if we fail to get the "email" from the "Premium / Extensions" tab
3051 if (empty($email)) {
3052 $settings = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('updraftvault');
3053 if (!is_wp_error($settings)) {
3054 if (!empty($settings['settings'])) {
3055 foreach ($settings['settings'] as $storage_options) {
3056 if (!empty($storage_options['email'])) {
3057 $email = $storage_options['email'];
3058 break;
3059 }
3060 }
3061 }
3062 }
3063 }
3064
3065 // Checking any possible email we could find from the "updraft_email" option in case the
3066 // above two checks failed.
3067 if (empty($email)) {
3068 $possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'));
3069 if (!empty($possible_emails)) {
3070 // If we get an array from the 'just_one_email' result then we're going
3071 // to pull the very first entry and make use of that on the succeeding process.
3072 if (is_array($possible_emails)) $possible_emails = array_shift($possible_emails);
3073
3074 if (is_string($possible_emails)) {
3075 $emails = explode(',', $possible_emails);
3076 $email = trim($emails[0]);
3077 }
3078 }
3079 }
3080
3081 $this->include_template('wp-admin/settings/updraftcentral-connect.php', false, array('email' => $email));
3082 ?>
3083 </div>
3084 <?php
3085 }
3086 ?>
3087 </div>
3088
3089 <div id="updraft-navtab-expert-content"<?php if ('expert' != $tabflag) echo ' class="updraft-hidden"'; ?> style="<?php if ('expert' != $tabflag) echo 'display:none;'; ?>">
3090 <?php $this->settings_advanced_tools(); ?>
3091 </div>
3092
3093 <div id="updraft-navtab-addons-content"<?php if ('addons' != $tabflag) echo ' class="updraft-hidden"'; ?> style="<?php if ('addons' != $tabflag) echo 'display:none;'; ?>">
3094
3095 <?php
3096 $tab_addons = $this->include_template('wp-admin/settings/tab-addons.php', true, array('tabflag' => $tabflag));
3097
3098 echo apply_filters('updraftplus_addonstab_content', $tab_addons);
3099
3100 ?>
3101
3102 </div>
3103
3104 <?php
3105 do_action('updraftplus_after_main_tab_content', $tabflag);
3106 // settings_header() opens a div
3107 $this->settings_footer();
3108 }
3109
3110 /**
3111 * Get main tabs array
3112 *
3113 * @return Array Array which have key as a tab key and value as tab label
3114 */
3115 private function get_main_tabs_array() {
3116 return apply_filters(
3117 'updraftplus_main_tabs',
3118 array(
3119 'backups' => __('Backup / Restore', 'updraftplus'),
3120 'migrate' => __('Migrate / Clone', 'updraftplus'),
3121 'settings' => __('Settings', 'updraftplus'),
3122 'expert' => __('Advanced Tools', 'updraftplus'),
3123 'addons' => __('Premium / Extensions', 'updraftplus'),
3124 )
3125 );
3126 }
3127
3128 /**
3129 * Potentially register an action for showing restore progress
3130 */
3131 private function print_restore_in_progress_box_if_needed() {
3132 global $updraftplus;
3133 $check_restore_progress = $updraftplus->check_restore_progress();
3134 // Check to see if the restore is still in progress
3135 if (is_array($check_restore_progress) && true == $check_restore_progress['status']) {
3136
3137 $restore_jobdata = $check_restore_progress['restore_jobdata'];
3138 $restore_jobdata['jobid'] = $check_restore_progress['restore_in_progress'];
3139 $this->restore_in_progress_jobdata = $restore_jobdata;
3140
3141 add_action('all_admin_notices', array($this, 'show_admin_restore_in_progress_notice'));
3142 }
3143 }
3144
3145 /**
3146 * This function is called via the command class, it will get the resume restore notice to be shown when a restore is taking place over AJAX
3147 *
3148 * @param string $job_id - the id of the job
3149 *
3150 * @return WP_Error|string - can return a string containing html or a WP_Error
3151 */
3152 public function get_restore_resume_notice($job_id) {
3153 global $updraftplus;
3154
3155 if (empty($job_id)) return new WP_Error('missing_parameter', 'Missing parameters.');
3156
3157 $restore_jobdata = $updraftplus->jobdata_getarray($job_id);
3158
3159 if (!is_array($restore_jobdata) && empty($restore_jobdata)) return new WP_Error('missing_jobdata', 'Job data not found.');
3160
3161 $restore_jobdata['jobid'] = $job_id;
3162 $this->restore_in_progress_jobdata = $restore_jobdata;
3163
3164 $html = $this->show_admin_restore_in_progress_notice(true, true);
3165
3166 if (empty($html)) return new WP_Error('job_aborted', 'Job aborted.');
3167
3168 return $html;
3169 }
3170
3171 /**
3172 * If added, then runs upon the WP action all_admin_notices, or can be called via get_restore_resume_notice() for when a restore is running over AJAX
3173 *
3174 * @param Boolean $return_instead_of_echo - indicates if we want to add the tfa UI
3175 * @param Boolean $exclude_js - indicates if we want to exclude the js in the returned html
3176 *
3177 * @return void|string - can return a string containing html or echo the html to page
3178 */
3179 public function show_admin_restore_in_progress_notice($return_instead_of_echo = false, $exclude_js = false) {
3180
3181 if (isset($_REQUEST['action']) && 'updraft_restore_abort' === $_REQUEST['action'] && !empty($_REQUEST['job_id'])) {
3182 delete_site_option('updraft_restore_in_progress');
3183 return;
3184 }
3185
3186 $restore_jobdata = $this->restore_in_progress_jobdata;
3187 $seconds_ago = time() - (int) $restore_jobdata['job_time_ms'];
3188 $minutes_ago = floor($seconds_ago/60);
3189 $seconds_ago = $seconds_ago - $minutes_ago*60;
3190 $time_ago = sprintf(__("%s minutes, %s seconds", 'updraftplus'), $minutes_ago, $seconds_ago);
3191
3192 $html = '<div class="updated show_admin_restore_in_progress_notice">';
3193 $html .= '<span class="unfinished-restoration"><strong>UpdraftPlus: '.__('Unfinished restoration', 'updraftplus').'</strong></span><br>';
3194 $html .= '<p>'.sprintf(__('You have an unfinished restoration operation, begun %s ago.', 'updraftplus'), $time_ago).'</p>';
3195 $html .= '<form method="post" action="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus">';
3196 $html .= wp_nonce_field('updraftplus-credentialtest-nonce');
3197 $html .= '<input id="updraft_restore_continue_action" type="hidden" name="action" value="updraft_restore_continue">';
3198 $html .= '<input type="hidden" name="updraftplus_ajax_restore" value="continue_ajax_restore">';
3199 $html .= '<input type="hidden" name="job_id" value="'.$restore_jobdata['jobid'].'" value="'.esc_attr($restore_jobdata['jobid']).'">';
3200
3201 if ($exclude_js) {
3202 $html .= '<button id="updraft_restore_resume" type="submit" class="button-primary">'.__('Continue restoration', 'updraftplus').'</button>';
3203 } else {
3204 $html .= '<button id="updraft_restore_resume" onclick="jQuery(\'#updraft_restore_continue_action\').val(\'updraft_restore_continue\'); jQuery(this).parent(\'form\').trigger(\'submit\');" type="submit" class="button-primary">'.__('Continue restoration', 'updraftplus').'</button>';
3205 }
3206 $html .= '<button id="updraft_restore_abort" onclick="jQuery(\'#updraft_restore_continue_action\').val(\'updraft_restore_abort\'); jQuery(this).parent(\'form\').trigger(\'submit\');" class="button-secondary">'.__('Dismiss', 'updraftplus').'</button>';
3207
3208 $html .= '</form></div>';
3209
3210 if ($return_instead_of_echo) return $html;
3211
3212 echo $html;
3213 }
3214
3215 /**
3216 * This method will build the UpdraftPlus.com login form and echo it to the page.
3217 *
3218 * @param String $option_page - the option page this form is being output to
3219 * @param Boolean $tfa - indicates if we want to add the tfa UI
3220 * @param Boolean $include_form_container - indicates if we want the form container
3221 * @param Array $further_options - other options (see below for the possibilities + defaults)
3222 *
3223 * @return void
3224 */
3225 public function build_credentials_form($option_page, $tfa = false, $include_form_container = true, $further_options = array()) {
3226
3227 global $updraftplus;
3228
3229 $further_options = wp_parse_args($further_options, array(
3230 'under_username' => __("Not yet got an account (it's free)? Go get one!", 'updraftplus'),
3231 'under_username_link' => $updraftplus->get_url('my-account')
3232 ));
3233
3234 if ($include_form_container) {
3235 $enter_credentials_begin = UpdraftPlus_Options::options_form_begin('', false, array(), 'updraftplus_com_login');
3236 if (is_multisite()) $enter_credentials_begin .= '<input type="hidden" name="action" value="update">';
3237 } else {
3238 $enter_credentials_begin = '<div class="updraftplus_com_login">';
3239 }
3240
3241 $interested = htmlspecialchars(__('Interested in knowing about your UpdraftPlus.Com password security? Read about it here.', 'updraftplus'));
3242
3243 $connect = htmlspecialchars(__('Connect', 'updraftplus'));
3244
3245 $enter_credentials_end = '<p class="updraft-after-form-table">';
3246
3247 if ($include_form_container) {
3248 $enter_credentials_end .= '<input type="submit" class="button-primary ud_connectsubmit" value="'.$connect.'" />';
3249 } else {
3250 $enter_credentials_end .= '<button class="button-primary ud_connectsubmit">'.$connect.'</button>';
3251 }
3252
3253 $enter_credentials_end .= '<span class="updraftplus_spinner spinner">' . __('Processing', 'updraftplus') . '...</span></p>';
3254
3255 $enter_credentials_end .= '<p class="updraft-after-form-table" style="font-size: 70%"><em><a href="https://updraftplus.com/faqs/tell-me-about-my-updraftplus-com-account/" target="_blank">'.$interested.'</a></em></p>';
3256
3257 $enter_credentials_end .= $include_form_container ? '</form>' : '</div>';
3258
3259 echo $enter_credentials_begin;
3260
3261 $options = apply_filters('updraftplus_com_login_options', array("email" => "", "password" => ""));
3262
3263 if ($include_form_container) {
3264 // We have to duplicate settings_fields() in order to set our referer
3265 // settings_fields(UDADDONS2_SLUG.'_options');
3266
3267 $option_group = $option_page.'_options';
3268 echo "<input type='hidden' name='option_page' value='" . esc_attr($option_group) . "' />";
3269 echo '<input type="hidden" name="action" value="update" />';
3270
3271 // wp_nonce_field("$option_group-options");
3272
3273 // This one is used on multisite
3274 echo '<input type="hidden" name="tab" value="addons" />';
3275
3276 $name = "_wpnonce";
3277 $action = esc_attr($option_group."-options");
3278 $nonce_field = '<input type="hidden" name="' . $name . '" value="' . wp_create_nonce($action) . '" />';
3279
3280 echo $nonce_field;
3281
3282 $referer = esc_attr(UpdraftPlus_Manipulation_Functions::wp_unslash($_SERVER['REQUEST_URI']));
3283
3284 // This one is used on single site installs
3285 if (false === strpos($referer, '?')) {
3286 $referer .= '?tab=addons';
3287 } else {
3288 $referer .= '&tab=addons';
3289 }
3290
3291 echo '<input type="hidden" name="_wp_http_referer" value="'.$referer.'" />';
3292 // End of duplication of settings-fields()
3293 }
3294 ?>
3295
3296 <h2> <?php _e('Connect with your UpdraftPlus.Com account', 'updraftplus'); ?></h2>
3297 <p class="updraftplus_com_login_status"></p>
3298
3299 <table class="form-table">
3300 <tbody>
3301 <tr class="non_tfa_fields">
3302 <th><?php _e('Email', 'updraftplus'); ?></th>
3303 <td>
3304 <label for="<?php echo $option_page; ?>_options_email">
3305 <input id="<?php echo $option_page; ?>_options_email" type="text" size="36" name="<?php echo $option_page; ?>_options[email]" value="<?php echo htmlspecialchars($options['email']); ?>" />
3306 <br/>
3307 <a target="_blank" href="<?php echo $further_options['under_username_link']; ?>"><?php echo $further_options['under_username']; ?></a>
3308 </label>
3309 </td>
3310 </tr>
3311 <tr class="non_tfa_fields">
3312 <th><?php _e('Password', 'updraftplus'); ?></th>
3313 <td>
3314 <label for="<?php echo $option_page; ?>_options_password">
3315 <input id="<?php echo $option_page; ?>_options_password" type="password" size="36" name="<?php echo $option_page; ?>_options[password]" value="<?php echo empty($options['password']) ? '' : htmlspecialchars($options['password']); ?>" />
3316 <br/>
3317 <a target="_blank" href="<?php echo $updraftplus->get_url('lost-password'); ?>"><?php _e('Forgotten your details?', 'updraftplus'); ?></a>
3318 </label>
3319 </td>
3320 </tr>
3321 <?php
3322 if ('updraftplus-addons' == $option_page) {
3323 ?>
3324 <tr class="non_tfa_fields">
3325 <th></th>
3326 <td>
3327 <label>
3328 <input type="checkbox" id="<?php echo $option_page; ?>_options_auto_updates" data-updraft_settings_test="updraft_auto_updates" name="<?php echo $option_page; ?>_options[updraft_auto_update]" value="1" <?php if ($updraftplus->is_automatic_updating_enabled()) echo 'checked="checked"'; ?> />
3329 <?php _e('Ask WordPress to update UpdraftPlus automatically when an update is available', 'updraftplus');?>
3330 </label>
3331 <?php
3332 $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
3333 if (!is_array($our_keys)) $our_keys = array();
3334
3335 if (empty($our_keys)) :
3336 ?>
3337 <p class="<?php echo $option_page; ?>-connect-to-udc">
3338 <label>
3339 <input type="checkbox" id="<?php echo $option_page; ?>_options_auto_udc_connect" name="<?php echo $option_page; ?>_options[updraft_auto_udc_connect]" value="1" checked="checked" />
3340 <?php _e('Add this website to UpdraftCentral (remote, centralised control) - free for up to 5 sites.', 'updraftplus'); ?> <a target="_blank" href="https://updraftcentral.com"><?php _e('Learn more about UpdraftCentral', 'updraftplus'); ?></a>
3341 </label>
3342 </p>
3343
3344 <?php endif; ?>
3345
3346 </td>
3347 </tr>
3348 <?php
3349 }
3350 ?>
3351 <?php
3352 if (isset($further_options['terms_and_conditions']) && isset($further_options['terms_and_conditions_link'])) {
3353 ?>
3354 <tr class="non_tfa_fields">
3355 <th></th>
3356 <td>
3357 <input type="checkbox" class="<?php echo $option_page; ?>_terms_and_conditions" name="<?php echo $option_page; ?>_terms_and_conditions" value="1">
3358 <a target="_blank" href="<?php echo $further_options['terms_and_conditions_link']; ?>"><?php echo $further_options['terms_and_conditions']; ?></a>
3359 </td>
3360 </tr>
3361 <?php
3362 }
3363 ?>
3364 <?php if ($tfa) { ?>
3365 <tr class="tfa_fields" style="display:none;">
3366 <th><?php _e('One Time Password (check your OTP app to get this password)', 'updraftplus'); ?></th>
3367 <td>
3368 <label for="<?php echo $option_page; ?>_options_two_factor_code">
3369 <input id="<?php echo $option_page; ?>_options_two_factor_code" type="text" size="10" name="<?php echo $option_page; ?>_options[two_factor_code]" />
3370 </label>
3371 </td>
3372 </tr>
3373 <?php } ?>
3374 </tbody>
3375 </table>
3376
3377 <?php
3378
3379 echo $enter_credentials_end;
3380 }
3381
3382 /**
3383 * Return widgetry for the 'backup now' modal.
3384 * Don't optimise this method away; it's used by third-party plugins (e.g. EUM).
3385 *
3386 * @return String
3387 */
3388 public function backupnow_modal_contents() {
3389 return $this->include_template('wp-admin/settings/backupnow-modal.php', true);
3390 }
3391
3392 /**
3393 * Also used by the auto-backups add-on
3394 *
3395 * @param Boolean $wide_format Whether to return data in a wide format
3396 * @param Boolean $print_active_jobs Whether to include currently active jobs
3397 * @return String - the HTML output
3398 */
3399 public function render_active_jobs_and_log_table($wide_format = false, $print_active_jobs = true) {
3400 global $updraftplus;
3401 ?>
3402 <div id="updraft_activejobs_table">
3403 <?php $active_jobs = ($print_active_jobs) ? $this->print_active_jobs() : '';?>
3404 <div id="updraft_activejobsrow" class="<?php
3405 if (!$active_jobs && !$wide_format) {
3406 echo 'hidden';
3407 }
3408 if ($wide_format) {
3409 echo ".minimum-height";
3410 }
3411 ?>">
3412 <div id="updraft_activejobs" class="<?php echo $wide_format ? 'wide-format' : ''; ?>">
3413 <?php echo $active_jobs;?>
3414 </div>
3415 </div>
3416 <div id="updraft_lastlogmessagerow">
3417 <?php if ($wide_format) {
3418 // Hide for now - too ugly
3419 ?>
3420 <div class="last-message"><strong><?php _e('Last log message', 'updraftplus');?>:</strong><br>
3421 <span id="updraft_lastlogcontainer"><?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_lastmessage()); ?></span><br>
3422 <?php $this->most_recently_modified_log_link(); ?>
3423 </div>
3424 <?php } else { ?>
3425 <div>
3426 <strong><?php _e('Last log message', 'updraftplus');?>:</strong>
3427 <span id="updraft_lastlogcontainer"><?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_lastmessage()); ?></span><br>
3428 <?php $this->most_recently_modified_log_link(); ?>
3429 </div>
3430 <?php } ?>
3431 </div>
3432 <?php
3433 // Currently disabled - not sure who we want to show this to
3434 if (1==0 && !defined('UPDRAFTPLUS_NOADS_B')) {
3435 $feed = $updraftplus->get_updraftplus_rssfeed();
3436 if (is_a($feed, 'SimplePie')) {
3437 echo '<tr><th style="vertical-align:top;">'.__('Latest UpdraftPlus.com news:', 'updraftplus').'</th><td class="updraft_simplepie">';
3438 echo '<ul class="disc;">';
3439 foreach ($feed->get_items(0, 5) as $item) {
3440 echo '<li>';
3441 echo '<a href="'.esc_attr($item->get_permalink()).'">';
3442 echo htmlspecialchars($item->get_title());
3443 // D, F j, Y H:i
3444 echo "</a> (".htmlspecialchars($item->get_date('j F Y')).")";
3445 echo '</li>';
3446 }
3447 echo '</ul></td></tr>';
3448 }
3449 }
3450 ?>
3451 </div>
3452 <?php
3453 }
3454
3455 /**
3456 * Output directly a link allowing download of the most recently modified log file
3457 */
3458 private function most_recently_modified_log_link() {
3459
3460 global $updraftplus;
3461 list($mod_time, $log_file, $nonce) = $updraftplus->last_modified_log();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
3462
3463 ?>
3464 <a href="?page=updraftplus&amp;action=downloadlatestmodlog&amp;wpnonce=<?php echo wp_create_nonce('updraftplus_download'); ?>" <?php if (!$mod_time) echo 'style="display:none;"'; ?> class="updraft-log-link" onclick="event.preventDefault(); updraft_popuplog('');"><?php _e('Download most recently modified log file', 'updraftplus');?></a>
3465 <?php
3466 }
3467
3468 public function settings_downloading_and_restoring($backup_history = array(), $return_result = false, $options = array()) {
3469 return $this->include_template('wp-admin/settings/downloading-and-restoring.php', $return_result, array('backup_history' => $backup_history, 'options' => $options));
3470 }
3471
3472 /**
3473 * Renders take backup content
3474 */
3475 public function take_backup_content() {
3476 global $updraftplus;
3477 $updraft_dir = $updraftplus->backups_dir_location();
3478 $backup_disabled = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir) ? '' : 'disabled="disabled"';
3479 $this->include_template('wp-admin/settings/take-backup.php', false, array('backup_disabled' => $backup_disabled));
3480 }
3481
3482 /**
3483 * Output a table row using the updraft_debugrow class
3484 *
3485 * @param String $head - header cell contents
3486 * @param String $content - content cell contents
3487 */
3488 public function settings_debugrow($head, $content) {
3489 echo "<tr class=\"updraft_debugrow\"><th>$head</th><td>$content</td></tr>";
3490 }
3491
3492 public function settings_advanced_tools($return_instead_of_echo = false, $pass_through = array()) {
3493 return $this->include_template('wp-admin/advanced/advanced-tools.php', $return_instead_of_echo, $pass_through);
3494 }
3495
3496 /**
3497 * Paint the HTML for the form for deleting old directories
3498 *
3499 * @param Boolean $include_blurb - whether to include explanatory text
3500 * @param Boolean $include_div - whether to wrap inside a div tag
3501 */
3502 public function print_delete_old_dirs_form($include_blurb = true, $include_div = true) {
3503 if ($include_blurb) {
3504 if ($include_div) {
3505 echo '<div id="updraft_delete_old_dirs_pagediv" class="updated delete-old-directories">';
3506 }
3507 echo '<p>'.__('Your WordPress install has old directories from its state before you restored/migrated (technical information: these are suffixed with -old). You should press this button to delete them as soon as you have verified that the restoration worked.', 'updraftplus').'</p>';
3508 }
3509 ?>
3510 <form method="post" action="<?php echo esc_url(add_query_arg(array('error' => false, 'updraft_restore_success' => false, 'action' => false, 'page' => 'updraftplus'), UpdraftPlus_Options::admin_page_url())); ?>">
3511 <?php wp_nonce_field('updraftplus-credentialtest-nonce', 'updraft_delete_old_dirs_nonce'); ?>
3512 <input type="hidden" name="action" value="updraft_delete_old_dirs">
3513 <input type="submit" class="button-primary" value="<?php echo esc_attr(__('Delete Old Directories', 'updraftplus'));?>">
3514 </form>
3515 <?php
3516 if ($include_blurb && $include_div) echo '</div>';
3517 }
3518
3519 /**
3520 * Return cron status information about a specified in-progress job
3521 *
3522 * @param Boolean|String $job_id - the job to get information about; or, if not specified, all jobs
3523 *
3524 * @return Array|Boolean - the requested information, or false if it was not found. Format differs depending on whether info on all jobs, or a single job, was requested.
3525 */
3526 public function get_cron($job_id = false) {
3527
3528 $cron = get_option('cron');
3529 if (!is_array($cron)) $cron = array();
3530 if (false === $job_id) return $cron;
3531
3532 foreach ($cron as $time => $job) {
3533 if (!isset($job['updraft_backup_resume'])) continue;
3534 foreach ($job['updraft_backup_resume'] as $info) {
3535 if (isset($info['args'][1]) && $job_id == $info['args'][1]) {
3536 global $updraftplus;
3537 $jobdata = $updraftplus->jobdata_getarray($job_id);
3538 return is_array($jobdata) ? array($time, $jobdata) : false;
3539 }
3540 }
3541 }
3542 }
3543
3544 /**
3545 * Gets HTML describing the active jobs
3546 *
3547 * @param Boolean $this_job_only A value for $this_job_only also causes something non-empty to always be returned (to allow detection of the job having started on the front-end)
3548 *
3549 * @return String - the HTML
3550 */
3551 private function print_active_jobs($this_job_only = false) {
3552 $cron = $this->get_cron();
3553 $ret = '';
3554
3555 foreach ($cron as $time => $job) {
3556 if (!isset($job['updraft_backup_resume'])) continue;
3557 foreach ($job['updraft_backup_resume'] as $info) {
3558 if (isset($info['args'][1])) {
3559 $job_id = $info['args'][1];
3560 if (false === $this_job_only || $job_id == $this_job_only) {
3561 $ret .= $this->print_active_job($job_id, false, $time, $info['args'][0]);
3562 }
3563 }
3564 }
3565 }
3566 // A value for $this_job_only implies that output is required
3567 if (false !== $this_job_only && !$ret) {
3568 $ret = $this->print_active_job($this_job_only);
3569 if ('' == $ret) {
3570 global $updraftplus;
3571 $log_file = $updraftplus->get_logfile_name($this_job_only);
3572 // if the file exists, the backup was booted. Check if the information about completion is found in the log, or if it was modified at least 2 minutes ago.
3573 if (file_exists($log_file) && ($updraftplus->found_backup_complete_in_logfile($this_job_only) || (time() - filemtime($log_file)) > 120)) {
3574 // The presence of the exact ID matters to the front-end - indicates that the backup job has at least begun
3575 $ret = '<div class="active-jobs updraft_finished" id="updraft-jobid-'.$this_job_only.'"><em>'.__('The backup has finished running', 'updraftplus').'</em> - <a class="updraft-log-link" data-jobid="'.$this_job_only.'">'.__('View Log', 'updraftplus').'</a></div>';
3576 }
3577 }
3578 }
3579
3580 return $ret;
3581 }
3582
3583 /**
3584 * Print the HTML for a particular job
3585 *
3586 * @param String $job_id - the job identifier/nonce
3587 * @param Boolean $is_oneshot - whether this backup should be 'one shot', i.e. no resumptions
3588 * @param Boolean|Integer $time
3589 * @param Integer $next_resumption
3590 *
3591 * @return String
3592 */
3593 private function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) {
3594
3595 $ret = '';
3596
3597 global $updraftplus;
3598 $jobdata = $updraftplus->jobdata_getarray($job_id);
3599
3600 if (false == apply_filters('updraftplus_print_active_job_continue', true, $is_oneshot, $next_resumption, $jobdata)) return '';
3601
3602 if (!isset($jobdata['backup_time'])) return '';
3603
3604 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
3605
3606 $began_at = isset($jobdata['backup_time']) ? get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_time']), 'D, F j, Y H:i') : '?';
3607
3608 $backup_label = !empty($jobdata['label']) ? $jobdata['label'] : '';
3609
3610 $remote_sent = (!empty($jobdata['service']) && ((is_array($jobdata['service']) && in_array('remotesend', $jobdata['service'])) || 'remotesend' === $jobdata['service'])) ? true : false;
3611
3612 $jobstatus = empty($jobdata['jobstatus']) ? 'unknown' : $jobdata['jobstatus'];
3613 $stage = 0;
3614 switch ($jobstatus) {
3615 // Stage 0
3616 case 'begun':
3617 $curstage = __('Backup begun', 'updraftplus');
3618 break;
3619 // Stage 1
3620 case 'filescreating':
3621 $stage = 1;
3622 $curstage = __('Creating file backup zips', 'updraftplus');
3623 if (!empty($jobdata['filecreating_substatus']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['description'])) {
3624
3625 $sdescrip = preg_replace('/ \(.*\)$/', '', $backupable_entities[$jobdata['filecreating_substatus']['e']]['description']);
3626 if (strlen($sdescrip) > 20 && isset($jobdata['filecreating_substatus']['e']) && is_array($jobdata['filecreating_substatus']['e']) && isset($backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription'])) $sdescrip = $backupable_entities[$jobdata['filecreating_substatus']['e']]['shortdescription'];
3627 $curstage .= ' ('.$sdescrip.')';
3628 if (isset($jobdata['filecreating_substatus']['i']) && isset($jobdata['filecreating_substatus']['t'])) {
3629 $stage = min(2, 1 + ($jobdata['filecreating_substatus']['i']/max($jobdata['filecreating_substatus']['t'], 1)));
3630 }
3631 }
3632 break;
3633 case 'filescreated':
3634 $stage = 2;
3635 $curstage = __('Created file backup zips', 'updraftplus');
3636 break;
3637 // Stage 4
3638 case 'clonepolling':
3639 $stage = 4;
3640 $curstage = __('Clone server being provisioned and booted (can take several minutes)', 'updraftplus');
3641 break;
3642 case 'partialclouduploading':
3643 case 'clouduploading':
3644 $stage = 'clouduploading' == $jobstatus ? 4 : 2;
3645 $curstage = __('Uploading files to remote storage', 'updraftplus');
3646 if ($remote_sent) $curstage = __('Sending files to remote site', 'updraftplus');
3647 if (isset($jobdata['uploading_substatus']['t']) && isset($jobdata['uploading_substatus']['i'])) {
3648 $t = max((int) $jobdata['uploading_substatus']['t'], 1);
3649 $i = min($jobdata['uploading_substatus']['i']/$t, 1);
3650 $p = min($jobdata['uploading_substatus']['p'], 1);
3651 $pd = $i + $p/$t;
3652 $stage = 'clouduploading' == $jobstatus ? $stage + $pd : $stage;
3653 $curstage .= ' ('.floor(100*$pd).'%, '.sprintf(__('file %d of %d', 'updraftplus'), (int)$jobdata['uploading_substatus']['i']+1, $t).')';
3654 }
3655 break;
3656 case 'pruning':
3657 $stage = 5;
3658 $curstage = __('Pruning old backup sets', 'updraftplus');
3659 break;
3660 case 'resumingforerrors':
3661 $stage = -1;
3662 $curstage = __('Waiting until scheduled time to retry because of errors', 'updraftplus');
3663 break;
3664 // Stage 6
3665 case 'finished':
3666 $stage = 6;
3667 $curstage = __('Backup finished', 'updraftplus');
3668 break;
3669 default:
3670 // Database creation and encryption occupies the space from 2 to 4. Databases are created then encrypted, then the next database is created/encrypted, etc.
3671 if ('dbcreated' == substr($jobstatus, 0, 9)) {
3672 $jobstatus = 'dbcreated';
3673 $whichdb = substr($jobstatus, 9);
3674 if (!is_numeric($whichdb)) $whichdb = 0;
3675 $howmanydbs = max((empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']), 1);
3676 $perdbspace = 2/$howmanydbs;
3677
3678 $stage = min(4, 2 + ($whichdb+2)*$perdbspace);
3679
3680 $curstage = __('Created database backup', 'updraftplus');
3681
3682 } elseif ('dbcreating' == substr($jobstatus, 0, 10)) {
3683 $whichdb = substr($jobstatus, 10);
3684 if (!is_numeric($whichdb)) $whichdb = 0;
3685 $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
3686 $perdbspace = 2/$howmanydbs;
3687 $jobstatus = 'dbcreating';
3688
3689 $stage = min(4, 2 + $whichdb*$perdbspace);
3690
3691 $curstage = __('Creating database backup', 'updraftplus');
3692 if (!empty($jobdata['dbcreating_substatus']['t'])) {
3693 $curstage .= ' ('.sprintf(__('table: %s', 'updraftplus'), $jobdata['dbcreating_substatus']['t']).')';
3694 if (!empty($jobdata['dbcreating_substatus']['i']) && !empty($jobdata['dbcreating_substatus']['a'])) {
3695 $substage = max(0.001, ($jobdata['dbcreating_substatus']['i'] / max($jobdata['dbcreating_substatus']['a'], 1)));
3696 $stage += $substage * $perdbspace * 0.5;
3697 }
3698 }
3699 } elseif ('dbencrypting' == substr($jobstatus, 0, 12)) {
3700 $whichdb = substr($jobstatus, 12);
3701 if (!is_numeric($whichdb)) $whichdb = 0;
3702 $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
3703 $perdbspace = 2/$howmanydbs;
3704 $stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace*0.5);
3705 $jobstatus = 'dbencrypting';
3706 $curstage = __('Encrypting database', 'updraftplus');
3707 } elseif ('dbencrypted' == substr($jobstatus, 0, 11)) {
3708 $whichdb = substr($jobstatus, 11);
3709 if (!is_numeric($whichdb)) $whichdb = 0;
3710 $howmanydbs = (empty($jobdata['backup_database']) || !is_array($jobdata['backup_database'])) ? 1 : count($jobdata['backup_database']);
3711 $jobstatus = 'dbencrypted';
3712 $perdbspace = 2/$howmanydbs;
3713 $stage = min(4, 2 + $whichdb*$perdbspace + $perdbspace);
3714 $curstage = __('Encrypted database', 'updraftplus');
3715 } else {
3716 $curstage = __('Unknown', 'updraftplus');
3717 }
3718 }
3719
3720 $runs_started = empty($jobdata['runs_started']) ? array() : $jobdata['runs_started'];
3721 $time_passed = empty($jobdata['run_times']) ? array() : $jobdata['run_times'];
3722 $last_checkin_ago = -1;
3723 if (is_array($time_passed)) {
3724 foreach ($time_passed as $run => $passed) {
3725 if (isset($runs_started[$run])) {
3726 $time_ago = microtime(true) - ($runs_started[$run] + $time_passed[$run]);
3727 if ($time_ago < $last_checkin_ago || -1 == $last_checkin_ago) $last_checkin_ago = $time_ago;
3728 }
3729 }
3730 }
3731
3732 $next_res_after = (int) $time-time();
3733 $next_res_txt = $is_oneshot ? '' : sprintf(__('next resumption: %d', 'updraftplus'), $next_resumption).($next_resumption ? ' '.sprintf(__('(after %ss)', 'updraftplus'), $next_res_after) : '').' ';
3734 $last_activity_txt = ($last_checkin_ago >= 0) ? sprintf(__('last activity: %ss ago', 'updraftplus'), floor($last_checkin_ago)).' ' : '';
3735
3736 if (($last_checkin_ago < 50 && $next_res_after>30) || $is_oneshot) {
3737 $show_inline_info = $last_activity_txt;
3738 $title_info = $next_res_txt;
3739 } else {
3740 $show_inline_info = $next_res_txt;
3741 $title_info = $last_activity_txt;
3742 }
3743
3744 $ret .= '<div class="updraft_row">';
3745
3746 $ret .= '<div class="updraft_col"><div class="updraft_jobtimings next-resumption';
3747
3748 if (!empty($jobdata['is_autobackup'])) $ret .= ' isautobackup';
3749
3750 $is_clone = empty($jobdata['clone_job']) ? '0' : '1';
3751
3752 $clone_url = empty($jobdata['clone_url']) ? false : true;
3753
3754 $ret .= '" data-jobid="'.$job_id.'" data-lastactivity="'.(int) $last_checkin_ago.'" data-nextresumption="'.$next_resumption.'" data-nextresumptionafter="'.$next_res_after.'" title="'.esc_attr(sprintf(__('Job ID: %s', 'updraftplus'), $job_id)).$title_info.'">'.(!empty($backup_label) ? esc_html($backup_label) : $began_at).
3755 '</div></div>';
3756
3757 $ret .= '<div class="updraft_col updraft_progress_container">';
3758 // Existence of the 'updraft-jobid-(id)' id is checked for in other places, so do not modify this
3759 $ret .= '<div class="job-id" data-isclone="'.$is_clone.'" id="updraft-jobid-'.$job_id.'">';
3760
3761 if ($clone_url) $ret .= '<div class="updraft_clone_url" data-clone_url="' . $jobdata['clone_url'] . '"></div>';
3762
3763 $ret .= apply_filters('updraft_printjob_beforewarnings', '', $jobdata, $job_id);
3764
3765 if (!empty($jobdata['warnings']) && is_array($jobdata['warnings'])) {
3766 $ret .= '<ul class="disc">';
3767 foreach ($jobdata['warnings'] as $warning) {
3768 $ret .= '<li>'.sprintf(__('Warning: %s', 'updraftplus'), make_clickable(htmlspecialchars($warning))).'</li>';
3769 }
3770 $ret .= '</ul>';
3771 }
3772
3773 $ret .= '<div class="curstage">';
3774 // $ret .= '<span class="curstage-info">'.htmlspecialchars($curstage).'</span>';
3775 $ret .= htmlspecialchars($curstage);
3776 // we need to add this data-progress attribute in order to be able to update the progress bar in UDC
3777
3778 $ret .= '<div class="updraft_percentage" data-info="'.esc_attr($curstage).'" data-progress="'.(($stage>0) ? (ceil((100/6)*$stage)) : '0').'" style="height: 100%; width:'.(($stage>0) ? (ceil((100/6)*$stage)) : '0').'%"></div>';
3779 $ret .= '</div></div>';
3780
3781 $ret .= '<div class="updraft_last_activity">';
3782
3783 $ret .= $show_inline_info;
3784 if (!empty($show_inline_info)) $ret .= ' - ';
3785
3786 $file_nonce = empty($jobdata['file_nonce']) ? $job_id : $jobdata['file_nonce'];
3787
3788 $ret .= '<a data-fileid="'.$file_nonce.'" data-jobid="'.$job_id.'" href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=downloadlog&updraftplus_backup_nonce='.$file_nonce.'" class="updraft-log-link">'.__('show log', 'updraftplus').'</a>';
3789 if (!$is_oneshot) $ret .=' - <a href="#" data-jobid="'.$job_id.'" title="'.esc_attr(__('Note: the progress bar below is based on stages, NOT time. Do not stop the backup simply because it seems to have remained in the same place for a while - that is normal.', 'updraftplus')).'" class="updraft_jobinfo_delete">'.__('stop', 'updraftplus').'</a>';
3790 $ret .= '</div>';
3791
3792 $ret .= '</div></div>';
3793
3794 return $ret;
3795
3796 }
3797
3798 private function delete_old_dirs_go($show_return = true) {
3799 echo $show_return ? '<h1>UpdraftPlus - '.__('Remove old directories', 'updraftplus').'</h1>' : '<h2>'.__('Remove old directories', 'updraftplus').'</h2>';
3800
3801 if ($this->delete_old_dirs()) {
3802 echo '<p>'.__('Old directories successfully removed.', 'updraftplus').'</p><br>';
3803 } else {
3804 echo '<p>',__('Old directory removal failed for some reason. You may want to do this manually.', 'updraftplus').'</p><br>';
3805 }
3806 if ($show_return) echo '<b>'.__('Actions', 'updraftplus').':</b> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus">'.__('Return to UpdraftPlus configuration', 'updraftplus').'</a>';
3807 }
3808
3809 /**
3810 * Deletes the -old directories that are created when a backup is restored.
3811 *
3812 * @return Boolean. Can also exit (something we ought to probably review)
3813 */
3814 private function delete_old_dirs() {
3815 global $wp_filesystem, $updraftplus;
3816 $credentials = request_filesystem_credentials(wp_nonce_url(UpdraftPlus_Options::admin_page_url()."?page=updraftplus&action=updraft_delete_old_dirs", 'updraftplus-credentialtest-nonce', 'updraft_delete_old_dirs_nonce'));
3817 $wpfs = WP_Filesystem($credentials);
3818 if (!empty($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
3819 foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
3820 exit;
3821 }
3822 if (!$wpfs) exit;
3823
3824 // From WP_CONTENT_DIR - which contains 'themes'
3825 $ret = $this->delete_old_dirs_dir($wp_filesystem->wp_content_dir());
3826
3827 $updraft_dir = $updraftplus->backups_dir_location();
3828 if ($updraft_dir) {
3829 $ret4 = $updraft_dir ? $this->delete_old_dirs_dir($updraft_dir, false) : true;
3830 } else {
3831 $ret4 = true;
3832 }
3833
3834 $plugs = untrailingslashit($wp_filesystem->wp_plugins_dir());
3835 if ($wp_filesystem->is_dir($plugs.'-old')) {
3836 echo "<strong>".__('Delete', 'updraftplus').": </strong>plugins-old: ";
3837 if (!$wp_filesystem->delete($plugs.'-old', true)) {
3838 $ret3 = false;
3839 echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
3840 echo $updraftplus->log_permission_failure_message($wp_filesystem->wp_content_dir(), 'Delete '.$plugs.'-old');
3841 } else {
3842 $ret3 = true;
3843 echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
3844 }
3845 } else {
3846 $ret3 = true;
3847 }
3848
3849 return $ret && $ret3 && $ret4;
3850 }
3851
3852 private function delete_old_dirs_dir($dir, $wpfs = true) {
3853
3854 $dir = trailingslashit($dir);
3855
3856 global $wp_filesystem, $updraftplus;
3857
3858 if ($wpfs) {
3859 $list = $wp_filesystem->dirlist($dir);
3860 } else {
3861 $list = scandir($dir);
3862 }
3863 if (!is_array($list)) return false;
3864
3865 $ret = true;
3866 foreach ($list as $item) {
3867 $name = (is_array($item)) ? $item['name'] : $item;
3868 if ("-old" == substr($name, -4, 4)) {
3869 // recursively delete
3870 print "<strong>".__('Delete', 'updraftplus').": </strong>".htmlspecialchars($name).": ";
3871
3872 if ($wpfs) {
3873 if (!$wp_filesystem->delete($dir.$name, true)) {
3874 $ret = false;
3875 echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
3876 echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
3877 } else {
3878 echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
3879 }
3880 } else {
3881 if (UpdraftPlus_Filesystem_Functions::remove_local_directory($dir.$name)) {
3882 echo "<strong>".__('OK', 'updraftplus')."</strong><br>";
3883 } else {
3884 $ret = false;
3885 echo "<strong>".__('Failed', 'updraftplus')."</strong><br>";
3886 echo $updraftplus->log_permission_failure_message($dir, 'Delete '.$dir.$name);
3887 }
3888 }
3889 }
3890 }
3891 return $ret;
3892 }
3893
3894 /**
3895 * The aim is to get a directory that is writable by the webserver, because that's the only way we can create zip files
3896 *
3897 * @return Boolean|WP_Error true if successful, otherwise false or a WP_Error
3898 */
3899 private function create_backup_dir() {
3900
3901 global $wp_filesystem, $updraftplus;
3902
3903 if (false === ($credentials = request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir')))) {
3904 return false;
3905 }
3906
3907 if (!WP_Filesystem($credentials)) {
3908 // our credentials were no good, ask the user for them again
3909 request_filesystem_credentials(UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir'), '', true);
3910 return false;
3911 }
3912
3913 $updraft_dir = $updraftplus->backups_dir_location();
3914
3915 $default_backup_dir = $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir);
3916
3917 $updraft_dir = ($updraft_dir) ? $wp_filesystem->find_folder(dirname($updraft_dir)).basename($updraft_dir) : $default_backup_dir;
3918
3919 if (!$wp_filesystem->is_dir($default_backup_dir) && !$wp_filesystem->mkdir($default_backup_dir, 0775)) {
3920 $wperr = new WP_Error;
3921 if ($wp_filesystem->errors->get_error_code()) {
3922 foreach ($wp_filesystem->errors->get_error_messages() as $message) {
3923 $wperr->add('mkdir_error', $message);
3924 }
3925 return $wperr;
3926 } else {
3927 return new WP_Error('mkdir_error', __('The request to the filesystem to create the directory failed.', 'updraftplus'));
3928 }
3929 }
3930
3931 if ($wp_filesystem->is_dir($default_backup_dir)) {
3932
3933 if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true;
3934
3935 @$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3936 if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) return true;
3937
3938 @$wp_filesystem->chmod($default_backup_dir, 0777);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3939
3940 if (UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir)) {
3941 echo '<p>'.__('The folder was created, but we had to change its file permissions to 777 (world-writable) to be able to write to it. You should check with your hosting provider that this will not cause any problems', 'updraftplus').'</p>';
3942 return true;
3943 } else {
3944 @$wp_filesystem->chmod($default_backup_dir, 0775);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3945 $show_dir = (0 === strpos($default_backup_dir, ABSPATH)) ? substr($default_backup_dir, strlen(ABSPATH)) : $default_backup_dir;
3946 return new WP_Error('writable_error', __('The folder exists, but your webserver does not have permission to write to it.', 'updraftplus').' '.__('You will need to consult with your web hosting provider to find out how to set permissions for a WordPress plugin to write to the directory.', 'updraftplus').' ('.$show_dir.')');
3947 }
3948 }
3949
3950 return true;
3951 }
3952
3953 /**
3954 * scans the content dir to see if any -old dirs are present
3955 *
3956 * @param Boolean $print_as_comment Echo information in an HTML comment
3957 * @return Boolean
3958 */
3959 private function scan_old_dirs($print_as_comment = false) {
3960 global $updraftplus;
3961 $dirs = scandir(untrailingslashit(WP_CONTENT_DIR));
3962 if (!is_array($dirs)) $dirs = array();
3963 $dirs_u = @scandir($updraftplus->backups_dir_location());// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3964 if (!is_array($dirs_u)) $dirs_u = array();
3965 foreach (array_merge($dirs, $dirs_u) as $dir) {
3966 if (preg_match('/-old$/', $dir)) {
3967 if ($print_as_comment) echo '<!--'.htmlspecialchars($dir).'-->';
3968 return true;
3969 }
3970 }
3971 // No need to scan ABSPATH - we don't backup there
3972 if (is_dir(untrailingslashit(WP_PLUGIN_DIR).'-old')) {
3973 if ($print_as_comment) echo '<!--'.htmlspecialchars(untrailingslashit(WP_PLUGIN_DIR).'-old').'-->';
3974 return true;
3975 }
3976 return false;
3977 }
3978
3979 /**
3980 * Outputs html for a storage method using the parameters passed in, this version should be removed when all remote storages use the multi version
3981 *
3982 * @param String $method a list of methods to be used when
3983 * @param String $header the table header content
3984 * @param String $contents the table contents
3985 */
3986 public function storagemethod_row($method, $header, $contents) {
3987 ?>
3988 <tr class="updraftplusmethod <?php echo $method;?>">
3989 <th><?php echo $header;?></th>
3990 <td><?php echo $contents;?></td>
3991 </tr>
3992 <?php
3993 }
3994
3995 /**
3996 * Outputs html for a storage method using the parameters passed in, this version of the method is compatible with multi storage options
3997 *
3998 * @param string $classes a list of classes to be used when
3999 * @param string $header the table header content
4000 * @param string $contents the table contents
4001 */
4002 public function storagemethod_row_multi($classes, $header, $contents) {
4003 ?>
4004 <tr class="<?php echo $classes;?>">
4005 <th><?php echo $header;?></th>
4006 <td><?php echo $contents;?></td>
4007 </tr>
4008 <?php
4009 }
4010
4011 /**
4012 * Returns html for a storage method using the parameters passed in, this version of the method is compatible with multi storage options
4013 * DEV NOTE: please don't use this method in a handlebars template, but write the HTML code directly in the template. Also, this method might be no longer available in future releases
4014 *
4015 * @param string $classes a list of classes to be used when
4016 * @param string $header the table header content
4017 * @param string $contents the table contents
4018 * @return string handlebars html template
4019 */
4020 public function get_storagemethod_row_multi_configuration_template($classes, $header, $contents) {
4021 return '<tr class="'.esc_attr($classes).'">
4022 <th>'.$header.'</th>
4023 <td>'.$contents.'</td>
4024 </tr>';
4025 }
4026
4027 /**
4028 * Get HTML suitable for the admin area for the status of the last backup
4029 *
4030 * @return String
4031 */
4032 public function last_backup_html() {
4033
4034 global $updraftplus;
4035
4036 $updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
4037
4038 if ($updraft_last_backup) {
4039
4040 // Convert to GMT, then to blog time
4041 $backup_time = (int) $updraft_last_backup['backup_time'];
4042
4043 $print_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $backup_time), 'D, F j, Y H:i');
4044
4045 if (empty($updraft_last_backup['backup_time_incremental'])) {
4046 $last_backup_text = "<span style=\"color:".(($updraft_last_backup['success']) ? 'green' : 'black').";\">".$print_time.'</span>';
4047 } else {
4048 $inc_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraft_last_backup['backup_time_incremental']), 'D, F j, Y H:i');
4049 $last_backup_text = "<span style=\"color:".(($updraft_last_backup['success']) ? 'green' : 'black').";\">$inc_time</span> (".sprintf(__('incremental backup; base backup: %s', 'updraftplus'), $print_time).')';
4050 }
4051
4052 $last_backup_text .= '<br>';
4053
4054 // Show errors + warnings
4055 if (is_array($updraft_last_backup['errors'])) {
4056 foreach ($updraft_last_backup['errors'] as $err) {
4057 $level = (is_array($err)) ? $err['level'] : 'error';
4058 $message = (is_array($err)) ? $err['message'] : $err;
4059 $last_backup_text .= ('warning' == $level) ? "<span style=\"color:orange;\">" : "<span style=\"color:red;\">";
4060 if ('warning' == $level) {
4061 $message = sprintf(__("Warning: %s", 'updraftplus'), make_clickable(htmlspecialchars($message)));
4062 } else {
4063 $message = htmlspecialchars($message);
4064 }
4065 $last_backup_text .= $message;
4066 $last_backup_text .= '</span><br>';
4067 }
4068 }
4069
4070 // Link log
4071 if (!empty($updraft_last_backup['backup_nonce'])) {
4072 $updraft_dir = $updraftplus->backups_dir_location();
4073
4074 $potential_log_file = $updraft_dir."/log.".$updraft_last_backup['backup_nonce'].".txt";
4075 if (is_readable($potential_log_file)) $last_backup_text .= "<a href=\"?page=updraftplus&action=downloadlog&updraftplus_backup_nonce=".$updraft_last_backup['backup_nonce']."\" class=\"updraft-log-link\" onclick=\"event.preventDefault(); updraft_popuplog('".$updraft_last_backup['backup_nonce']."');\">".__('Download log file', 'updraftplus')."</a>";
4076 }
4077
4078 } else {
4079 $last_backup_text = "<span style=\"color:blue;\">".__('No backup has been completed', 'updraftplus')."</span>";
4080 }
4081
4082 return $last_backup_text;
4083
4084 }
4085
4086 /**
4087 * Get a list of backup intervals
4088 *
4089 * @param String $what_for - 'files' or 'db'
4090 *
4091 * @return Array - keys are used as identifiers in the UI drop-down; values are user-displayed text describing the interval
4092 */
4093 public function get_intervals($what_for = 'db') {
4094 global $updraftplus;
4095
4096 if ($updraftplus->is_restricted_hosting('only_one_backup_per_month')) {
4097 $intervals = array(
4098 'manual' => _x('Manual', 'i.e. Non-automatic', 'updraftplus'),
4099 'monthly' => __('Monthly', 'updraftplus')
4100 );
4101 } else {
4102 $intervals = array(
4103 'manual' => _x('Manual', 'i.e. Non-automatic', 'updraftplus'),
4104 'everyhour' => __('Every hour', 'updraftplus'),
4105 'every2hours' => sprintf(__('Every %s hours', 'updraftplus'), '2'),
4106 'every4hours' => sprintf(__('Every %s hours', 'updraftplus'), '4'),
4107 'every8hours' => sprintf(__('Every %s hours', 'updraftplus'), '8'),
4108 'twicedaily' => sprintf(__('Every %s hours', 'updraftplus'), '12'),
4109 'daily' => __('Daily', 'updraftplus'),
4110 'weekly' => __('Weekly', 'updraftplus'),
4111 'fortnightly' => __('Fortnightly', 'updraftplus'),
4112 'monthly' => __('Monthly', 'updraftplus'),
4113 );
4114
4115 if ('files' == $what_for) unset($intervals['everyhour']);
4116 }
4117
4118 return apply_filters('updraftplus_backup_intervals', $intervals, $what_for);
4119 }
4120
4121 public function really_writable_message($really_is_writable, $updraft_dir) {
4122 if ($really_is_writable) {
4123 $dir_info = '<span style="color:green;">'.__('Backup directory specified is writable, which is good.', 'updraftplus').'</span>';
4124 } else {
4125 $dir_info = '<span style="color:red;">';
4126 if (!is_dir($updraft_dir)) {
4127 $dir_info .= __('Backup directory specified does <b>not</b> exist.', 'updraftplus');
4128 } else {
4129 $dir_info .= __('Backup directory specified exists, but is <b>not</b> writable.', 'updraftplus');
4130 }
4131 $dir_info .= '<span class="updraft-directory-not-writable-blurb"><span class="directory-permissions"><a class="updraft_create_backup_dir" href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraft_create_backup_dir&nonce='.wp_create_nonce('create_backup_dir').'">'.__('Follow this link to attempt to create the directory and set the permissions', 'updraftplus').'</a></span>, '.__('or, to reset this option', 'updraftplus').' <a href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" class="updraft_backup_dir_reset">'.__('press here', 'updraftplus').'</a>. '.__('If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.', 'updraftplus').'</span>';
4132 }
4133 return $dir_info;
4134 }
4135
4136 /**
4137 * Directly output the settings form (suitable for the admin area)
4138 *
4139 * @param Array $options current options (passed on to the template)
4140 */
4141 public function settings_formcontents($options = array()) {
4142 $this->include_template('wp-admin/settings/form-contents.php', false, array(
4143 'options' => $options
4144 ));
4145 if (!(defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND)) {
4146 $this->include_template('wp-admin/settings/exclude-modal.php', false);
4147 }
4148 }
4149
4150 public function get_settings_js($method_objects, $really_is_writable, $updraft_dir, $active_service) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
4151
4152 global $updraftplus;
4153
4154 ob_start();
4155 ?>
4156 jQuery(function() {
4157 <?php
4158 if (!$really_is_writable) echo "jQuery('.backupdirrow').show();\n";
4159 ?>
4160 <?php
4161 if (!empty($active_service)) {
4162 if (is_array($active_service)) {
4163 foreach ($active_service as $serv) {
4164 echo "jQuery('.".esc_js($serv)."').show();\n";
4165 }
4166 } else {
4167 echo "jQuery('.".esc_js($active_service)."').show();\n";
4168 }
4169 } else {
4170 echo "jQuery('.none').show();\n";
4171 }
4172 foreach ($updraftplus->backup_methods as $method => $description) {
4173 // already done: require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
4174 $call_method = "UpdraftPlus_BackupModule_$method";
4175 if (method_exists($call_method, 'config_print_javascript_onready')) {
4176 $method_objects[$method]->config_print_javascript_onready();
4177 }
4178 }
4179 ?>
4180 });
4181 <?php
4182 $ret = ob_get_contents();
4183 ob_end_clean();
4184 return $ret;
4185 }
4186
4187 /**
4188 * Return the HTML for the files selector widget
4189 *
4190 * @param String $prefix Prefix for the ID
4191 * @param Boolean $show_exclusion_options True or False for exclusion options
4192 * @param Boolean|String $include_more $include_more can be (bool) or (string)"sometimes"
4193 *
4194 * @return String
4195 */
4196 public function files_selector_widgetry($prefix = '', $show_exclusion_options = true, $include_more = true) {
4197
4198 $ret = '';
4199
4200 global $updraftplus;
4201 $for_updraftcentral = defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND;
4202 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
4203 // The true (default value if non-existent) here has the effect of forcing a default of on.
4204 $include_more_paths = UpdraftPlus_Options::get_updraft_option('updraft_include_more_path');
4205 foreach ($backupable_entities as $key => $info) {
4206 $included = (UpdraftPlus_Options::get_updraft_option("updraft_include_$key", apply_filters("updraftplus_defaultoption_include_".$key, true))) ? 'checked="checked"' : "";
4207 if ('others' == $key || 'uploads' == $key) {
4208
4209 $data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : '';
4210
4211 $ret .= '<label '.(('others' == $key) ? 'title="'.sprintf(__('Your wp-content directory server path: %s', 'updraftplus'), WP_CONTENT_DIR).'" ' : '').' for="'.$prefix.'updraft_include_'.$key.'" class="updraft_checkbox"><input class="updraft_include_entity" id="'.$prefix.'updraft_include_'.$key.'" '.$data_toggle_exclude_field.' type="checkbox" name="updraft_include_'.$key.'" value="1" '.$included.'> '.(('others' == $key) ? __('Any other directories found inside wp-content', 'updraftplus') : htmlspecialchars($info['description'])).'</label>';
4212
4213 if ($show_exclusion_options) {
4214 $include_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_'.$key.'_exclude', ('others' == $key) ? UPDRAFT_DEFAULT_OTHERS_EXCLUDE : UPDRAFT_DEFAULT_UPLOADS_EXCLUDE);
4215
4216 $display = ($included) ? '' : 'class="updraft-hidden" style="display:none;"';
4217 $exclude_container_class = $prefix.'updraft_include_'.$key.'_exclude';
4218 if (!$for_updraftcentral) $exclude_container_class .= '_container';
4219
4220 $ret .= "<div id=\"".$exclude_container_class."\" $display class=\"updraft_exclude_container\">";
4221
4222 $ret .= '<label class="updraft-exclude-label" for="'.$prefix.'updraft_include_'.$key.'_exclude">'.__('Exclude these from', 'updraftplus').' '.htmlspecialchars($info['description']).':</label> <span class="updraft-fs-italic">'.__('(the asterisk character matches zero or more characters)', 'updraftplus').'</span>';
4223
4224 $exclude_input_type = $for_updraftcentral ? "text" : "hidden";
4225 $exclude_input_extra_attr = $for_updraftcentral ? 'title="'.__('If entering multiple files/directories, then separate them with commas. For entities at the top level, you can use a * at the start or end of the entry as a wildcard.', 'updraftplus').'" size="54"' : '';
4226 $ret .= '<input type="'.$exclude_input_type.'" id="'.$prefix.'updraft_include_'.$key.'_exclude" name="updraft_include_'.$key.'_exclude" '.$exclude_input_extra_attr.' value="'.htmlspecialchars($include_exclude).'" />';
4227
4228 if (!$for_updraftcentral) {
4229 global $updraftplus;
4230 $backupable_file_entities = $updraftplus->get_backupable_file_entities();
4231
4232 if ('uploads' == $key) {
4233 $path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['uploads']);
4234 } elseif ('others' == $key) {
4235 $path = UpdraftPlus_Manipulation_Functions::wp_normalize_path($backupable_file_entities['others']);
4236 }
4237 $ret .= $this->include_template('wp-admin/settings/file-backup-exclude.php', true, array(
4238 'key' => $key,
4239 'include_exclude' => $include_exclude,
4240 'path' => $path,
4241 'show_exclusion_options' => $show_exclusion_options,
4242 ));
4243 }
4244 $ret .= '</div>';
4245 }
4246
4247 } else {
4248
4249 if ('more' != $key || true === $include_more || ('sometimes' === $include_more && !empty($include_more_paths))) {
4250
4251 $data_toggle_exclude_field = $show_exclusion_options ? 'data-toggle_exclude_field="'.$key.'"' : '';
4252
4253 $ret .= "<label for=\"".$prefix."updraft_include_$key\"".((isset($info['htmltitle'])) ? ' title="'.htmlspecialchars($info['htmltitle']).'"' : '')." class=\"updraft_checkbox\"><input class=\"updraft_include_entity\" $data_toggle_exclude_field id=\"".$prefix."updraft_include_$key\" type=\"checkbox\" name=\"updraft_include_$key\" value=\"1\" $included /> ".htmlspecialchars($info['description']);
4254
4255 $ret .= "</label>";
4256 $ret .= apply_filters("updraftplus_config_option_include_$key", '', $prefix, $for_updraftcentral);
4257 }
4258 }
4259 }
4260
4261 return $ret;
4262 }
4263
4264 /**
4265 * Output or echo HTML for an error condition relating to a remote storage method
4266 *
4267 * @param String $text - the text of the message; this should already be escaped (no more is done)
4268 * @param String $extraclass - a CSS class for the resulting DOM node
4269 * @param Integer $echo - if set, then the results will be echoed as well as returned
4270 *
4271 * @return String - the results
4272 */
4273 public function show_double_warning($text, $extraclass = '', $echo = true) {
4274
4275 $ret = "<div class=\"error updraftplusmethod $extraclass\"><p>$text</p></div>";
4276 $ret .= "<div class=\"notice error below-h2\"><p>$text</p></div>";
4277
4278 if ($echo) echo $ret;
4279 return $ret;
4280
4281 }
4282
4283 public function optionfilter_split_every($value) {
4284 return max(absint($value), UPDRAFTPLUS_SPLIT_MIN);
4285 }
4286
4287 /**
4288 * Check if curl exists; if not, print or return appropriate error messages
4289 *
4290 * @param String $service the service description (used only for user-visible messages - so, use the description)
4291 * @param Boolean $has_fallback set as true if the lack of Curl only affects the ability to connect over SSL
4292 * @param String $extraclass an extra CSS class for any resulting message, passed on to show_double_warning()
4293 * @param Boolean $echo_instead_of_return whether the result should be echoed or returned
4294 * @return String any resulting message, if $echo_instead_of_return was set
4295 */
4296 public function curl_check($service, $has_fallback = false, $extraclass = '', $echo_instead_of_return = true) {
4297
4298 $ret = '';
4299
4300 // Check requirements
4301 if (!function_exists("curl_init") || !function_exists('curl_exec')) {
4302
4303 $ret .= $this->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP installation does not included a <strong>required</strong> (for %s) module (%s). Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'), $service, 'Curl').' ', $extraclass, false);
4304
4305 } else {
4306 $curl_version = curl_version();
4307 $curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL);
4308 if (!$curl_ssl_supported) {
4309 if ($has_fallback) {
4310 $ret .= '<p><strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP/Curl installation does not support https access. Communications with %s will be unencrypted. Ask your web host to install Curl/SSL in order to gain the ability for encryption (via an add-on).", 'updraftplus'), $service).'</p>';
4311 } else {
4312 $ret .= $this->show_double_warning('<p><strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP/Curl installation does not support https access. We cannot access %s without this support. Please contact your web hosting provider's support. %s <strong>requires</strong> Curl+https. Please do not file any support requests; there is no alternative.", 'updraftplus'), $service, $service).'</p>', $extraclass, false);
4313 }
4314 } else {
4315 $ret .= '<p><em>'.sprintf(__("Good news: Your site's communications with %s can be encrypted. If you see any errors to do with encryption, then look in the 'Expert Settings' for more help.", 'updraftplus'), $service).'</em></p>';
4316 }
4317 }
4318 if ($echo_instead_of_return) {
4319 echo $ret;
4320 } else {
4321 return $ret;
4322 }
4323 }
4324
4325 /**
4326 * Get backup information in HTML format for a specific backup
4327 *
4328 * @param Array $backup_history all backups history
4329 * @param String $key backup timestamp
4330 * @param String $nonce backup nonce (job ID)
4331 * @param Array|Null $job_data if an array, then use this as the job data (if null, then it will be fetched directly)
4332 *
4333 * @return string HTML-formatted backup information
4334 */
4335 public function raw_backup_info($backup_history, $key, $nonce, $job_data = null) {
4336
4337 global $updraftplus;
4338
4339 $backup = $backup_history[$key];
4340
4341 $only_remote_sent = !empty($backup['service']) && (array('remotesend') === $backup['service'] || 'remotesend' === $backup['service']);
4342
4343 $pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $key), 'M d, Y G:i');
4344
4345 $rawbackup = "<h2 title=\"$key\">$pretty_date</h2>";
4346
4347 if (!empty($backup['label'])) $rawbackup .= '<span class="raw-backup-info">'.$backup['label'].'</span>';
4348
4349 if (null === $job_data) $job_data = empty($nonce) ? array() : $updraftplus->jobdata_getarray($nonce);
4350
4351 if (!$only_remote_sent) {
4352 $rawbackup .= '<hr>';
4353 $rawbackup .= '<input type="checkbox" name="always_keep_this_backup" id="always_keep_this_backup" data-backup_key="'.$key.'" '.(empty($backup['always_keep']) ? '' : 'checked ').'><label for="always_keep_this_backup">'.__('Only allow this backup to be deleted manually (i.e. keep it even if retention limits are hit).', 'updraftplus').'</label>';
4354 }
4355
4356 $rawbackup .= '<hr><p>';
4357
4358 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
4359
4360 $checksums = $updraftplus->which_checksums();
4361
4362 foreach ($backupable_entities as $type => $info) {
4363 if (!isset($backup[$type])) continue;
4364
4365 $rawbackup .= $updraftplus->printfile($info['description'], $backup, $type, $checksums, $job_data, true);
4366 }
4367
4368 $total_size = 0;
4369 foreach ($backup as $ekey => $files) {
4370 if ('db' == strtolower(substr($ekey, 0, 2)) && '-size' != substr($ekey, -5, 5)) {
4371 $rawbackup .= $updraftplus->printfile(__('Database', 'updraftplus'), $backup, $ekey, $checksums, $job_data, true);
4372 }
4373 if (!isset($backupable_entities[$ekey]) && ('db' != substr($ekey, 0, 2) || '-size' == substr($ekey, -5, 5))) continue;
4374 if (is_string($files)) $files = array($files);
4375 foreach ($files as $findex => $file) {
4376 $size_key = (0 == $findex) ? $ekey.'-size' : $ekey.$findex.'-size';
4377 $total_size = (false === $total_size || !isset($backup[$size_key]) || !is_numeric($backup[$size_key])) ? false : $total_size + $backup[$size_key];
4378 }
4379 }
4380
4381 $services = empty($backup['service']) ? array('none') : $backup['service'];
4382 if (!is_array($services)) $services = array('none');
4383
4384 $rawbackup .= '<strong>'.__('Uploaded to:', 'updraftplus').'</strong> ';
4385
4386 $show_services = '';
4387 foreach ($services as $serv) {
4388 if ('none' == $serv || '' == $serv) {
4389 $add_none = true;
4390 } elseif (isset($updraftplus->backup_methods[$serv])) {
4391 $show_services .= $show_services ? ', '.$updraftplus->backup_methods[$serv] : $updraftplus->backup_methods[$serv];
4392 } else {
4393 $show_services .= $show_services ? ', '.$serv : $serv;
4394 }
4395 }
4396 if ('' == $show_services && $add_none) $show_services .= __('None', 'updraftplus');
4397
4398 $rawbackup .= $show_services;
4399
4400 if (false !== $total_size) {
4401 $rawbackup .= '</p><strong>'.__('Total backup size:', 'updraftplus').'</strong> '.UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_size).'<p>';
4402 }
4403
4404 $rawbackup .= '</p><hr><p><pre>'.print_r($backup, true).'</pre></p>';
4405
4406 if (!empty($job_data) && is_array($job_data)) {
4407 $rawbackup .= '<p><pre>'.htmlspecialchars(print_r($job_data, true)).'</pre></p>';
4408 }
4409
4410 return esc_attr($rawbackup);
4411 }
4412
4413 private function download_db_button($bkey, $key, $esc_pretty_date, $backup, $accept = array()) {
4414
4415 if (!empty($backup['meta_foreign']) && isset($accept[$backup['meta_foreign']])) {
4416 $desc_source = $accept[$backup['meta_foreign']]['desc'];
4417 } else {
4418 $desc_source = __('unknown source', 'updraftplus');
4419 }
4420
4421 $ret = '';
4422
4423 if ('db' == $bkey) {
4424 $dbt = empty($backup['meta_foreign']) ? esc_attr(__('Database', 'updraftplus')) : esc_attr(sprintf(__('Database (created by %s)', 'updraftplus'), $desc_source));
4425 } else {
4426 $dbt = __('External database', 'updraftplus').' ('.substr($bkey, 2).')';
4427 }
4428
4429 $ret .= $this->download_button($bkey, $key, 0, null, '', $dbt, $esc_pretty_date, '0');
4430
4431 return $ret;
4432 }
4433
4434 /**
4435 * Go through each of the file entities
4436 *
4437 * @param Array $backup An array of meta information
4438 * @param Integer $key Backup timestamp (epoch time)
4439 * @param Array $accept An array of values to be accepted from vaules within $backup
4440 * @param String $entities Entities to be added
4441 * @param String $esc_pretty_date Whether the button needs to escape the pretty date format
4442 * @return String - the resulting HTML
4443 */
4444 public function download_buttons($backup, $key, $accept, &$entities, $esc_pretty_date) {
4445 global $updraftplus;
4446 $ret = '';
4447 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
4448
4449 $first_entity = true;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- template
4450
4451 foreach ($backupable_entities as $type => $info) {
4452 if (!empty($backup['meta_foreign']) && 'wpcore' != $type) continue;
4453
4454 $ide = '';
4455
4456 if (empty($backup['meta_foreign'])) {
4457 $sdescrip = preg_replace('/ \(.*\)$/', '', $info['description']);
4458 if (strlen($sdescrip) > 20 && isset($info['shortdescription'])) $sdescrip = $info['shortdescription'];
4459 } else {
4460 $info['description'] = 'WordPress';
4461
4462 if (isset($accept[$backup['meta_foreign']])) {
4463 $desc_source = $accept[$backup['meta_foreign']]['desc'];
4464 $ide .= sprintf(__('Backup created by: %s.', 'updraftplus'), $accept[$backup['meta_foreign']]['desc']).' ';
4465 } else {
4466 $desc_source = __('unknown source', 'updraftplus');
4467 $ide .= __('Backup created by unknown source (%s) - cannot be restored.', 'updraftplus').' ';
4468 }
4469
4470 $sdescrip = (empty($accept[$backup['meta_foreign']]['separatedb'])) ? sprintf(__('Files and database WordPress backup (created by %s)', 'updraftplus'), $desc_source) : sprintf(__('Files backup (created by %s)', 'updraftplus'), $desc_source);
4471 }
4472 if (isset($backup[$type])) {
4473 if (!is_array($backup[$type])) $backup[$type] = array($backup[$type]);
4474 $howmanyinset = count($backup[$type]);
4475 $expected_index = 0;
4476 $index_missing = false;
4477 $set_contents = '';
4478 $entities .= "/$type=";
4479 $whatfiles = $backup[$type];
4480 ksort($whatfiles);
4481 $total_file_size = 0;
4482 foreach ($whatfiles as $findex => $bfile) {
4483 $set_contents .= ('' == $set_contents) ? $findex : ",$findex";
4484 if ($findex != $expected_index) $index_missing = true;
4485 $expected_index++;
4486
4487 if ($howmanyinset > 0) {
4488 if (!empty($backup[$type.(($findex > 0) ? $findex : '')."-size"]) && $findex < $howmanyinset) $total_file_size += $backup[$type.(($findex > 0) ? $findex : '')."-size"];
4489 }
4490 }
4491
4492 $ide = __('Press here to download or browse', 'updraftplus').' '.strtolower($info['description']);
4493 $ide .= ' '.sprintf(__('(%d archive(s) in set, total %s).', 'updraftplus'), $howmanyinset, '%UP_backups_total_file_size%');
4494 if ($index_missing) $ide .= ' '.__('You appear to be missing one or more archives from this multi-archive set.', 'updraftplus');
4495
4496 $entities .= $set_contents.'/';
4497 if (!empty($backup['meta_foreign'])) {
4498 $entities .= '/plugins=0//themes=0//uploads=0//others=0/';
4499 }
4500
4501 $ret .= $this->download_button($type, $key, 0, null, $ide, $sdescrip, $esc_pretty_date, $set_contents);
4502
4503 $ret = str_replace('%UP_backups_total_file_size%', UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_file_size), $ret);
4504 }
4505 }
4506 return $ret;
4507 }
4508
4509 public function date_label($pretty_date, $key, $backup, $jobdata, $nonce, $simple_format = false) {
4510
4511 $pretty_date = $simple_format ? $pretty_date : '<div class="clear-right">'.$pretty_date.'</div>';
4512
4513 $ret = apply_filters('updraftplus_showbackup_date', $pretty_date, $backup, $jobdata, (int) $key, $simple_format);
4514 if (is_array($jobdata) && !empty($jobdata['resume_interval']) && (empty($jobdata['jobstatus']) || 'finished' != $jobdata['jobstatus'])) {
4515 if ($simple_format) {
4516 $ret .= ' '.__('(Not finished)', 'updraftplus');
4517 } else {
4518 $ret .= apply_filters('updraftplus_msg_unfinishedbackup', "<br><span title=\"".esc_attr(__('If you are seeing more backups than you expect, then it is probably because the deletion of old backup sets does not happen until a fresh backup completes.', 'updraftplus'))."\">".__('(Not finished)', 'updraftplus').'</span>', $jobdata, $nonce);
4519 }
4520 }
4521 return $ret;
4522 }
4523
4524 public function download_button($type, $backup_timestamp, $findex, $info, $title, $pdescrip, $esc_pretty_date, $set_contents) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
4525
4526 $ret = '';
4527
4528 $wp_nonce = wp_create_nonce('updraftplus_download');
4529
4530 // updraft_downloader(base, backup_timestamp, what, whicharea, set_contents, prettydate, async)
4531 $ret .= '<button data-wp_nonce="'.esc_attr($wp_nonce).'" data-backup_timestamp="'.esc_attr($backup_timestamp).'" data-what="'.esc_attr($type).'" data-set_contents="'.esc_attr($set_contents).'" data-prettydate="'.esc_attr($esc_pretty_date).'" type="button" class="button updraft_download_button '."uddownloadform_${type}_${backup_timestamp}_${findex}".'" title="'.$title.'">'.$pdescrip.'</button>';
4532 // onclick="'."return updraft_downloader('uddlstatus_', '$backup_timestamp', '$type', '.ud_downloadstatus', '$set_contents', '$esc_pretty_date', true)".'"
4533
4534 return $ret;
4535 }
4536
4537 public function restore_button($backup, $key, $pretty_date, $entities = '') {
4538 $ret = '<div class="restore-button">';
4539
4540 if ($entities) {
4541 $show_data = $pretty_date;
4542 if (isset($backup['native']) && false == $backup['native']) {
4543 $show_data .= ' '.__('(backup set imported from remote location)', 'updraftplus');
4544 }
4545
4546 $ret .= '<button data-showdata="'.esc_attr($show_data).'" data-backup_timestamp="'.$key.'" data-entities="'.esc_attr($entities).'" title="'.__('After pressing this button, you will be given the option to choose which components you wish to restore', 'updraftplus').'" type="button" class="button button-primary choose-components-button">'.__('Restore', 'updraftplus').'</button>';
4547 }
4548 $ret .= "</div>\n";
4549 return $ret;
4550 }
4551
4552 /**
4553 * Get HTML for the 'Upload' button for a particular backup in the 'Existing backups' tab
4554 *
4555 * @param Integer $backup_time - backup timestamp (epoch time)
4556 * @param String $nonce - backup nonce
4557 * @param Array $backup - backup information array
4558 * @param Null|Array $jobdata - if not null, then use as the job data instead of fetching
4559 *
4560 * @return String - the resulting HTML
4561 */
4562 public function upload_button($backup_time, $nonce, $backup, $jobdata = null) {
4563 global $updraftplus;
4564
4565 // Check the job is not still running.
4566 if (null === $jobdata) $jobdata = $updraftplus->jobdata_getarray($nonce);
4567
4568 if (!empty($jobdata) && 'finished' != $jobdata['jobstatus']) return '';
4569
4570 // Check that the user has remote storage setup.
4571 $services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list());
4572 if (empty($services)) return '';
4573
4574 $show_upload = false;
4575
4576 // Check that the backup has not already been sent to remote storage before.
4577 if (empty($backup['service']) || array('none') == $backup['service'] || array('') == $backup['service'] || 'none' == $backup['service']) {
4578 $show_upload = true;
4579 // If it has been uploaded then check if there are any new remote storage options that it has not yet been sent to.
4580 } elseif (!empty($backup['service']) && array('none') != $backup['service'] && array('') != $backup['service'] && 'none' != $backup['service']) {
4581
4582 foreach ($services as $key => $value) {
4583 if (in_array($value, $backup['service'])) unset($services[$key]);
4584 }
4585
4586 if (!empty($services)) $show_upload = true;
4587 }
4588
4589 if ($show_upload) {
4590
4591 $backup_local = $this->check_backup_is_complete($backup, false, false, true);
4592
4593 if ($backup_local) {
4594 $service_list = '';
4595 $service_list_display = '';
4596 $is_first_service = true;
4597
4598 foreach ($services as $key => $service) {
4599 if (!$is_first_service) {
4600 $service_list .= ',';
4601 $service_list_display .= ', ';
4602 }
4603 $service_list .= $service;
4604 $service_list_display .= $updraftplus->backup_methods[$service];
4605
4606 $is_first_service = false;
4607 }
4608
4609 return '<div class="updraftplus-upload">
4610 <button data-nonce="'.$nonce.'" data-key="'.$backup_time.'" data-services="'.esc_attr($service_list).'" title="'.__('After pressing this button, you can select where to upload your backup from a list of your currently saved remote storage locations', 'updraftplus').' ('.htmlspecialchars($service_list_display).')." type="button" class="button button-primary updraft-upload-link">'.__('Upload', 'updraftplus').'</button>
4611 </div>';
4612 }
4613
4614 return '';
4615 }
4616 }
4617
4618 /**
4619 * Get HTML for the 'Delete' button for a particular backup in the 'Existing backups' tab
4620 *
4621 * @param Integer $backup_time - backup timestamp (epoch time)
4622 * @param String $nonce - backup nonce
4623 * @param Array $backup - backup information array
4624 *
4625 * @return String - the resulting HTML
4626 */
4627 public function delete_button($backup_time, $nonce, $backup) {
4628 $sval = (!empty($backup['service']) && 'email' != $backup['service'] && 'none' != $backup['service'] && array('email') !== $backup['service'] && array('none') !== $backup['service'] && array('remotesend') !== $backup['service']) ? '1' : '0';
4629 return '<div class="updraftplus-remove" data-hasremote="'.$sval.'">
4630 <a data-hasremote="'.$sval.'" data-nonce="'.$nonce.'" data-key="'.$backup_time.'" class="button button-remove no-decoration updraft-delete-link" href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" title="'.esc_attr(__('Delete this backup set', 'updraftplus')).'">'.__('Delete', 'updraftplus').'</a>
4631 </div>';
4632 }
4633
4634 public function log_button($backup) {
4635 global $updraftplus;
4636 $updraft_dir = $updraftplus->backups_dir_location();
4637 $ret = '';
4638 if (isset($backup['nonce']) && preg_match("/^[0-9a-f]{12}$/", $backup['nonce']) && is_readable($updraft_dir.'/log.'.$backup['nonce'].'.txt')) {
4639 $nval = $backup['nonce'];
4640 $lt = __('View Log', 'updraftplus');
4641 $url = esc_attr(UpdraftPlus_Options::admin_page()."?page=updraftplus&action=downloadlog&amp;updraftplus_backup_nonce=$nval");
4642 $ret .= <<<ENDHERE
4643 <div style="clear:none;" class="updraft-viewlogdiv">
4644 <a class="button no-decoration updraft-log-link" href="$url" data-jobid="$nval">
4645 $lt
4646 </a>
4647 <!--
4648 <form action="$url" method="get">
4649 <input type="hidden" name="action" value="downloadlog" />
4650 <input type="hidden" name="page" value="updraftplus" />
4651 <input type="hidden" name="updraftplus_backup_nonce" value="$nval" />
4652 <input type="submit" value="$lt" class="updraft-log-link" onclick="event.preventDefault(); updraft_popuplog('$nval');" />
4653 </form>
4654 -->
4655 </div>
4656 ENDHERE;
4657 return $ret;
4658 }
4659 }
4660
4661 /**
4662 * This function will check that a backup is complete depending on the parameters passed in.
4663 * A backup is complete in the case of a "clone" if it contains a db, plugins, themes, uploads and others.
4664 * A backup is complete in the case of a "full backup" when it contains everything the user has set in their options to be backed up.
4665 * It can also check if the backup is local on the filesystem.
4666 *
4667 * @param array $backup - the backup array we want to check
4668 * @param boolean $full_backup - a boolean to indicate if the backup should also be a full backup
4669 * @param boolean $clone - a boolean to indicate if the backup is for a clone, if so it does not need to be a full backup it only needs to include everything a clone can restore
4670 * @param boolean $local - a boolean to indicate if the backup should be present on the local file system or not
4671 *
4672 * @return boolean - returns true if the backup is complete and if specified is found on the local system otherwise false
4673 */
4674 public function check_backup_is_complete($backup, $full_backup, $clone, $local) {
4675
4676 global $updraftplus;
4677
4678 if (empty($backup)) return false;
4679
4680 if ($clone) {
4681 $entities = array('db' => '', 'plugins' => '', 'themes' => '', 'uploads' => '', 'others' => '');
4682 } else {
4683 $entities = $updraftplus->get_backupable_file_entities(true, true);
4684
4685 // Add the database to the entities array ready to loop over
4686 $entities['db'] = '';
4687
4688 foreach ($entities as $key => $info) {
4689 if (!UpdraftPlus_Options::get_updraft_option("updraft_include_$key", false)) {
4690 unset($entities[$key]);
4691 }
4692 }
4693 }
4694
4695 $updraft_dir = trailingslashit($updraftplus->backups_dir_location());
4696
4697 foreach ($entities as $type => $info) {
4698
4699 if ($full_backup) {
4700 if (UpdraftPlus_Options::get_updraft_option("updraft_include_$type", false) && !isset($backup[$type])) return false;
4701 }
4702
4703 if (!isset($backup[$type])) return false;
4704
4705 if ($local) {
4706 // Cast this to an array so that a warning is not thrown when we encounter a Database.
4707 foreach ((array) $backup[$type] as $value) {
4708 if (!file_exists($updraft_dir . DIRECTORY_SEPARATOR . $value)) return false;
4709 }
4710 }
4711 }
4712
4713 return true;
4714 }
4715
4716 /**
4717 * This function will set up the backup job data for when we are uploading a local backup to remote storage. It changes the initial jobdata so that UpdraftPlus knows about what files it's uploading and so that it skips directly to the upload stage.
4718 *
4719 * @param array $jobdata - the initial job data that we want to change
4720 * @param array $options - options sent from the front end includes backup timestamp and nonce
4721 *
4722 * @return array - the modified jobdata
4723 */
4724 public function upload_local_backup_jobdata($jobdata, $options) {
4725 global $updraftplus;
4726
4727 if (!is_array($jobdata)) return $jobdata;
4728
4729 $backup_history = UpdraftPlus_Backup_History::get_history();
4730 $services = !empty($options['services']) ? $options['services'] : array();
4731 $backup = $backup_history[$options['use_timestamp']];
4732
4733 /*
4734 The initial job data is not set up in a key value array instead it is set up so key "x" is the name of the key and then key "y" is the value.
4735 e.g array[0] = 'backup_name' array[1] = 'my_backup'
4736 */
4737 $jobstatus_key = array_search('jobstatus', $jobdata) + 1;
4738 $backup_time_key = array_search('backup_time', $jobdata) + 1;
4739 $backup_database_key = array_search('backup_database', $jobdata) + 1;
4740 $backup_files_key = array_search('backup_files', $jobdata) + 1;
4741 $service_key = array_search('service', $jobdata) + 1;
4742
4743 $db_backups = $jobdata[$backup_database_key];
4744 $db_backup_info = $updraftplus->update_database_jobdata($db_backups, $backup);
4745 $file_backups = $updraftplus->update_files_jobdata($backup);
4746
4747 // Next we need to build the services array using the remote storage destinations the user has selected to upload this backup set to
4748 $selected_services = array();
4749 if (is_array($services)) {
4750 foreach ($services as $storage_info) {
4751 $selected_services[] = $storage_info['value'];
4752 }
4753 } else {
4754 $selected_services = array($services);
4755 }
4756
4757 $jobdata[$jobstatus_key] = 'clouduploading';
4758 $jobdata[$backup_time_key] = $options['use_timestamp'];
4759 $jobdata[$backup_files_key] = 'finished';
4760 $jobdata[] = 'backup_files_array';
4761 $jobdata[] = $file_backups;
4762 $jobdata[] = 'blog_name';
4763 $jobdata[] = $db_backup_info['blog_name'];
4764 $jobdata[$backup_database_key] = $db_backup_info['db_backups'];
4765 $jobdata[] = 'local_upload';
4766 $jobdata[] = true;
4767 if (!empty($selected_services)) $jobdata[$service_key] = $selected_services;
4768
4769
4770 return $jobdata;
4771 }
4772
4773 /**
4774 * This function allows us to change the backup name, this is needed when uploading a local database backup to remote storage when the backup has come from another site.
4775 *
4776 * @param string $backup_name - the current name of the backup file
4777 * @param string $use_time - the current timestamp we are using
4778 * @param string $blog_name - the blog name of the current site
4779 *
4780 * @return string - the new filename or the original if the blog name from the job data is not set
4781 */
4782 public function upload_local_backup_name($backup_name, $use_time, $blog_name) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Filter use
4783 global $updraftplus;
4784
4785 $backup_blog_name = $updraftplus->jobdata_get('blog_name', '');
4786
4787 if ('' != $blog_name && '' != $backup_blog_name) {
4788 return str_replace($blog_name, $backup_blog_name, $backup_name);
4789 }
4790
4791 return $backup_name;
4792 }
4793
4794 /**
4795 * This function starts the updraftplus restore process it processes $_REQUEST
4796 * (keys: updraft_*, meta_foreign, backup_timestamp and job_id)
4797 *
4798 * @return void
4799 */
4800 public function prepare_restore() {
4801
4802 global $updraftplus;
4803
4804 // on restore start job_id is empty but if we needed file system permissions or this is a resumption then we have already started a job so reuse it
4805 $restore_job_id = empty($_REQUEST['job_id']) ? false : stripslashes($_REQUEST['job_id']);
4806
4807 if (false !== $restore_job_id && !preg_match('/^[0-9a-f]+$/', $restore_job_id)) die('Invalid request (restore_job_id).');
4808
4809 // Set up nonces, log files etc.
4810 $updraftplus->initiate_restore_job($restore_job_id);
4811
4812 // If this is the start of a restore then get the restore data from the posted data and put it into jobdata.
4813 if (isset($_REQUEST['action']) && 'updraft_restore' == $_REQUEST['action']) {
4814
4815 if (empty($restore_job_id)) {
4816 $jobdata_to_save = array();
4817 foreach ($_REQUEST as $key => $value) {
4818 if (false !== strpos($key, 'updraft_') || 'backup_timestamp' == $key || 'meta_foreign' == $key) {
4819 if ('updraft_restorer_restore_options' == $key) parse_str(stripslashes($value), $value);
4820 $jobdata_to_save[$key] = $value;
4821 }
4822 }
4823
4824 $selective_restore_types = array(
4825 'tables',
4826 'plugins',
4827 'themes'
4828 );
4829
4830 foreach ($selective_restore_types as $type) {
4831 if (isset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options']) && !empty($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options'])) {
4832
4833 $restore_entities_options = $jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options'];
4834
4835 $include_unspecified_entities = false;
4836 $entities_to_restore = array();
4837 $entities_to_skip = array();
4838
4839 foreach ($restore_entities_options as $entity) {
4840 if ('udp_all_other_'.$type == $entity) {
4841 $include_unspecified_entities = true;
4842 } elseif ('udp-skip-'.$type == substr($entity, 0, strlen('udp-skip-'.$type))) {
4843 $entities_to_skip[] = substr($entity, strlen('udp-skip-'.$type) + 1);
4844 } else {
4845 $entities_to_restore[] = $entity;
4846 }
4847 }
4848
4849 $jobdata_to_save['updraft_restorer_restore_options']['include_unspecified_'.$type] = $include_unspecified_entities;
4850 $jobdata_to_save['updraft_restorer_restore_options'][$type.'_to_restore'] = $entities_to_restore;
4851 $jobdata_to_save['updraft_restorer_restore_options'][$type.'_to_skip'] = $entities_to_skip;
4852 unset($jobdata_to_save['updraft_restorer_restore_options']['updraft_restore_'.$type.'_options']);
4853 }
4854 }
4855
4856 $updraftplus->jobdata_set_multi($jobdata_to_save);
4857
4858 // Use a site option, as otherwise on multisite when all the array of options is updated via UpdraftPlus_Options::update_site_option(), it will over-write any restored UD options from the backup
4859 update_site_option('updraft_restore_in_progress', $updraftplus->nonce);
4860 }
4861 }
4862
4863 // If this is the start of an ajax restore then end execution here so it can then be booted over ajax
4864 if (isset($_REQUEST['updraftplus_ajax_restore']) && 'start_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
4865 // return to prevent any more code from running
4866 return $this->prepare_ajax_restore();
4867
4868 } elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
4869 // If we enter here then in order to restore we needed to require the filesystem credentials we should save these before returning back to the browser and load them back after the AJAX call, this prevents us asking for the filesystem credentials again
4870 $filesystem_credentials = array(
4871 'hostname' => '',
4872 'username' => '',
4873 'password' => '',
4874 'connection_type' => '',
4875 'upgrade' => '',
4876 );
4877
4878 $credentials_found = false;
4879
4880 foreach ($_REQUEST as $key => $value) {
4881 if (array_key_exists($key, $filesystem_credentials)) {
4882 $filesystem_credentials[$key] = stripslashes($value);
4883 $credentials_found = true;
4884 }
4885 }
4886
4887 if ($credentials_found) $updraftplus->jobdata_set('filesystem_credentials', $filesystem_credentials);
4888
4889 // return to prevent any more code from running
4890 return $this->prepare_ajax_restore();
4891 }
4892
4893 if (!empty($_REQUEST['updraftplus_ajax_restore'])) add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 5);
4894
4895 $is_continuation = ('updraft_ajaxrestore_continue' == $_REQUEST['action']) ? true : false;
4896
4897 if ($is_continuation) {
4898 $restore_in_progress = get_site_option('updraft_restore_in_progress');
4899 if ($restore_in_progress != $_REQUEST['job_id']) {
4900 $abort_restore_already = true;
4901 $updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_mismatch)', 'error', 'job_id_mismatch');
4902 } else {
4903 $restore_jobdata = $updraftplus->jobdata_getarray($restore_in_progress);
4904 if (is_array($restore_jobdata) && isset($restore_jobdata['job_type']) && 'restore' == $restore_jobdata['job_type'] && isset($restore_jobdata['second_loop_entities']) && !empty($restore_jobdata['second_loop_entities']) && isset($restore_jobdata['job_time_ms']) && isset($restore_jobdata['backup_timestamp'])) {
4905 $backup_timestamp = $restore_jobdata['backup_timestamp'];
4906 $continuation_data = $restore_jobdata;
4907 $continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore';
4908 } else {
4909 $abort_restore_already = true;
4910 $updraftplus->log(__('Sufficient information about the in-progress restoration operation could not be found.', 'updraftplus') . ' (job_id_nojobdata)', 'error', 'job_id_nojobdata');
4911 }
4912 }
4913 } elseif (isset($_REQUEST['updraftplus_ajax_restore']) && 'do_ajax_restore' == $_REQUEST['updraftplus_ajax_restore']) {
4914 $backup_timestamp = $updraftplus->jobdata_get('backup_timestamp');
4915 $continuation_data = array('updraftplus_ajax_restore' => 'do_ajax_restore');
4916 } else {
4917 $backup_timestamp = (int) $_REQUEST['backup_timestamp'];
4918 $continuation_data = null;
4919 }
4920
4921 $filesystem_credentials = $updraftplus->jobdata_get('filesystem_credentials', array());
4922
4923 if (!empty($filesystem_credentials)) {
4924 $continuation_data['updraftplus_ajax_restore'] = 'continue_ajax_restore';
4925 // If the filesystem credentials are not empty then we now need to load these back into $_POST so that WP_Filesystem can access them
4926 foreach ($filesystem_credentials as $key => $value) {
4927 $_POST[$key] = $value;
4928 }
4929 }
4930
4931 if (empty($abort_restore_already)) {
4932 $backup_success = $this->restore_backup($backup_timestamp, $continuation_data);
4933 } else {
4934 $backup_success = false;
4935 }
4936
4937 if (empty($updraftplus->errors) && true === $backup_success) {
4938 // TODO: Deal with the case of some of the work having been deferred
4939 echo '<p class="updraft_restore_successful"><strong>';
4940 $updraftplus->log_e('Restore successful!');
4941 echo '</strong></p>';
4942 $updraftplus->log('Restore successful');
4943 $s_val = 1;
4944 if (!empty($this->entities_to_restore) && is_array($this->entities_to_restore)) {
4945 foreach ($this->entities_to_restore as $v) {
4946 if ('db' != $v) $s_val = 2;
4947 }
4948 }
4949 $pval = $updraftplus->have_addons ? 1 : 0;
4950
4951 echo '<strong>' . __('Actions', 'updraftplus') . ':</strong> <a href="' . UpdraftPlus_Options::admin_page_url() . '?page=updraftplus&updraft_restore_success=' . $s_val . '&pval=' . $pval . '">' . __('Return to UpdraftPlus configuration', 'updraftplus') . '</a>';
4952 return;
4953
4954 } elseif (is_wp_error($backup_success)) {
4955 echo '<p class="updraft_restore_error">';
4956 $updraftplus->log_e('Restore failed...');
4957 echo '</p>';
4958 $updraftplus->log_wp_error($backup_success);
4959 $updraftplus->log('Restore failed');
4960 echo '<div class="updraft_restore_errors">';
4961 $updraftplus->list_errors();
4962 echo '</div>';
4963 echo '<strong>' . __('Actions', 'updraftplus') . ':</strong> <a href="' . UpdraftPlus_Options::admin_page_url() . '?page=updraftplus">' . __('Return to UpdraftPlus configuration', 'updraftplus') . '</a>';
4964 return;
4965 } elseif (false === $backup_success) {
4966 // This means, "not yet - but stay on the page because we may be able to do it later, e.g. if the user types in the requested information"
4967 echo '<p class="updraft_restore_error">';
4968 $updraftplus->log_e('Restore failed...');
4969 echo '</p>';
4970 $updraftplus->log("Restore failed");
4971 echo '<div class="updraft_restore_errors">';
4972 $updraftplus->list_errors();
4973 echo '</div>';
4974 echo '<strong>' . __('Actions', 'updraftplus') . ':</strong> <a href="' . UpdraftPlus_Options::admin_page_url() . '?page=updraftplus">' . __('Return to UpdraftPlus configuration', 'updraftplus') . '</a>';
4975 return;
4976 }
4977 }
4978
4979 /**
4980 * This function will load the required ajax and output any relevant html for the ajax restore
4981 *
4982 * @return void
4983 */
4984 private function prepare_ajax_restore() {
4985 global $updraftplus;
4986
4987 $debug = $updraftplus->use_unminified_scripts();
4988 $enqueue_version = $debug ? $updraftplus->version . '.' . time() : $updraftplus->version;
4989 $updraft_min_or_not = $updraftplus->get_updraftplus_file_version();
4990 $ajax_action = isset($_REQUEST['updraftplus_ajax_restore']) && 'continue_ajax_restore' == $_REQUEST['updraftplus_ajax_restore'] && 'updraft_restore' != $_REQUEST['action'] ? 'updraft_ajaxrestore_continue' : 'updraft_ajaxrestore';
4991
4992 // get the entities info
4993 $jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce);
4994 $restore_components = $jobdata['updraft_restore'];
4995 usort($restore_components, array('UpdraftPlus_Manipulation_Functions', 'sort_restoration_entities'));
4996
4997 $backupable_entities = $updraftplus->get_backupable_file_entities(true, true);
4998 $pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $jobdata['backup_timestamp']), 'M d, Y G:i');
4999
5000 wp_enqueue_script('updraft-admin-restore', UPDRAFTPLUS_URL . '/js/updraft-admin-restore' . $updraft_min_or_not . '.js', array(), $enqueue_version);
5001
5002 $updraftplus->log("Restore setup, now closing connection and starting restore over AJAX.");
5003
5004 echo '<div class="updraft_restore_container">';
5005 echo '<div class="error" id="updraft-restore-hidethis">';
5006 echo '<p><strong>'. __('Warning: If you can still read these words after the page finishes loading, then there is a JavaScript or jQuery problem in the site.', 'updraftplus') .' '.__('This may prevent the restore procedure from being able to proceed.', 'updraftplus').'</strong>';
5007 echo ' <a href="'. apply_filters('updraftplus_com_link', "https://updraftplus.com/do-you-have-a-javascript-or-jquery-error/") .'" target="_blank">'. __('Go here for more information.', 'updraftplus') .'</a></p>';
5008 echo '</div>';
5009 echo '<div class="updraft_restore_main--header">'.__('UpdraftPlus Restoration', 'updraftplus').' - '.__('Backup', 'updraftplus').' '.$pretty_date.'</div>';
5010 echo '<div class="updraft_restore_main">';
5011
5012 if ($debug) echo '<input type="hidden" id="updraftplus_ajax_restore_debug" name="updraftplus_ajax_restore_debug" value="1">';
5013 echo '<input type="hidden" id="updraftplus_ajax_restore_job_id" name="updraftplus_restore_job_id" value="' . esc_attr($updraftplus->nonce) . '">';
5014 echo '<input type="hidden" id="updraftplus_ajax_restore_action" name="updraftplus_restore_action" value="' . esc_attr($ajax_action) . '">';
5015 echo '<div id="updraftplus_ajax_restore_progress" style="display: none;"></div>';
5016
5017 echo '<div class="updraft_restore_main--components">';
5018 echo ' <p>'.sprintf(__('The restore operation has begun (%s). Do not close this page until it reports itself as having finished.', 'updraftplus'), htmlspecialchars($updraftplus->nonce)).'</p>';
5019 echo ' <h2>'.__('Restoration progress:', 'updraftplus').'</h2>';
5020 echo ' <div class="updraft_restore_result"><span class="dashicons"></span><pan class="updraft_restore_result--text"></span></div>';
5021 echo ' <ul class="updraft_restore_components_list">';
5022 echo ' <li data-component="verifying" class="active"><span class="updraft_component--description">'.__('Verifying', 'updraftplus').'</span><span class="updraft_component--progress"></span></li>';
5023 foreach ($restore_components as $restore_component) {
5024 // Set Database description
5025 if ('db' == $restore_component && !isset($backupable_entities[$restore_component]['description'])) $backupable_entities[$restore_component]['description'] = __('Database', 'updraftplus');
5026 if (!isset($backupable_entities[$restore_component])) {
5027 die('Abort: invalid data');
5028 }
5029 echo ' <li data-component="'.esc_attr($restore_component).'"><span class="updraft_component--description">'.(isset($backupable_entities[$restore_component]['description']) ? htmlspecialchars($backupable_entities[$restore_component]['description']) : htmlspecialchars($restore_component)).'</span><span class="updraft_component--progress"></span></li>';
5030 }
5031 echo ' <li data-component="cleaning"><span class="updraft_component--description">'.__('Cleaning', 'updraftplus').'</span><span class="updraft_component--progress"></span></li>';
5032 echo ' <li data-component="finished"><span class="updraft_component--description">'.__('Finished', 'updraftplus').'</span><span class="updraft_component--progress"></span></li>';
5033 echo ' </ul>'; // end ul.updraft_restore_components_list
5034 // Provide download link for the log file
5035 echo ' <p><a target="_blank" href="?action=downloadlog&page=updraftplus&updraftplus_backup_nonce='.esc_attr($updraftplus->nonce).'">'.__('Follow this link to download the log file for this restoration (needed for any support requests).', 'updraftplus').'</a></p>';
5036 echo '</div>'; // end .updraft_restore_main--components
5037 echo '<div class="updraft_restore_main--activity">';
5038 echo ' <h2 class="updraft_restore_main--activity-title">'.__('Activity log', 'updraftplus').' <i id="activity-full-log" title="'.__('Full-screen', 'updraftplus').'" class="dashicons dashicons-fullscreen-alt" style="float: right; cursor: pointer; margin-left: 7px;"></i> <span id="updraftplus_ajax_restore_last_activity"></span></h2>';
5039 echo ' <div id="updraftplus_ajax_restore_output"></div>';
5040 echo '</div>'; // end .updraft_restore_main--activity
5041 echo '
5042 <div class="updraft-restore--footer">
5043 <ul class="updraft-restore--stages">
5044 <li><span>'.__('1. Component selection', 'updraftplus').'</span></li>
5045 <li><span>'.__('2. Verifications', 'updraftplus').'</span></li>
5046 <li class="active"><span>'.__('3. Restoration', 'updraftplus').'</span></li>
5047 </ul>
5048 </div>';
5049 echo '</div>'; // end .updraft_restore_main
5050 echo '</div>'; // end .updraft_restore_container
5051 }
5052
5053 /**
5054 * Processes the jobdata to build an array of entities to restore.
5055 *
5056 * @param Array $backup_set - information on the backup to restore
5057 *
5058 * @return Array - the entities to restore built from the restore jobdata
5059 */
5060 private function get_entities_to_restore_from_jobdata($backup_set) {
5061
5062 global $updraftplus;
5063
5064 $updraft_restore = $updraftplus->jobdata_get('updraft_restore');
5065
5066 if (empty($updraft_restore) || (!is_array($updraft_restore))) $updraft_restore = array();
5067
5068 $entities_to_restore = array();
5069 $foreign_known = apply_filters('updraftplus_accept_archivename', array());
5070
5071 foreach ($updraft_restore as $entity) {
5072 if (empty($backup_set['meta_foreign'])) {
5073 $entities_to_restore[$entity] = $entity;
5074 } else {
5075 if ('db' == $entity && !empty($foreign_known[$backup_set['meta_foreign']]) && !empty($foreign_known[$backup_set['meta_foreign']]['separatedb'])) {
5076 $entities_to_restore[$entity] = 'db';
5077 } else {
5078 $entities_to_restore[$entity] = 'wpcore';
5079 }
5080 }
5081 }
5082
5083 return $entities_to_restore;
5084 }
5085
5086 /**
5087 * Processes the jobdata to build an array of restoration options
5088 *
5089 * @return Array - the restore options built from the restore jobdata
5090 */
5091 private function get_restore_options_from_jobdata() {
5092
5093 global $updraftplus;
5094
5095 $restore_options = $updraftplus->jobdata_get('updraft_restorer_restore_options');
5096 $updraft_encryptionphrase = $updraftplus->jobdata_get('updraft_encryptionphrase');
5097 $include_wpconfig = $updraftplus->jobdata_get('updraft_restorer_wpcore_includewpconfig');
5098
5099 $restore_options['updraft_encryptionphrase'] = empty($updraft_encryptionphrase) ? '' : $updraft_encryptionphrase;
5100
5101 $restore_options['updraft_restorer_wpcore_includewpconfig'] = !empty($include_wpconfig);
5102
5103 $restore_options['updraft_incremental_restore_point'] = empty($restore_options['updraft_incremental_restore_point']) ? -1 : (int) $restore_options['updraft_incremental_restore_point'];
5104
5105 return $restore_options;
5106 }
5107
5108 /**
5109 * Carry out the restore process within the WP admin dashboard, using data from $_POST
5110 *
5111 * @param Integer $timestamp Identifying the backup to be restored
5112 * @param Array|null $continuation_data For continuing a multi-stage restore; this is the saved jobdata for the job; in this method the keys used are second_loop_entities, restore_options; but it is also passed on to Updraft_Restorer::perform_restore()
5113 * @return Boolean|WP_Error - a WP_Error indicates a terminal failure; false indicates not-yet complete (not necessarily terminal); true indicates complete.
5114 */
5115 private function restore_backup($timestamp, $continuation_data = null) {
5116
5117 global $updraftplus, $updraftplus_restorer;
5118
5119 $second_loop_entities = empty($continuation_data['second_loop_entities']) ? array() : $continuation_data['second_loop_entities'];
5120
5121 // If this is a resumption and we still need to restore the database we should rebuild the backup history to ensure the database is in there.
5122 if (!empty($second_loop_entities['db'])) UpdraftPlus_Backup_History::rebuild();
5123
5124 $backup_set = UpdraftPlus_Backup_History::get_history($timestamp);
5125
5126 if (empty($backup_set)) {
5127 echo '<p>'.__('This backup does not exist in the backup history - restoration aborted. Timestamp:', 'updraftplus').' '.htmlspecialchars($timestamp).'</p><br>';
5128 return new WP_Error('does_not_exist', __('Backup does not exist in the backup history', 'updraftplus')." ($timestamp)");
5129 }
5130
5131 $backup_set['timestamp'] = $timestamp;
5132
5133 $url_parameters = array(
5134 'backup_timestamp' => $timestamp,
5135 'job_id' => $updraftplus->nonce
5136 );
5137
5138 if (!empty($continuation_data['updraftplus_ajax_restore'])) {
5139 $url_parameters['updraftplus_ajax_restore'] = 'continue_ajax_restore';
5140 $updraftplus->output_to_browser(''); // Start timer
5141 // Force output buffering off so that we get log lines sent to the browser as they come not all at once at the end of the ajax restore
5142 // zlib creates an output buffer, and waits for the entire page to be generated before it can send it to the client try to turn it off
5143 @ini_set("zlib.output_compression", 0);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
5144 // Turn off PHP output buffering for NGINX
5145 header('X-Accel-Buffering: no');
5146 header('Content-Encoding: none');
5147 while (ob_get_level()) {
5148 ob_end_flush();
5149 }
5150 ob_implicit_flush(1);
5151 }
5152
5153 $updraftplus->log("Ensuring WP_Filesystem is setup for a restore");
5154
5155 // This will print HTML and die() if necessary
5156 UpdraftPlus_Filesystem_Functions::ensure_wp_filesystem_set_up_for_restore($url_parameters);
5157
5158 $updraftplus->log("WP_Filesystem is setup and ready for a restore");
5159
5160 $entities_to_restore = $this->get_entities_to_restore_from_jobdata($backup_set);
5161
5162 if (empty($entities_to_restore)) {
5163 $restore_jobdata = $updraftplus->jobdata_getarray($updraftplus->nonce);
5164 echo '<p>'.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'</p><p>'.__('If making a request for support, please include this information:', 'updraftplus').' '.count($restore_jobdata).' : '.htmlspecialchars(serialize($restore_jobdata)).'</p>';
5165 return new WP_Error('missing_info', 'Backup information not found');
5166 }
5167
5168 // This is used in painting the admin page after a successful restore
5169 $this->entities_to_restore = $entities_to_restore;
5170
5171 // This will be removed by Updraft_Restorer::post_restore_clean_up()
5172 set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
5173
5174 // Set $restore_options, either from the continuation data, or from $_POST
5175 if (!empty($continuation_data['restore_options'])) {
5176 $restore_options = $continuation_data['restore_options'];
5177 } else {
5178 // Gather the restore options into one place - code after here should read the options
5179 $restore_options = $this->get_restore_options_from_jobdata();
5180 $updraftplus->jobdata_set('restore_options', $restore_options);
5181 }
5182
5183 add_action('updraftplus_restoration_title', array($this, 'restoration_title'));
5184
5185 $updraftplus->log_restore_update(array('type' => 'state', 'stage' => 'started', 'data' => array()));
5186
5187 // We use a single object for each entity, because we want to store information about the backup set
5188 $updraftplus_restorer = new Updraft_Restorer(new Updraft_Restorer_Skin, $backup_set, false, $restore_options, $continuation_data);
5189
5190 $restore_result = $updraftplus_restorer->perform_restore($entities_to_restore, $restore_options);
5191
5192 $updraftplus_restorer->post_restore_clean_up($restore_result);
5193
5194 $pval = $updraftplus->have_addons ? 1 : 0;
5195 $sval = (true === $restore_result) ? 1 : 0;
5196
5197 $pages = get_pages(array('number' => 2));
5198 $page_urls = array(
5199 'home' => get_home_url(),
5200 );
5201
5202 foreach ($pages as $page_info) {
5203 $page_urls[$page_info->post_name] = get_page_link($page_info->ID);
5204 }
5205
5206 $updraftplus->log_restore_update(
5207 array(
5208 'type' => 'state',
5209 'stage' => 'finished',
5210 'data' => array(
5211 'actions' => array(
5212 __('Return to UpdraftPlus configuration', 'updraftplus') => UpdraftPlus_Options::admin_page_url() . '?page=updraftplus&updraft_restore_success=' . $sval . '&pval=' . $pval
5213 ),
5214 'urls' => $page_urls,
5215 )
5216 )
5217 );
5218
5219 return $restore_result;
5220 }
5221
5222 /**
5223 * Called when the restore process wants to print a title
5224 *
5225 * @param String $title - title
5226 */
5227 public function restoration_title($title) {
5228 echo '<h2>'.$title.'</h2>';
5229 }
5230
5231 /**
5232 * Logs a line from the restore process, being called from UpdraftPlus::log().
5233 * Hooks the WordPress filter updraftplus_logline
5234 * In future, this can get more sophisticated. For now, things are funnelled through here, giving the future possibility.
5235 *
5236 * @param String $line - the line to be logged
5237 * @param String $nonce - the job ID of the restore job
5238 * @param String $level - the level of the log notice
5239 * @param String|Boolean $uniq_id - a unique ID for the log if it should only be logged once; or false otherwise
5240 * @param String $destination - the type of job ongoing. If it is not 'restore', then we will skip the logging.
5241 *
5242 * @return String|Boolean - the filtered value. If set to false, then UpdraftPlus::log() will stop processing the log line.
5243 */
5244 public function updraftplus_logline($line, $nonce, $level, $uniq_id, $destination) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
5245
5246 if ('progress' != $destination || (defined('WP_CLI') && WP_CLI) || false === $line || false === strpos($line, 'RINFO:')) return $line;
5247
5248 global $updraftplus;
5249
5250 $updraftplus->output_to_browser($line);
5251
5252 // Indicate that we have completely handled all logging needed
5253 return false;
5254 }
5255
5256 /**
5257 * Ensure that what is returned is an array. Used as a WP options filter.
5258 *
5259 * @param Array $input - input
5260 *
5261 * @return Array
5262 */
5263 public function return_array($input) {
5264 return is_array($input) ? $input : array();
5265 }
5266
5267 /**
5268 * Called upon the WP action wp_ajax_updraft_savesettings. Will die().
5269 */
5270 public function updraft_ajax_savesettings() {
5271 try {
5272 if (empty($_POST) || empty($_POST['subaction']) || 'savesettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
5273
5274 if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');
5275
5276 parse_str(stripslashes($_POST['settings']), $posted_settings);
5277 // We now have $posted_settings as an array
5278 if (!empty($_POST['updraftplus_version'])) $posted_settings['updraftplus_version'] = $_POST['updraftplus_version'];
5279
5280 echo json_encode($this->save_settings($posted_settings));
5281 } catch (Exception $e) {
5282 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
5283 error_log($log_message);
5284 echo json_encode(array(
5285 'fatal_error' => true,
5286 'fatal_error_message' => $log_message
5287 ));
5288 // @codingStandardsIgnoreLine
5289 } catch (Error $e) {
5290 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during save settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
5291 error_log($log_message);
5292 echo json_encode(array(
5293 'fatal_error' => true,
5294 'fatal_error_message' => $log_message
5295 ));
5296 }
5297 die;
5298 }
5299
5300 public function updraft_ajax_importsettings() {
5301 try {
5302 if (empty($_POST) || empty($_POST['subaction']) || 'importsettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
5303
5304 if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');
5305
5306 $this->import_settings($_POST);
5307 } catch (Exception $e) {
5308 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
5309 error_log($log_message);
5310 echo json_encode(array(
5311 'fatal_error' => true,
5312 'fatal_error_message' => $log_message
5313 ));
5314 // @codingStandardsIgnoreLine
5315 } catch (Error $e) {
5316 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during import settings. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
5317 error_log($log_message);
5318 echo json_encode(array(
5319 'fatal_error' => true,
5320 'fatal_error_message' => $log_message
5321 ));
5322 }
5323 }
5324
5325 /**
5326 * This method handles the imported json settings it will convert them into a readable format for the existing save settings function, it will also update some of the options to match the new remote storage options format (Apr 2017)
5327 *
5328 * @param Array $settings - The settings from the imported json file
5329 */
5330 public function import_settings($settings) {
5331 // A bug in UD releases around 1.12.40 - 1.13.3 meant that it was saved in URL-string format, instead of JSON
5332 $perhaps_not_yet_parsed = json_decode(stripslashes($settings['settings']), true);
5333
5334 if (!is_array($perhaps_not_yet_parsed)) {
5335 parse_str($perhaps_not_yet_parsed, $posted_settings);
5336 } else {
5337 $posted_settings = $perhaps_not_yet_parsed;
5338 }
5339
5340 if (!empty($settings['updraftplus_version'])) $posted_settings['updraftplus_version'] = $settings['updraftplus_version'];
5341
5342 // Handle the settings name change of WebDAV and SFTP (Apr 2017) if someone tries to import an old settings to this version
5343 if (isset($posted_settings['updraft_webdav_settings'])) {
5344 $posted_settings['updraft_webdav'] = $posted_settings['updraft_webdav_settings'];
5345 unset($posted_settings['updraft_webdav_settings']);
5346 }
5347
5348 if (isset($posted_settings['updraft_sftp_settings'])) {
5349 $posted_settings['updraft_sftp'] = $posted_settings['updraft_sftp_settings'];
5350 unset($posted_settings['updraft_sftp_settings']);
5351 }
5352
5353 // We also need to wrap some of the options in the new style settings array otherwise later on we will lose the settings if this information is missing
5354 if (empty($posted_settings['updraft_webdav']['settings'])) $posted_settings['updraft_webdav'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_webdav']);
5355 if (empty($posted_settings['updraft_googledrive']['settings'])) $posted_settings['updraft_googledrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googledrive']);
5356 if (empty($posted_settings['updraft_googlecloud']['settings'])) $posted_settings['updraft_googlecloud'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_googlecloud']);
5357 if (empty($posted_settings['updraft_onedrive']['settings'])) $posted_settings['updraft_onedrive'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_onedrive']);
5358 if (empty($posted_settings['updraft_azure']['settings'])) $posted_settings['updraft_azure'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_azure']);
5359 if (empty($posted_settings['updraft_dropbox']['settings'])) $posted_settings['updraft_dropbox'] = UpdraftPlus_Storage_Methods_Interface::wrap_remote_storage_options($posted_settings['updraft_dropbox']);
5360
5361 echo json_encode($this->save_settings($posted_settings));
5362
5363 die;
5364 }
5365
5366 /**
5367 * This function will get a list of remote storage methods with valid connection details and create a HTML list of checkboxes
5368 *
5369 * @return String - HTML checkbox list of remote storage methods with valid connection details
5370 */
5371 private function backup_now_remote_message() {
5372 global $updraftplus;
5373
5374 $services = (array) $updraftplus->just_one($updraftplus->get_canonical_service_list());
5375 $all_services = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids($services);
5376 $active_remote_storage_list = '';
5377
5378 foreach ($all_services as $method => $sinfo) {
5379 if ('email' == $method) {
5380 $possible_emails = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'));
5381 if (!empty($possible_emails)) {
5382 $active_remote_storage_list .= '<input class="updraft_remote_service_entity" id="'.$method.'updraft_service" checked="checked" type="checkbox" name="updraft_include_remote_service_'. $method . '" value=""> <label for="'.$method.'updraft_service">'.$updraftplus->backup_methods[$method].'</label><br>';
5383 }
5384 } elseif (empty($sinfo['object']) || empty($sinfo['instance_settings']) || !is_callable(array($sinfo['object'], 'options_exist'))) {
5385 continue;
5386 }
5387
5388 $instance_count = 1;
5389 foreach ($sinfo['instance_settings'] as $instance => $opt) {
5390 if ($sinfo['object']->options_exist($opt)) {
5391 $instance_count_label = (1 == $instance_count) ? '' : ' ('.$instance_count.')';
5392 $label = empty($opt['instance_label']) ? $sinfo['object']->get_description() . $instance_count_label : $opt['instance_label'];
5393 if (!isset($opt['instance_enabled'])) $opt['instance_enabled'] = 1;
5394 $checked = empty($opt['instance_enabled']) ? '' : 'checked="checked"';
5395 $active_remote_storage_list .= '<input class="updraft_remote_service_entity" id="'.$method.'updraft_service_'.$instance.'" ' . $checked . ' type="checkbox" name="updraft_include_remote_service_'. $method . '" value="'.$instance.'"> <label for="'.$method.'updraft_service_'.$instance.'">'.$label.'</label><br>';
5396 $instance_count++;
5397 }
5398 }
5399 }
5400
5401 $service = $updraftplus->just_one(UpdraftPlus_Options::get_updraft_option('updraft_service'));
5402 if (is_string($service)) $service = array($service);
5403 if (!is_array($service)) $service = array();
5404
5405 $no_remote_configured = (empty($service) || array('none') === $service || array('') === $service) ? true : false;
5406
5407 if ($no_remote_configured && empty($active_remote_storage_list)) {
5408 return '<input type="checkbox" disabled="disabled" id="backupnow_includecloud"> <em>'.sprintf(__("Backup won't be sent to any remote storage - none has been saved in the %s", 'updraftplus'), '<a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&amp;tab=settings" id="updraft_backupnow_gotosettings">'.__('settings', 'updraftplus')).'</a>. '.__('Not got any remote storage?', 'updraftplus').' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/landing/vault/").'" target="_blank">'.__("Check out UpdraftPlus Vault.", 'updraftplus').'</a></em>';
5409 }
5410
5411 if (empty($active_remote_storage_list)) {
5412 $active_remote_storage_list = '<p>'.__('No remote storage locations with valid options found.', 'updraftplus').'</p>';
5413 }
5414
5415 return '<input type="checkbox" id="backupnow_includecloud" checked="checked"> <label for="backupnow_includecloud">'.__("Send this backup to remote storage", 'updraftplus').'</label> (<a href="'.esc_url(UpdraftPlus::get_current_clean_url()).'" id="backupnow_includecloud_showmoreoptions">...</a>)<br><div id="backupnow_includecloud_moreoptions" class="updraft-hidden" style="display:none;"><em>'. __('The following remote storage options are configured.', 'updraftplus').'</em><br>'.$active_remote_storage_list.'</div>';
5416 }
5417
5418 /**
5419 * This method works through the passed in settings array and saves the settings to the database clearing old data and setting up a return array with content to update the page via ajax
5420 *
5421 * @param array $settings An array of settings taking from the admin page ready to be saved to the database
5422 * @return array An array response containing the status of the update along with content to be used to update the admin page.
5423 */
5424 public function save_settings($settings) {
5425
5426 global $updraftplus;
5427
5428 // Make sure that settings filters are registered
5429 UpdraftPlus_Options::admin_init();
5430
5431 $more_files_path_updated = false;
5432
5433 if (isset($settings['updraftplus_version']) && $updraftplus->version == $settings['updraftplus_version']) {
5434
5435 $return_array = array('saved' => true);
5436
5437 $add_to_post_keys = array('updraft_interval', 'updraft_interval_database', 'updraft_interval_increments', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_files', 'updraft_startday_db');
5438
5439 // If database and files are on same schedule, override the db day/time settings
5440 if (isset($settings['updraft_interval_database']) && isset($settings['updraft_interval_database']) && $settings['updraft_interval_database'] == $settings['updraft_interval'] && isset($settings['updraft_starttime_files'])) {
5441 $settings['updraft_starttime_db'] = $settings['updraft_starttime_files'];
5442 $settings['updraft_startday_db'] = $settings['updraft_startday_files'];
5443 }
5444 foreach ($add_to_post_keys as $key) {
5445 // For add-ons that look at $_POST to find saved settings, add the relevant keys to $_POST so that they find them there
5446 if (isset($settings[$key])) {
5447 $_POST[$key] = $settings[$key];
5448 }
5449 }
5450
5451 // Check if updraft_include_more_path is set, if it is then we need to update the page, if it's not set but there's content already in the database that is cleared down below so again we should update the page.
5452 $more_files_path_updated = false;
5453
5454 // i.e. If an option has been set, or if it was currently active in the settings
5455 if (isset($settings['updraft_include_more_path']) || UpdraftPlus_Options::get_updraft_option('updraft_include_more_path')) {
5456 $more_files_path_updated = true;
5457 }
5458
5459 // Wipe the extra retention rules, as they are not saved correctly if the last one is deleted
5460 UpdraftPlus_Options::update_updraft_option('updraft_retain_extrarules', array());
5461 UpdraftPlus_Options::update_updraft_option('updraft_email', array());
5462 UpdraftPlus_Options::update_updraft_option('updraft_report_warningsonly', array());
5463 UpdraftPlus_Options::update_updraft_option('updraft_report_wholebackup', array());
5464 UpdraftPlus_Options::update_updraft_option('updraft_extradbs', array());
5465 UpdraftPlus_Options::update_updraft_option('updraft_include_more_path', array());
5466
5467 $relevant_keys = $updraftplus->get_settings_keys();
5468
5469 if (isset($settings['updraft_auto_updates']) && in_array('updraft_auto_updates', $relevant_keys)) {
5470 $updraftplus->set_automatic_updates($settings['updraft_auto_updates']);
5471 unset($settings['updraft_auto_updates']); // unset the key and its value to prevent being processed the second time
5472 }
5473
5474 if (method_exists('UpdraftPlus_Options', 'mass_options_update')) {
5475 $original_settings = $settings;
5476 $settings = UpdraftPlus_Options::mass_options_update($settings);
5477 $mass_updated = true;
5478 }
5479
5480 foreach ($settings as $key => $value) {
5481
5482 if (in_array($key, $relevant_keys)) {
5483 if ('updraft_service' == $key && is_array($value)) {
5484 foreach ($value as $subkey => $subvalue) {
5485 if ('0' == $subvalue) unset($value[$subkey]);
5486 }
5487 }
5488
5489 // This flag indicates that either the stored database option was changed, or that the supplied option was changed before being stored. It isn't comprehensive - it's only used to update some UI elements with invalid input.
5490 $updated = empty($mass_updated) ? (is_string($value) && UpdraftPlus_Options::get_updraft_option($key) != $value) : (is_string($value) && (!isset($original_settings[$key]) || $original_settings[$key] != $value));
5491
5492 if (empty($mass_updated)) UpdraftPlus_Options::update_updraft_option($key, $value);
5493
5494 // Add information on what has changed to array to loop through to update links etc.
5495 // Restricting to strings for now, to prevent any unintended leakage (since this is just used for UI updating)
5496 if ($updated) {
5497 $value = UpdraftPlus_Options::get_updraft_option($key);
5498 if (is_string($value)) $return_array['changed'][$key] = $value;
5499 }
5500 // @codingStandardsIgnoreLine
5501 } else {
5502 // This section is ignored by CI otherwise it will complain the ELSE is empty.
5503
5504 // When last active, it was catching: option_page, action, _wpnonce, _wp_http_referer, updraft_s3_endpoint, updraft_dreamobjects_endpoint. The latter two are empty; probably don't need to be in the page at all.
5505 // error_log("Non-UD key when saving from POSTed data: ".$key);
5506 }
5507 }
5508 } else {
5509 $return_array = array('saved' => false, 'error_message' => sprintf(__('UpdraftPlus seems to have been updated to version (%s), which is different to the version running when this settings page was loaded. Please reload the settings page before trying to save settings.', 'updraftplus'), $updraftplus->version));
5510 }
5511
5512 // Checking for various possible messages
5513 $updraft_dir = $updraftplus->backups_dir_location(false);
5514 $really_is_writable = UpdraftPlus_Filesystem_Functions::really_is_writable($updraft_dir);
5515 $dir_info = $this->really_writable_message($really_is_writable, $updraft_dir);
5516 $button_title = esc_attr(__('This button is disabled because your backup directory is not writable (see the settings).', 'updraftplus'));
5517
5518 $return_array['backup_now_message'] = $this->backup_now_remote_message();
5519
5520 $return_array['backup_dir'] = array('writable' => $really_is_writable, 'message' => $dir_info, 'button_title' => $button_title);
5521
5522 // Check if $more_files_path_updated is true, is so then there's a change and we should update the backup modal
5523 if ($more_files_path_updated) {
5524 $return_array['updraft_include_more_path'] = $this->files_selector_widgetry('backupnow_files_', false, 'sometimes');
5525 }
5526
5527 // Because of the single AJAX call, we need to remove the existing UD messages from the 'all_admin_notices' action
5528 remove_all_actions('all_admin_notices');
5529
5530 // Moving from 2 to 1 ajax call
5531 ob_start();
5532
5533 $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
5534
5535 $this->setup_all_admin_notices_global($service);
5536 $this->setup_all_admin_notices_udonly($service);
5537
5538 do_action('all_admin_notices');
5539
5540 if (!$really_is_writable) { // Check if writable
5541 $this->show_admin_warning_unwritable();
5542 }
5543
5544 if ($return_array['saved']) { //
5545 $this->show_admin_warning(__('Your settings have been saved.', 'updraftplus'), 'updated fade');
5546 } else {
5547 if (isset($return_array['error_message'])) {
5548 $this->show_admin_warning($return_array['error_message'], 'error');
5549 } else {
5550 $this->show_admin_warning(__('Your settings failed to save. Please refresh the settings page and try again', 'updraftplus'), 'error');
5551 }
5552 }
5553
5554 $messages_output = ob_get_contents();
5555
5556 ob_clean();
5557
5558 // Backup schedule output
5559 $this->next_scheduled_backups_output('line');
5560
5561 $scheduled_output = ob_get_clean();
5562
5563 $return_array['messages'] = $messages_output;
5564 $return_array['scheduled'] = $scheduled_output;
5565 $return_array['files_scheduled'] = $this->next_scheduled_files_backups_output(true);
5566 $return_array['database_scheduled'] = $this->next_scheduled_database_backups_output(true);
5567
5568
5569 // Add the updated options to the return message, so we can update on screen
5570 return $return_array;
5571
5572 }
5573
5574 /**
5575 * Authenticate remote storage instance
5576 *
5577 * @param array - $data It consists of below key elements:
5578 * $remote_method - Remote storage service
5579 * $instance_id - Remote storage instance id
5580 * @return array An array response containing the status of the authentication
5581 */
5582 public function auth_remote_method($data) {
5583 global $updraftplus;
5584
5585 $response = array();
5586
5587 if (isset($data['remote_method']) && isset($data['instance_id'])) {
5588 $response['result'] = 'success';
5589 $remote_method = $data['remote_method'];
5590 $instance_id = $data['instance_id'];
5591
5592 $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method));
5593
5594 try {
5595 $storage_objects_and_ids[$remote_method]['object']->authenticate_storage($instance_id);
5596 } catch (Exception $e) {
5597 $response['result'] = 'error';
5598 $response['message'] = $updraftplus->backup_methods[$remote_method] . ' ' . __('authentication error', 'updraftplus') . ' ' . $e->getMessage();
5599 }
5600 } else {
5601 $response['result'] = 'error';
5602 $response['message'] = __('Remote storage method and instance id are required for authentication.', 'updraftplus');
5603 }
5604
5605 return $response;
5606 }
5607
5608 /**
5609 * Deauthenticate remote storage instance
5610 *
5611 * @param array - $data It consists of below key elements:
5612 * $remote_method - Remote storage service
5613 * $instance_id - Remote storage instance id
5614 * @return array An array response containing the status of the deauthentication
5615 */
5616 public function deauth_remote_method($data) {
5617 global $updraftplus;
5618
5619 $response = array();
5620
5621 if (isset($data['remote_method']) && isset($data['instance_id'])) {
5622 $response['result'] = 'success';
5623 $remote_method = $data['remote_method'];
5624 $instance_id = $data['instance_id'];
5625
5626 $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array($remote_method));
5627
5628 try {
5629 $storage_objects_and_ids[$remote_method]['object']->deauthenticate_storage($instance_id);
5630 } catch (Exception $e) {
5631 $response['result'] = 'error';
5632 $response['message'] = $updraftplus->backup_methods[$remote_method] . ' deauthentication error ' . $e->getMessage();
5633 }
5634 } else {
5635 $response['result'] = 'error';
5636 $response['message'] = 'Remote storage method and instance id are required for deauthentication.';
5637 }
5638
5639 return $response;
5640 }
5641
5642 /**
5643 * A method to remove UpdraftPlus settings from the options table.
5644 *
5645 * @param boolean $wipe_all_settings Set to true as default as we want to remove all options, set to false if calling from UpdraftCentral, as we do not want to remove the UpdraftCentral key or we will lose connection to the site.
5646 * @return boolean
5647 */
5648 public function wipe_settings($wipe_all_settings = true) {
5649
5650 global $updraftplus;
5651
5652 $settings = $updraftplus->get_settings_keys();
5653
5654 // if this is false the UDC has called it we don't want to remove the UDC key other wise we will lose connection to the remote site.
5655 if (false == $wipe_all_settings) {
5656 $key = array_search('updraft_central_localkeys', $settings);
5657 unset($settings[$key]);
5658 }
5659
5660 foreach ($settings as $s) UpdraftPlus_Options::delete_updraft_option($s);
5661
5662 if (is_multisite()) $updraftplus->wipe_state_data(true, 'sitemeta');
5663 $updraftplus->wipe_state_data(true);
5664
5665 $site_options = array('updraft_oneshotnonce');
5666 foreach ($site_options as $s) delete_site_option($s);
5667
5668 $this->show_admin_warning(__("Your settings have been wiped.", 'updraftplus'));
5669
5670 return true;
5671 }
5672
5673 /**
5674 * This get the details for updraft vault and to be used globally
5675 *
5676 * @param string $instance_id - the instance_id of the current instance being used
5677 * @return object - the UpdraftVault option setup to use the passed in instance id or if one wasn't passed then use the default set of options
5678 */
5679 public function get_updraftvault($instance_id = '') {
5680 $storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array('updraftvault'));
5681
5682 if (isset($storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id])) {
5683 $opts = $storage_objects_and_ids['updraftvault']['instance_settings'][$instance_id];
5684 $vault = $storage_objects_and_ids['updraftvault']['object'];
5685 $vault->set_options($opts, false, $instance_id);
5686 } else {
5687 include_once(UPDRAFTPLUS_DIR.'/methods/updraftvault.php');
5688 $vault = new UpdraftPlus_BackupModule_updraftvault();
5689 }
5690
5691 return $vault;
5692 }
5693
5694 /**
5695 * http_get will allow the HTTP Fetch execute available in advanced tools
5696 *
5697 * @param String $uri Specific URL passed to curl
5698 * @param Boolean $curl True or False if cURL is to be used
5699 * @return String - JSON encoded results
5700 */
5701 public function http_get($uri = null, $curl = false) {
5702
5703 if (!preg_match('/^https?/', $uri)) return json_encode(array('e' => 'Non-http URL specified'));
5704
5705 if ($curl) {
5706 if (!function_exists('curl_exec')) {
5707 return json_encode(array('e' => 'No Curl installed'));
5708 die;
5709 }
5710 $ch = curl_init();
5711 curl_setopt($ch, CURLOPT_URL, $uri);
5712 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
5713 curl_setopt($ch, CURLOPT_FAILONERROR, true);
5714 curl_setopt($ch, CURLOPT_HEADER, false);
5715 curl_setopt($ch, CURLOPT_VERBOSE, true);
5716 curl_setopt($ch, CURLOPT_STDERR, $output = fopen('php://temp', "w+"));
5717 $response = curl_exec($ch);
5718 $error = curl_error($ch);
5719 $getinfo = curl_getinfo($ch);
5720 curl_close($ch);
5721
5722 rewind($output);
5723 $verb = stream_get_contents($output);
5724
5725 $resp = array();
5726 if (false === $response) {
5727 $resp['e'] = htmlspecialchars($error);
5728 }
5729 $resp['r'] = (empty($response)) ? '' : htmlspecialchars(substr($response, 0, 2048));
5730
5731 if (!empty($verb)) $resp['r'] = htmlspecialchars($verb)."\n\n".$resp['r'];
5732
5733 // Extra info returned for Central
5734 $resp['verb'] = $verb;
5735 $resp['response'] = $response;
5736 $resp['status'] = $getinfo;
5737
5738 return json_encode($resp);
5739 } else {
5740 $response = wp_remote_get($uri, array('timeout' => 10));
5741 if (is_wp_error($response)) {
5742 return json_encode(array('e' => htmlspecialchars($response->get_error_message())));
5743 }
5744 return json_encode(
5745 array(
5746 'r' => wp_remote_retrieve_response_code($response).': '.htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)),
5747 'code' => wp_remote_retrieve_response_code($response),
5748 'html_response' => htmlspecialchars(substr(wp_remote_retrieve_body($response), 0, 2048)),
5749 'response' => $response
5750 )
5751 );
5752 }
5753 }
5754
5755 /**
5756 * This will return all the details for raw backup and file list, in HTML format
5757 *
5758 * @param Boolean $no_pre_tags - if set, then <pre></pre> tags will be removed from the output
5759 *
5760 * @return String
5761 */
5762 public function show_raw_backups($no_pre_tags = false) {
5763 global $updraftplus;
5764
5765 $response = array();
5766
5767 $response['html'] = '<h3 id="ud-debuginfo-rawbackups">'.__('Known backups (raw)', 'updraftplus').'</h3><pre>';
5768 ob_start();
5769 $history = UpdraftPlus_Backup_History::get_history();
5770 var_dump($history);
5771 $response["html"] .= ob_get_clean();
5772 $response['html'] .= '</pre>';
5773
5774 $response['html'] .= '<h3 id="ud-debuginfo-files">'.__('Files', 'updraftplus').'</h3><pre>';
5775 $updraft_dir = $updraftplus->backups_dir_location();
5776 $raw_output = array();
5777 $d = dir($updraft_dir);
5778 while (false !== ($entry = $d->read())) {
5779 $fp = $updraft_dir.'/'.$entry;
5780 $mtime = filemtime($fp);
5781 if (is_dir($fp)) {
5782 $size = ' d';
5783 } elseif (is_link($fp)) {
5784 $size = ' l';
5785 } elseif (is_file($fp)) {
5786 $size = sprintf("%8.1f", round(filesize($fp)/1024, 1)).' '.gmdate('r', $mtime);
5787 } else {
5788 $size = ' ?';
5789 }
5790 if (preg_match('/^log\.(.*)\.txt$/', $entry, $lmatch)) $entry = '<a target="_top" href="?action=downloadlog&amp;page=updraftplus&amp;updraftplus_backup_nonce='.htmlspecialchars($lmatch[1]).'">'.$entry.'</a>';
5791 $raw_output[$mtime] = empty($raw_output[$mtime]) ? sprintf("%s %s\n", $size, $entry) : $raw_output[$mtime].sprintf("%s %s\n", $size, $entry);
5792 }
5793 @$d->close();// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
5794 krsort($raw_output, SORT_NUMERIC);
5795
5796 foreach ($raw_output as $line) {
5797 $response['html'] .= $line;
5798 }
5799
5800 $response['html'] .= '</pre>';
5801
5802 $response['html'] .= '<h3 id="ud-debuginfo-options">'.__('Options (raw)', 'updraftplus').'</h3>';
5803 $opts = $updraftplus->get_settings_keys();
5804 asort($opts);
5805 // <tr><th>'.__('Key', 'updraftplus').'</th><th>'.__('Value', 'updraftplus').'</th></tr>
5806 $response['html'] .= '<table><thead></thead><tbody>';
5807 foreach ($opts as $opt) {
5808 $response['html'] .= '<tr><td>'.htmlspecialchars($opt).'</td><td>'.htmlspecialchars(print_r(UpdraftPlus_Options::get_updraft_option($opt), true)).'</td>';
5809 }
5810
5811 // Get the option saved by yahnis-elsts/plugin-update-checker
5812 $response['html'] .= '<tr><td>external_updates-updraftplus</td><td><pre>'.htmlspecialchars(print_r(get_site_option('external_updates-updraftplus'), true)).'</pre></td>';
5813
5814 $response['html'] .= '</tbody></table>';
5815
5816 ob_start();
5817 do_action('updraftplus_showrawinfo');
5818 $response['html'] .= ob_get_clean();
5819
5820 if (true == $no_pre_tags) {
5821 $response['html'] = str_replace('<pre>', '', $response['html']);
5822 $response['html'] = str_replace('</pre>', '', $response['html']);
5823 }
5824
5825 return $response;
5826 }
5827
5828 /**
5829 * This will call any wp_action
5830 *
5831 * @param Array|Null $data The array of data with the vaules for wpaction
5832 * @param Callable|Boolean $close_connection_callable A callable to call to close the browser connection, or true for a default suitable for internal use, or false for none
5833 * @return Array - results
5834 */
5835 public function call_wp_action($data = null, $close_connection_callable = false) {
5836 global $updraftplus;
5837
5838 ob_start();
5839
5840 $res = '<em>Request received: </em>';
5841
5842 if (preg_match('/^([^:]+)+:(.*)$/', $data['wpaction'], $matches)) {
5843 $action = $matches[1];
5844 if (null === ($args = json_decode($matches[2], true))) {
5845 $res .= "The parameters (should be JSON) could not be decoded";
5846 $action = false;
5847 } else {
5848 if (is_string($args)) $args = array($args);
5849 $res .= "Will despatch action: ".htmlspecialchars($action).", parameters: ".htmlspecialchars(implode(',', $args));
5850 }
5851 } else {
5852 $action = $data['wpaction'];
5853 $res .= "Will despatch action: ".htmlspecialchars($action).", no parameters";
5854 }
5855
5856 ob_get_clean();
5857
5858 // Need to add this as the close browser should only work for UDP
5859 if ($close_connection_callable) {
5860 if (is_callable($close_connection_callable)) {
5861 call_user_func($close_connection_callable, array('r' => $res));
5862 } else {
5863 $updraftplus->close_browser_connection(json_encode(array('r' => $res)));
5864 }
5865 }
5866
5867 if (!empty($action)) {
5868 if (!empty($args)) {
5869 ob_start();
5870 $returned = do_action_ref_array($action, $args);
5871 $output = ob_get_clean();
5872 $res .= " - do_action_ref_array Trigger ";
5873 } else {
5874 ob_start();
5875 do_action($action);
5876 $output = ob_get_contents();
5877 ob_end_clean();
5878 $res .= " - do_action Trigger ";
5879 }
5880 }
5881 $response = array();
5882 $response['response'] = $res;
5883 $response['log'] = $output;
5884
5885 // Check if response is empty
5886 if (!empty($returned)) $response['status'] = $returned;
5887
5888 return $response;
5889 }
5890
5891 /**
5892 * Enqueue JSTree JavaScript and CSS, taking into account whether it is already enqueued, and current debug settings
5893 */
5894 public function enqueue_jstree() {
5895 global $updraftplus;
5896
5897 static $already_enqueued = false;
5898 if ($already_enqueued) return;
5899
5900 $already_enqueued = true;
5901 $jstree_enqueue_version = $updraftplus->use_unminified_scripts() ? '3.3.12-rc.0'.'.'.time() : '3.3.12-rc.0';
5902 $min_or_not = $updraftplus->use_unminified_scripts() ? '' : '.min';
5903
5904 wp_enqueue_script('jstree', UPDRAFTPLUS_URL.'/includes/jstree/jstree'.$min_or_not.'.js', array('jquery'), $jstree_enqueue_version);
5905 wp_enqueue_style('jstree', UPDRAFTPLUS_URL.'/includes/jstree/themes/default/style'.$min_or_not.'.css', array(), $jstree_enqueue_version);
5906 }
5907
5908 /**
5909 * Detects byte-order mark at the start of common files and change waning message texts
5910 *
5911 * @return string|boolean BOM warning text or false if not bom characters detected
5912 */
5913 public function get_bom_warning_text() {
5914 $files_to_check = array(
5915 ABSPATH.'wp-config.php',
5916 get_template_directory().DIRECTORY_SEPARATOR.'functions.php',
5917 );
5918 if (is_child_theme()) {
5919 $files_to_check[] = get_stylesheet_directory().DIRECTORY_SEPARATOR.'functions.php';
5920 }
5921 $corrupted_files = array();
5922 foreach ($files_to_check as $file) {
5923 if (!file_exists($file)) continue;
5924 if (false === ($fp = fopen($file, 'r'))) continue;
5925 if (false === ($file_data = fread($fp, 8192)));
5926 fclose($fp);
5927 $substr_file_data = array();
5928 for ($substr_length = 2; $substr_length <= 5; $substr_length++) {
5929 $substr_file_data[$substr_length] = substr($file_data, 0, $substr_length);
5930 }
5931 // Detect UTF-7, UTF-8, UTF-16 (BE), UTF-16 (LE), UTF-32 (BE) & UTF-32 (LE) Byte order marks (BOM)
5932 $bom_decimal_representations = array(
5933 array(43, 47, 118, 56), // UTF-7 (Hexadecimal: 2B 2F 76 38)
5934 array(43, 47, 118, 57), // UTF-7 (Hexadecimal: 2B 2F 76 39)
5935 array(43, 47, 118, 43), // UTF-7 (Hexadecimal: 2B 2F 76 2B)
5936 array(43, 47, 118, 47), // UTF-7 (Hexadecimal: 2B 2F 76 2F)
5937 array(43, 47, 118, 56, 45), // UTF-7 (Hexadecimal: 2B 2F 76 38 2D)
5938 array(239, 187, 191), // UTF-8 (Hexadecimal: 2B 2F 76 38 2D)
5939 array(254, 255), // UTF-16 (BE) (Hexadecimal: FE FF)
5940 array(255, 254), // UTF-16 (LE) (Hexadecimal: FF FE)
5941 array(0, 0, 254, 255), // UTF-32 (BE) (Hexadecimal: 00 00 FE FF)
5942 array(255, 254, 0, 0), // UTF-32 (LE) (Hexadecimal: FF FE 00 00)
5943 );
5944 foreach ($bom_decimal_representations as $bom_decimal_representation) {
5945 $no_of_chars = count($bom_decimal_representation);
5946 array_unshift($bom_decimal_representation, 'C*');
5947 $binary = call_user_func_array('pack', $bom_decimal_representation);
5948 if ($binary == $substr_file_data[$no_of_chars]) {
5949 $corrupted_files[] = $file;
5950 break;
5951 }
5952 }
5953 }
5954 if (empty($corrupted_files)) {
5955 return false;
5956 } else {
5957 $corrupted_files_count = count($corrupted_files);
5958 return '<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(_n('The file %s has a "byte order mark" (BOM) at its beginning.', 'The files %s have a "byte order mark" (BOM) at their beginning.', $corrupted_files_count, 'updraftplus'), '<strong>'.implode('</strong>, <strong>', $corrupted_files).'</strong>').' <a href="'.apply_filters('updraftplus_com_link', "https://updraftplus.com/problems-with-extra-white-space/").'" target="_blank">'.__('Follow this link for more information', 'updraftplus').'</a>';
5959 }
5960 }
5961
5962 /**
5963 * Gets an instance of the "UpdraftPlus_UpdraftCentral_Cloud" class which will be
5964 * used to login or register the user to the UpdraftCentral cloud
5965 *
5966 * @return object
5967 */
5968 public function get_updraftcentral_cloud() {
5969 if (!class_exists('UpdraftPlus_UpdraftCentral_Cloud')) include_once(UPDRAFTPLUS_DIR.'/includes/updraftcentral.php');
5970 return new UpdraftPlus_UpdraftCentral_Cloud();
5971 }
5972
5973 /**
5974 * This function will build and return the UpdraftPlus tempoaray clone ui widget
5975 *
5976 * @param boolean $include_testing_ui - a boolean to indicate if testing-only UI elements should be shown (N.B. they can only work if the user also has testing permissions)
5977 * @param array $supported_wp_versions - an array of supported WordPress versions
5978 * @param array $supported_packages - an array of supported clone packages
5979 * @param array $supported_regions - an array of supported clone regions
5980 * @param string $nearest_region - the user's nearest region
5981 *
5982 * @return string - the clone UI widget
5983 */
5984 public function updraftplus_clone_ui_widget($include_testing_ui, $supported_wp_versions, $supported_packages, $supported_regions, $nearest_region = '') {
5985 global $updraftplus;
5986
5987 $output = '<p class="updraftplus-option updraftplus-option-inline php-version">';
5988 $output .= '<span class="updraftplus-option-label">'.sprintf(__('%s version:', 'updraftplus'), 'PHP').'</span> ';
5989 $output .= $this->output_select_data($this->clone_php_versions, 'php');
5990 $output .= '</p>';
5991 $output .= '<p class="updraftplus-option updraftplus-option-inline wp-version">';
5992 $output .= ' <span class="updraftplus-option-label">'.sprintf(__('%s version:', 'updraftplus'), 'WordPress').'</span> ';
5993 $output .= $this->output_select_data($this->get_wordpress_versions($supported_wp_versions), 'wp');
5994 $output .= '</p>';
5995 $output .= '<p class="updraftplus-option updraftplus-option-inline region">';
5996 $output .= ' <span class="updraftplus-option-label">'.__('Clone region:', 'updraftplus').'</span> ';
5997 $output .= $this->output_select_data($supported_regions, 'region', $nearest_region);
5998 $output .= '</p>';
5999
6000 $backup_history = UpdraftPlus_Backup_History::get_history();
6001
6002 foreach ($backup_history as $key => $backup) {
6003 $backup_complete = $this->check_backup_is_complete($backup, false, true, false);
6004 $remote_sent = !empty($backup['service']) && ((is_array($backup['service']) && in_array('remotesend', $backup['service'])) || 'remotesend' === $backup['service']);
6005 if (!$backup_complete || $remote_sent) unset($backup_history[$key]);
6006 }
6007
6008
6009 $output .= '<p class="updraftplus-option updraftplus-option-inline updraftclone-backup">';
6010 $output .= ' <span class="updraftplus-option-label">'.__('Clone:', 'updraftplus').'</span> ';
6011 $output .= '<select id="updraftplus_clone_backup_options" name="updraftplus_clone_backup_options">';
6012 $output .= '<option value="current" data-nonce="current" data-timestamp="current" selected="selected">'. __('This current site', 'updraftplus') .'</option>';
6013 $output .= '<option value="wp_only" data-nonce="wp_only" data-timestamp="wp_only">'. __('An empty WordPress install', 'updraftplus') .'</option>';
6014
6015 if (!empty($backup_history)) {
6016 foreach ($backup_history as $key => $backup) {
6017 $total_size = round($updraftplus->get_total_backup_size($backup) / 1073741824, 1);
6018 $pretty_date = get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $key), 'M d, Y G:i');
6019 $label = isset($backup['label']) ? ' ' . $backup['label'] : '';
6020 $output .= '<option value="'.$key. '" data-nonce="'.$backup['nonce'].'" data-timestamp="'.$key.'" data-size="'.$total_size.'">' . $pretty_date . $label . '</option>';
6021 }
6022 }
6023 $output .= '</select>';
6024 $output .= '</p>';
6025 $output .= '<p class="updraftplus-option updraftplus-option-inline package">';
6026 $output .= ' <span class="updraftplus-option-label">'.__('Clone package', 'updraftplus').' (<a href="'.$updraftplus->get_url('clone_packages').'" target="_blank">'.__('more info', 'updraftplus').'</a>):</span> ';
6027 $output .= '<select id="updraftplus_clone_package_options" name="updraftplus_clone_package_options" data-package_version="starter">';
6028 foreach ($supported_packages as $key => $value) {
6029 $output .= '<option value="'.esc_attr($key).'" data-size="'.esc_attr($value).'"';
6030 if ('starter' == $key) $output .= 'selected="selected"';
6031 $output .= ">".htmlspecialchars($key) . ('starter' == $key ? ' ' . __('(current version)', 'updraftplus') : '')."</option>\n";
6032 }
6033 $output .= '</select>';
6034 $output .= '</p>';
6035
6036 if ((defined('UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT') && UPDRAFTPLUS_UPDRAFTCLONE_DEVELOPMENT) || $include_testing_ui) {
6037 $output .= '<p class="updraftplus-option updraftplus-option-inline updraftclone-branch">';
6038 $output .= ' <span class="updraftplus-option-label">UpdraftClone Branch:</span> ';
6039 $output .= '<input id="updraftplus_clone_updraftclone_branch" type="text" size="36" name="updraftplus_clone_updraftclone_branch" value="">';
6040 $output .= '</p>';
6041 $output .= '<p class="updraftplus-option updraftplus-option-inline updraftplus-branch">';
6042 $output .= ' <span class="updraftplus-option-label">UpdraftPlus Branch:</span> ';
6043 $output .= '<input id="updraftplus_clone_updraftplus_branch" type="text" size="36" name="updraftplus_clone_updraftplus_branch" value="">';
6044 $output .= '</p>';
6045 $output .= '<p><input type="checkbox" id="updraftplus_clone_use_queue" name="updraftplus_clone_use_queue" value="1" checked="checked"><label for="updraftplus_clone_use_queue" class="updraftplus_clone_use_queue">Use the UpdraftClone queue</label></p>';
6046 }
6047 $output .= '<p class="updraftplus-option limit-to-admins">';
6048 $output .= '<input type="checkbox" class="updraftplus_clone_admin_login_options" id="updraftplus_clone_admin_login_options" name="updraftplus_clone_admin_login_options" value="1" checked="checked">';
6049 $output .= '<label for="updraftplus_clone_admin_login_options" class="updraftplus_clone_admin_login_options_label">'.__('Forbid non-administrators to login to WordPress on your clone', 'updraftplus').'</label>';
6050 $output .= '</p>';
6051
6052 $output = apply_filters('updraftplus_clone_additional_ui', $output);
6053
6054 return $output;
6055 }
6056
6057 /**
6058 * This function will output a select input using the passed in values.
6059 *
6060 * @param array $data - the keys and values for the select
6061 * @param string $name - the name of the items in the select input
6062 * @param string $selected - the value we want selected by default
6063 *
6064 * @return string - the output of the select input
6065 */
6066 public function output_select_data($data, $name, $selected = '') {
6067
6068 $name_version = empty($selected) ? $this->get_current_version($name) : $selected;
6069
6070 $output = '<select id="updraftplus_clone_'.$name.'_options" name="updraftplus_clone_'.$name.'_options" data-'.$name.'_version="'.$name_version.'">';
6071
6072 foreach ($data as $value) {
6073 $output .= "<option value=\"$value\" ";
6074 if ($value == $name_version) $output .= 'selected="selected"';
6075 $output .= ">".htmlspecialchars($value) . ($value == $name_version && 'region' != $name ? ' ' . __('(current version)', 'updraftplus') : '')."</option>\n";
6076 }
6077
6078 $output .= '</select>';
6079
6080 return $output;
6081 }
6082
6083 /**
6084 * This function will output the clones network information
6085 *
6086 * @param string $url - the clone URL
6087 *
6088 * @return string - the clone network information
6089 */
6090 public function updraftplus_clone_info($url) {
6091 global $updraftplus;
6092
6093 if (!empty($url)) {
6094 $content = '<div class="updraftclone_network_info">';
6095 $content .= '<p>' . __('Your clone has started and will be available at the following URLs once it is ready.', 'updraftplus') . '</p>';
6096 $content .= '<p><strong>' . __('Front page:', 'updraftplus') . '</strong> <a target="_blank" href="' . esc_html($url) . '">' . esc_html($url) . '</a></p>';
6097 $content .= '<p><strong>' . __('Dashboard:', 'updraftplus') . '</strong> <a target="_blank" href="' . esc_html(trailingslashit($url)) . 'wp-admin">' . esc_html(trailingslashit($url)) . 'wp-admin</a></p>';
6098 $content .= '</div>';
6099 $content .= '<p><a target="_blank" href="'.$updraftplus->get_url('my-account').'">'.__('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus').'</a></p>';
6100 } else {
6101 $content = '<p>' . __('Your clone has started, network information is not yet available but will be displayed here and at your updraftplus.com account once it is ready.', 'updraftplus') . '</p>';
6102 $content .= '<p><a target="_blank" href="' . $updraftplus->get_url('my-account') . '">' . __('You can find your temporary clone information in your updraftplus.com account here.', 'updraftplus') . '</a></p>';
6103 }
6104
6105 return $content;
6106 }
6107
6108 /**
6109 * This function will build and return an array of major WordPress versions, the array is built by calling the WordPress version API once every 24 hours and adding any new entires to our existing array of versions.
6110 *
6111 * @param array $supported_wp_versions - an array of supported WordPress versions
6112 *
6113 * @return array - an array of WordPress major versions
6114 */
6115 private function get_wordpress_versions($supported_wp_versions) {
6116
6117 if (empty($supported_wp_versions)) $supported_wp_versions[] = $this->get_current_version('wp');
6118
6119 $key = array_search($this->get_current_version('wp'), $supported_wp_versions);
6120
6121 if ($key) {
6122 $supported_wp_versions = array_slice($supported_wp_versions, $key);
6123 }
6124
6125 $version_array = $supported_wp_versions;
6126
6127 return $version_array;
6128 }
6129
6130 /**
6131 * This function will get the current version the server is running for the passed in item e.g WordPress or PHP
6132 *
6133 * @param string $name - the thing we want to get the version for e.g WordPress or PHP
6134 *
6135 * @return string - returns the current version of the passed in item
6136 */
6137 public function get_current_version($name) {
6138
6139 $version = '';
6140
6141 if ('php' == $name) {
6142 $parts = explode(".", PHP_VERSION);
6143 $version = $parts[0] . "." . $parts[1];
6144 } elseif ('wp' == $name) {
6145 global $updraftplus;
6146 $wp_version = $updraftplus->get_wordpress_version();
6147 $parts = explode(".", $wp_version);
6148 $version = $parts[0] . "." . $parts[1];
6149 }
6150
6151 return $version;
6152 }
6153
6154 /**
6155 * Show which remote storage settings are partially setup error, or if manual auth is supported show the manual auth UI
6156 */
6157 public function show_admin_warning_if_remote_storage_with_partial_setttings() {
6158 if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) {
6159 $enabled_services = UpdraftPlus_Storage_Methods_Interface::get_enabled_storage_objects_and_ids(array_keys($this->storage_service_with_partial_settings));
6160 foreach ($this->storage_service_with_partial_settings as $method => $method_name) {
6161 if (empty($enabled_services[$method]['object']) || empty($enabled_services[$method]['instance_settings']) || !$enabled_services[$method]['object']->supports_feature('manual_authentication')) {
6162 $this->show_admin_warning(sprintf(__('The following remote storage (%s) have only been partially configured, manual authorization is not supported with this remote storage, please try again and if the problem persists contact support.', 'updraftplus'), $method), 'error');
6163 } else {
6164 $this->show_admin_warning($enabled_services[$method]['object']->get_manual_authorisation_template(), 'error');
6165 }
6166 }
6167 } else {
6168 $this->show_admin_warning('UpdraftPlus: '.sprintf(__('The following remote storage (%s) have only been partially configured, if you are having problems you can try to manually authorise at the UpdraftPlus settings page.', 'updraftplus'), implode(', ', $this->storage_service_with_partial_settings)).' <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&amp;tab=settings">'.__('Return to UpdraftPlus configuration', 'updraftplus').'</a>', 'error');
6169 }
6170 }
6171
6172 /**
6173 * Show remote storage settings are empty warning
6174 */
6175 public function show_admin_warning_if_remote_storage_settting_are_empty() {
6176 if ((isset($_REQUEST['page']) && 'updraftplus' == $_REQUEST['page']) || (defined('DOING_AJAX') && DOING_AJAX)) {
6177 $this->show_admin_warning(sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)), 'error');
6178 } else {
6179 $this->show_admin_warning('UpdraftPlus: '.sprintf(__('You have requested saving to remote storage (%s), but without entering any settings for that storage.', 'updraftplus'), implode(', ', $this->storage_service_without_settings)).' <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&amp;tab=settings">'.__('Return to UpdraftPlus configuration', 'updraftplus').'</a>', 'error');
6180 }
6181 }
6182
6183 /**
6184 * Receive Heartbeat data and respond.
6185 *
6186 * Processes data received via a Heartbeat request, and returns additional data to pass back to the front end.
6187 *
6188 * @param array $response - Heartbeat response data to pass back to front end.
6189 * @param array $data - Data received from the front end (unslashed).
6190 */
6191 public function process_status_in_heartbeat($response, $data) {
6192
6193 if (!UpdraftPlus_Options::user_can_manage() || !is_array($response) || empty($data['updraftplus'])) return $response;
6194
6195 try {
6196 $response['updraftplus'] = $this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($data['updraftplus']));
6197 } catch (Exception $e) {
6198 $log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
6199 error_log($log_message);
6200 $response['updraftplus'] = array(
6201 'fatal_error' => true,
6202 'fatal_error_message' => $log_message
6203 );
6204 // @codingStandardsIgnoreLine
6205 } catch (Error $e) {
6206 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
6207 error_log($log_message);
6208 $response['updraftplus'] = array(
6209 'fatal_error' => true,
6210 'fatal_error_message' => $log_message
6211 );
6212 }
6213
6214 if (isset($data['updraftplus']['updraft_credentialtest_nonce'])) {
6215 if (!wp_verify_nonce($data['updraftplus']['updraft_credentialtest_nonce'], 'updraftplus-credentialtest-nonce')) {
6216 $response['updraftplus']['updraft_credentialtest_nonce'] = wp_create_nonce('updraftplus-credentialtest-nonce');
6217 }
6218 }
6219
6220 $response['updraftplus']['time_now'] = get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'D, F j, Y H:i');
6221
6222 return $response;
6223 }
6224
6225 /**
6226 * Show warning about restriction implied by the hosting company (can only perform a full backup once per month, incremental backup should not go above one per day)
6227 */
6228 public function show_admin_warning_one_backup_per_month() {
6229
6230 global $updraftplus;
6231
6232 $hosting_company = $updraftplus->get_hosting_info();
6233
6234 $txt1 = __('Your website is hosted with %s (%s).', 'updraftplus');
6235 $txt2 = __('%s permits UpdraftPlus to perform only one backup per month. Thus, we recommend you choose a full backup when performing a manual backup and to use that option when creating a scheduled backup.', 'updraftplus');
6236 $txt3 = __('Due to the restriction, some settings can be automatically adjusted, disabled or not available.', 'updraftplus');
6237
6238 $this->show_plugin_page_admin_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf("$txt1 $txt2 $txt3", $hosting_company['name'], $hosting_company['website'], $hosting_company['name']), 'update-nag notice notice-warning', true);
6239 }
6240
6241 /**
6242 * Find out if the current request is a backup download request, and proceed with the download if it is
6243 */
6244 public function maybe_download_backup_from_email() {
6245 global $pagenow;
6246 if (UpdraftPlus_Options::user_can_manage() && (!defined('DOING_AJAX') || !DOING_AJAX) && UpdraftPlus_Options::admin_page() === $pagenow && isset($_REQUEST['page']) && 'updraftplus' === $_REQUEST['page'] && isset($_REQUEST['action']) && 'updraft_download_backup' === $_REQUEST['action']) {
6247 $findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex'];
6248 $timestamp = empty($_REQUEST['timestamp']) ? '' : $_REQUEST['timestamp'];
6249 $nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
6250 $type = empty($_REQUEST['type']) ? '' : $_REQUEST['type'];
6251 if (empty($timestamp) || empty($nonce) || empty($type)) wp_die(__('The download link is broken, you may have clicked the link from untrusted source', 'updraftplus'), '', array('back_link' => true));
6252 $backup_history = UpdraftPlus_Backup_History::get_history();
6253 if (!isset($backup_history[$timestamp]['nonce']) || $backup_history[$timestamp]['nonce'] !== $nonce) wp_die(__("The download link is broken or the backup file is no longer available", 'updraftplus'), '', array('back_link' => true));
6254 $this->do_updraft_download_backup($findexes, $type, $timestamp, 2, false, '');
6255 exit; // we don't need anything else but an exit
6256 }
6257 }
6258 }
6259