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

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