PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.22.14
UpdraftPlus: WP Backup & Migration Plugin v1.22.14
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.14, at admin.php

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