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

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

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