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

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