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

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