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

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