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

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