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

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

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