PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.7
Backup Migration v2.1.7
2.1.7 2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / initializer.php
backup-backup / includes Last commit date
banner 3 weeks ago bodies 3 weeks ago check 3 weeks ago cli 3 weeks ago cron 3 weeks ago dashboard 3 weeks ago database 3 weeks ago external 3 weeks ago extracter 3 weeks ago htaccess 3 weeks ago notices 3 weeks ago progress 3 weeks ago scanner 3 weeks ago services 3 weeks ago staging 3 weeks ago traits 3 weeks ago uploader 3 weeks ago vendor 3 weeks ago zipper 3 weeks ago .htaccess 3 weeks ago activation.php 3 weeks ago ajax.php 3 weeks ago ajax_offline.php 3 weeks ago analyst.php 3 weeks ago backup-process.php 3 weeks ago class-backup-method-mananger.php 3 weeks ago cli-handler.php 3 weeks ago compatibility.php 3 weeks ago config.php 3 weeks ago config_v2.php 3 weeks ago constants.php 3 weeks ago file-explorer.php 3 weeks ago initializer.php 3 weeks ago logger.php 3 weeks ago offline.php 3 weeks ago
initializer.php
2149 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin;
5
6 // Exit on direct access
7 if (!defined('ABSPATH')) {
8 exit;
9 }
10
11 // Require classes
12 require_once BMI_INCLUDES . '/logger.php';
13
14 // Alias for classes
15 use BMI\Plugin\BMI_Logger as Logger;
16 use BMI\Plugin\CRON\BMI_Crons as Crons;
17 use BMI\Plugin\Dashboard as Dashboard;
18 use BMI\Plugin\Scanner\BMI_BackupsScanner as Backups;
19 use BMI\Plugin\Heart\BMI_Backup_Heart as Bypasser;
20 use BMI\Plugin\Zipper\BMI_Zipper as Zipper;
21 use BMI\Plugin\Staging\BMI_Staging as Staging;
22 use BMI\Plugin\External\BMI_External_BackupBliss as BackupBliss;
23 use BMI\Plugin\Services\FileHasher;
24
25 /**
26 * Backup Migration Main Class
27 */
28 class Backup_Migration_Plugin {
29 public function initialize() {
30
31 // Determine which BMI version is used
32 add_action('wp_head', function () {
33 echo '<meta name="bmi-version" content="' . esc_attr(BMI_VERSION) . '" />';
34 });
35
36 if (!file_exists(BMI_BACKUPS)) @mkdir(BMI_BACKUPS, 0755, true);
37 if (!file_exists(BMI_STAGING)) @mkdir(BMI_STAGING, 0755, true);
38
39 // Handle PHP CLI functions
40 if (defined('BMI_USING_CLI_FUNCTIONALITY') && BMI_USING_CLI_FUNCTIONALITY === true) {
41
42 // Below all WordPress functions and directives can be accessed
43 if (defined('BMI_CLI_FUNCTION')) {
44
45 if (BMI_CLI_FUNCTION == 'bmi_restore' && defined('BMI_CLI_ARGUMENT')) {
46
47 $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
48 $_POST['f'] = 'restore-backup';
49 if (defined('BMI_CLI_ARGUMENT_2')) {
50 $_POST['remote'] = BMI_CLI_ARGUMENT_2;
51 } else $_POST['remote'] = false;
52 $_POST['file'] = BMI_CLI_ARGUMENT;
53
54 $this->ajax(true);
55
56 } elseif (BMI_CLI_FUNCTION == 'bmi_backup' || BMI_CLI_FUNCTION == 'bmi_backup_cron') {
57
58 if (BMI_CLI_FUNCTION == 'bmi_backup_cron') {
59 define('BMI_DOING_SCHEDULED_BACKUP', true);
60 define('BMI_DOING_SCHEDULED_BACKUP_VIA_CLI', true);
61 }
62
63 $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
64 $_POST['f'] = 'create-backup';
65
66 $this->ajax(true);
67
68 } elseif (BMI_CLI_FUNCTION == 'bmi_quick_migration') {
69
70 $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
71 $_POST['f'] = 'download-backup';
72 $_POST['url'] = BMI_CLI_ARGUMENT;
73
74 $this->ajax(true);
75
76 }
77
78 }
79
80 return;
81
82 }
83
84 if (defined('BMI_RESTORE_SECRET') && defined('BMI_POST_CONTINUE_RESTORE') && constant('BMI_POST_CONTINUE_RESTORE') === true) {
85
86 if (!isset($_POST['bmi_restore_secret'])) exit;
87
88 // Check the secret
89 $bmi_secret_storage = BMI_TMP . DIRECTORY_SEPARATOR . '.restore_secret';
90 if (file_exists($bmi_secret_storage)) {
91 $bmi_saved_secret = file_get_contents($bmi_secret_storage);
92 if ($bmi_saved_secret === $_POST['bmi_restore_secret']) {
93 $bmi_continue_module = true;
94 } else exit;
95 } else exit;
96
97 $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
98 $_POST['f'] = 'continue_restore_process';
99
100 $this->ajax(true);
101
102 return;
103
104 }
105
106 // Hooks
107 register_deactivation_hook(BMI_ROOT_FILE, [&$this, 'deactivation']);
108 require_once BMI_INCLUDES . '/cron/bootstrap.php';
109 \BMI\Plugin\CRON\TaskManager::boot();
110
111 // File downloading
112 add_action('wp_loaded', [&$this, 'handle_downloading']);
113
114 // Additional actions
115 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET') {
116 add_action('wp_loaded', [&$this, 'handle_after_actions'], 1000);
117 }
118 // Handle CRONs
119 add_action('bmi_do_backup_right_now', [&$this, 'handle_cron_backup']);
120 add_action('bmi_handle_cron_check', [&$this, 'handle_cron_check']);
121 add_action('wp_loaded', [&$this, 'handle_crons']);
122 add_action('wp_loaded', [&$this, 'include_offline']);
123 add_action('admin_notices', [&$this, 'incompatibility_notices']);
124 add_action('bmip_fire_action', [&$this, 'fireBMIPAction'], 10, 4);
125 add_action('bmi_backup_upload_completed', [&$this, 'handle_after_cron']);
126 add_action('bmi_watchdog_cron', [$this, 'handleWatchDogCron']);
127
128 // Return if CRON time
129 if (function_exists('wp_doing_cron') && wp_doing_cron()) return;
130
131 // Check user permissions
132 $user = get_userdata(get_current_user_id());
133 if (!$user || !$user->roles) return;
134 if (!current_user_can('do_backups') && !in_array('administrator', (array) $user->roles)) return;
135
136 if (Dashboard\bmi_get_config('OTHER:PROMOTIONAL:DISPLAY') != 'true') {
137
138 // Include our cool banner
139 include_once BMI_INCLUDES . '/banner/misc.php';
140
141 // Review banner
142 if (!is_dir(WP_PLUGIN_DIR . '/backup-backup-pro')) {
143 if (!(class_exists('\Inisev\Subs\Inisev_Review') || class_exists('Inisev\Subs\Inisev_Review') || class_exists('Inisev_Review'))) {
144 require_once BMI_MODULES_DIR . 'review' . DIRECTORY_SEPARATOR . 'review.php';
145 }
146 $review_banner = new \Inisev\Subs\Inisev_Review(BMI_ROOT_FILE, BMI_ROOT_DIR, 'backup-backup', 'Backup & Migration', 'http://bit.ly/3vdk45L', 'backup-migration');
147 }
148
149 if (!(class_exists('\Inisev\Subs\New_BB_Banner') || class_exists('Inisev\Subs\New_BB_Banner') || class_exists('New_BB_Banner'))) {
150 require_once BMI_MODULES_DIR . 'new-bb-banner' . DIRECTORY_SEPARATOR . 'misc.php';
151 new \Inisev\Subs\New_BB_Banner(BMI_ROOT_FILE, BMI_ROOT_DIR, 'backup-backup', 'Backup & Migration', 'backup-migration');
152 }
153
154
155 // GDrive banner
156 if (!is_dir(WP_PLUGIN_DIR . '/backup-backup-pro')) {
157 if (!(class_exists('\Inisev\Subs\BMI_Banners_GDrive') || class_exists('Inisev\Subs\BMI_Banners_GDrive') || class_exists('BMI_Banners_GDrive'))) {
158 require_once BMI_MODULES_DIR . 'gdrivebanner' . DIRECTORY_SEPARATOR . 'misc.php';
159 }
160 $gdirve_banner = new \Inisev\Subs\BMI_Banners_GDrive('Backup & Migration', 'backup-migration');
161 }
162
163 // Backup banner
164 if (!(class_exists('\Inisev\Subs\BMI_Backup_Banner') || class_exists('Inisev\Subs\BMI_Backup_Banner') || class_exists('BMI_Backup_Banner'))) {
165 require_once BMI_MODULES_DIR . 'backup-banner' . DIRECTORY_SEPARATOR . 'misc.php';
166 new \Inisev\Subs\BMI_Backup_Banner(BMI_ROOT_FILE, BMI_ROOT_DIR, 'backup-backup', 'Backup & Migration', 'backup-migration');
167 }
168
169 }
170
171 // POST Logic
172 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
173
174 // Register AJAX Handler
175 add_action('wp_ajax_backup_migration', [&$this, 'ajax']);
176
177 // Stop GET Registration
178 // return; // Commented because of conflicts with USM Icons
179
180 }
181
182 // Actions
183 add_action('admin_init', [&$this, 'admin_init_hook']);
184 if (function_exists('is_multisite') && is_multisite()) {
185 add_action('network_admin_menu', [&$this, 'submenu']);
186 } else {
187 add_action('admin_menu', [&$this, 'submenu']);
188 }
189 add_action('admin_notices', [&$this, 'admin_notices']);
190
191
192 // Settings action
193 if (function_exists('is_multisite') && is_multisite()) {
194 add_filter('network_admin_plugin_action_links_' . plugin_basename(BMI_ROOT_FILE), [&$this, 'settings_action']);
195 } else {
196 add_filter('plugin_action_links_' . plugin_basename(BMI_ROOT_FILE), [&$this, 'settings_action']);
197 }
198
199 // Whitelist configuration files for Security Ninja
200 add_filter('securityninja_whitelist', [&$this, 'securityninja_whitelist_config_files']);
201
202 // Ignore below actions if those true
203 if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
204 return;
205 }
206
207 // Styles & scripts
208 add_action('admin_enqueue_scripts', [&$this, 'enqueue_styles']);
209 add_action('admin_enqueue_scripts', [&$this, 'enqueue_scripts']);
210
211 // External storage errors
212 add_action('bmi_external_errors', function() {
213
214 if (file_exists(BMI_INCLUDES . 'notices/dropbox-issues-notice.php'))
215 require_once BMI_INCLUDES . 'notices/dropbox-issues-notice.php';
216
217 if (file_exists(BMI_INCLUDES . '/notices/aws-issues.php'))
218 require_once BMI_INCLUDES . '/notices/aws-issues.php';
219
220 if (file_exists(BMI_INCLUDES . '/notices/google-drive-issues.php'))
221 require_once BMI_INCLUDES . '/notices/google-drive-issues.php';
222
223 if (file_exists(BMI_INCLUDES . '/notices/wasabi-issues.php'))
224 require_once BMI_INCLUDES . '/notices/wasabi-issues.php';
225
226 require_once BMI_INCLUDES . '/notices/backupbliss.php';
227 });
228
229 $upload_issue_notice = $this->backupbliss_space_issues();
230
231 if ($upload_issue_notice) {
232 add_action("admin_notices", function() use ($upload_issue_notice) {
233 $global_warning = true;
234 $error_message = $upload_issue_notice;
235 require_once BMI_INCLUDES . '/external/backupbliss.php';
236 $backupbliss = new BackupBliss();
237 if (!$backupbliss->hasRequiredSpaceBeenFreed()){
238 include BMI_INCLUDES . '/dashboard/modals/bb-warning-notice.php';
239 }
240
241 });
242 }
243
244 }
245
246 public function incompatibility_notices() {
247
248 if (strpos(home_url(), 'instawp') === false && strpos(home_url(), 'playground.wordpress') === false) return;
249
250 $environment = 'sandbox';
251 if (strpos(home_url(), 'instawp') !== false) $environment = 'InstaWP';
252 if (strpos(home_url(), 'playground.wordpress') !== false) $environment = 'WordPress Playground';
253
254 $class = 'notice notice-warning';
255 $message = __('We noticed that you are using %s, our plugin may not work in this environment, please use %s environment instead.', 'backup-backup');
256
257 printf('<div class="%s"><p><b>Backup Migration:</b> %s</p></div>',
258 esc_attr($class),
259 sprintf(
260 esc_html($message),
261 '<i>' . esc_html($environment) . '</i>',
262 '<a href="https://tastewp.com" target="_blank">TasteWP</a>'
263 )
264 );
265 }
266
267 public static function randomString($max = 16) {
268
269 $bank = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
270 $bank .= 'abcdefghijklmnopqrstuvwxyz';
271 $bank .= '0123456789';
272
273 $str = str_shuffle($bank);
274
275 while (is_numeric($str[0])) {
276 $str = str_shuffle($bank);
277 }
278
279 $str = substr($str, 0, $max);
280
281 return $str;
282
283 }
284
285 public static function isFunctionEnabled($func) {
286
287 $disabled = explode(',', ini_get('disable_functions'));
288 $isDisabled = in_array($func, $disabled);
289 if (!$isDisabled && function_exists($func)) return true;
290 else return false;
291
292 }
293
294 /**
295 * hotFixPatches - Function which fixes things for "old" users
296 *
297 * @return void
298 */
299 public function hotfix_patches() {
300
301 if (!is_admin()) return;
302
303 $current_patch = get_option('bmi_hotfixes', array());
304
305 if (!in_array('BMI_D15_M6_26', $current_patch)) {
306 if (file_exists(BMI_BACKUPS . '/md5summary.php')) {
307 unlink(BMI_BACKUPS . '/md5summary.php');
308 }
309 $current_patch[] = 'BMI_D15_M6_26';
310 }
311 if (!in_array('BMI_D1_M6_26', $current_patch)) {
312 require_once BMI_INCLUDES . '/external/backupbliss.php';
313 $backupbliss = new BackupBliss();
314 $connectionStatus = $backupbliss->verifyConnection();
315 if ($connectionStatus['result'] == 'connected') {
316 Dashboard\bmi_set_config('STORAGE::EXTERNAL::BACKUPBLISS', true);
317 }
318 $current_patch[] = 'BMI_D1_M6_26';
319 }
320 if (!in_array('BMI_D17_M1_26', $current_patch)) {
321 $baseurl = home_url();
322 if (substr($baseurl, 0, 4) != 'http') {
323 if (is_ssl()) $baseurl = 'https://' . home_url();
324 else $baseurl = 'http://' . home_url();
325 }
326
327 $sk = get_option('bmi_sk_keepalive');
328 // Generate if missing (First time patch is applied)
329 if (empty($sk)) {
330 $sk = wp_generate_password(32, false);
331 update_option('bmi_sk_keepalive', $sk);
332 }
333
334 $url = 'https://authentication.backupbliss.com/v2/crons/connect';
335 $response = wp_remote_post($url, array(
336 'method' => 'POST',
337 'timeout' => 15,
338 'redirection' => 2,
339 'httpversion' => '1.0',
340 'blocking' => true,
341 'body' => array('site' => $baseurl, 'sk' => $sk)
342 ));
343
344 $current_patch[] = 'BMI_D17_M1_26';
345 }
346
347 if (!in_array('BMI_D13_M12_01', $current_patch)) {
348 Dashboard\bmi_set_config('OTHER::NEW_SEARCH_REPLACE_ENGINE', true);
349 Dashboard\bmi_set_config('OTHER:DB:SEARCHREPLACE:MAX', 5000);
350 $current_patch[] = 'BMI_D13_M12_01';
351 }
352
353 if (!in_array('BMI_D20_M07_01', $current_patch)) {
354
355 $current_directory = Dashboard\bmi_get_config('STORAGE::LOCAL::PATH');
356 if (basename($current_directory) == 'backup-migration') {
357
358 require_once BMI_INCLUDES . '/ajax.php';
359 $fValue = isset($_POST['f']) ? $_POST['f'] : '';
360 unset($_POST['f']);
361 $handler_a = new BMI_Ajax(true);
362 $_POST['f'] = $fValue;
363
364 $handler_a->post['directory'] = dirname($current_directory) . DIRECTORY_SEPARATOR . 'backup-migration-' . $this->randomString(10);
365 $handler_a->post['access'] = Dashboard\bmi_get_config('STORAGE::DIRECT::URL');
366
367 $res_a = $handler_a->saveStorageConfig();
368 if (isset($res_a['status']) && $res_a['status'] == 'success') {
369
370 $current_patch[] = 'BMI_D20_M07_01';
371
372 }
373
374 } else {
375
376 $current_patch[] = 'BMI_D20_M07_01';
377
378 }
379
380 }
381
382 if (!in_array('BMI_D17_M12_Y21_02', $current_patch)) {
383
384 $current_splitting_value = Dashboard\bmi_get_config('OTHER:RESTORE:SPLITTING');
385 $current_query_size = Dashboard\bmi_get_config('OTHER:DB:QUERIES');
386
387 $current_query_size = intval($current_query_size);
388 if ($current_splitting_value == 'true' || $current_splitting_value === true) {
389 $current_splitting_value = true;
390 } else {
391 $current_splitting_value = false;
392 }
393
394 if ($current_splitting_value === false || $current_query_size != 300) {
395
396 $b_db_restore_splitting = true;
397 $b_db_query_size = '2000';
398
399 $error_b = 0;
400 if (!Dashboard\bmi_set_config('OTHER:RESTORE:SPLITTING', $b_db_restore_splitting)) {
401 $error_b++;
402 }
403 if (!Dashboard\bmi_set_config('OTHER:DB:QUERIES', $b_db_query_size)) {
404 $error_b++;
405 }
406
407 if ($error_b <= 0) {
408
409 $current_patch[] = 'BMI_D17_M12_Y21_02';
410
411 }
412
413 } else {
414
415 $current_patch[] = 'BMI_D17_M12_Y21_02';
416
417 }
418
419 }
420
421 if (!in_array('BMI_D13_M08_Y23_01', $current_patch)) {
422
423 $current_direct_download_value = Dashboard\bmi_get_config('OTHER:DOWNLOAD:DIRECT');
424
425 if ($current_direct_download_value === false || $current_direct_download_value == 'false') {
426
427 $error_b = 0;
428 if (!Dashboard\bmi_set_config('OTHER:DOWNLOAD:DIRECT', true)) $error_b++;
429 if ($error_b <= 0) $current_patch[] = 'BMI_D13_M08_Y23_01';
430
431 } else $current_patch[] = 'BMI_D13_M08_Y23_01';
432
433 }
434
435 update_option('bmi_hotfixes', $current_patch);
436
437 }
438
439 public function ajax($cli = false) {
440 if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
441 if ((isset($_POST['token']) && $_POST['token'] == 'bmi' && isset($_POST['f']) && is_admin()) || $cli) {
442 try {
443
444 if (gettype($cli) != 'boolean') $cli = false;
445
446 // Extend execution time
447 if ($this->isFunctionEnabled('headers_sent') && $this->isFunctionEnabled('session_status')) {
448 if (!headers_sent() && session_status() === PHP_SESSION_DISABLED) {
449 if ($this->isFunctionEnabled('ignore_user_abort')) @ignore_user_abort(true);
450 if ($this->isFunctionEnabled('set_time_limit')) @set_time_limit(16000);
451 if ($this->isFunctionEnabled('ini_set')) {
452 @ini_set('max_execution_time', '259200');
453 @ini_set('max_input_time', '259200');
454 }
455 }
456 }
457
458 // May cause issues with auto login
459 // if (strlen(session_id()) > 0) session_write_close();
460
461 if ($this->isFunctionEnabled('register_shutdown_function')) {
462 register_shutdown_function([$this, 'execution_shutdown']);
463 }
464
465 // Require AJAX Handler
466 require_once BMI_INCLUDES . '/ajax.php';
467 $handler = new BMI_Ajax($cli);
468
469 } catch (\Exception $e) {
470
471 Logger::error('POST error:');
472 Logger::error($e);
473 if ($_POST['f'] == 'create-backup') {
474 $progress = &$GLOBALS['bmi_backup_progress'];
475 $this->handleErrorDuringBackup($e->getMessage(), $e->getFile(), $e->getLine(), $progress);
476 }
477 if ($_POST['f'] == 'restore-backup') {
478 $progress = &$GLOBALS['bmi_migration_progress'];
479 $this->handleErrorDuringRestore($e->getMessage(), $e->getFile(), $e->getLine(), $progress);
480 }
481
482 $this->res(['status' => 'error', 'error' => $e]);
483 exit;
484
485 } catch (\Throwable $e) {
486
487 Logger::error('POST error:');
488 Logger::error($e);
489 if ($_POST['f'] == 'create-backup') {
490 $progress = &$GLOBALS['bmi_backup_progress'];
491 $this->handleErrorDuringBackup($e->getMessage(), $e->getFile(), $e->getLine(), $progress);
492 }
493 if ($_POST['f'] == 'restore-backup') {
494 $progress = &$GLOBALS['bmi_migration_progress'];
495 $this->handleErrorDuringRestore($e->getMessage(), $e->getFile(), $e->getLine(), $progress);
496 }
497
498 $this->res(['status' => 'error', 'error' => $e]);
499 exit;
500
501 }
502 }
503 }
504 }
505
506 public function execution_shutdown() {
507 $err = error_get_last();
508
509 if ($err === null || !empty($GLOBALS['bmi_error_handled'])) {
510 return;
511 }
512
513 $msg = $err['message'];
514 $file = $err['file'];
515 $line = $err['line'];
516 $type = $err['type'];
517
518 if (defined('BMI_USING_CLI_FUNCTIONALITY') && BMI_USING_CLI_FUNCTIONALITY === true) {
519 $lock_cli = BMI_BACKUPS . '/.migration_lock_cli';
520 $lock_cli_end = BMI_BACKUPS . '/.migration_lock_ended';
521 $cli_failed_lock = BMI_BACKUPS . '/.backup_lock_cli_failed';
522 $lock_cli_end_backup = BMI_BACKUPS . '/.backup_lock_cli_end';
523
524 if (file_exists($lock_cli)) @unlink($lock_cli);
525 if (file_exists($lock_cli_end)) @touch($lock_cli_end);
526 if (file_exists($cli_failed_lock)) @touch($cli_failed_lock);
527 if (file_exists($lock_cli_end_backup)) @touch($lock_cli_end_backup);
528 }
529
530 $fatal_levels = [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_PARSE];
531 $action = '';
532 if (isset($_POST['f'])) {
533 $action = $_POST['f'];
534 } elseif (isset($GLOBALS['bmi_current_action'])) {
535 $action = $GLOBALS['bmi_current_action'];
536 }
537
538 $is_our_plugin = strpos($file, 'backup-backup') !== false;
539
540 if (!in_array($type, $fatal_levels)) {
541 if ($is_our_plugin) {
542 Logger::error(__("A non-fatal error occurred within the Backup Migration plugin, which may have affected the request.", 'backup-backup'));
543 Logger::error(sprintf(__('Message: %s | File: %s:%s', 'backup-backup'), $msg, $file, $line));
544 }
545 return;
546 }
547
548
549 if (!$is_our_plugin) {
550 Logger::error(__("A fatal error occurred outside of Backup Migration, but caused the request to crash.", 'backup-backup'));
551 } else {
552 Logger::error(__("A fatal error occurred within the Backup Migration plugin.", 'backup-backup'));
553 }
554
555 Logger::error(sprintf(__('Message: %s | File: %s:%s', 'backup-backup'), $msg, $file, $line));
556
557 if ($action === 'create-backup') {
558 $progress = isset($GLOBALS['bmi_backup_progress']) ? $GLOBALS['bmi_backup_progress'] : null;
559 if ($progress) {
560 $progress->log(sprintf(__('Fatal Error: %s', 'backup-backup'), $msg), 'error');
561 $progress->log($this->fetchFatalErrorVerbose($msg), 'verbose');
562 $progress->log(__('More information is available in the troubleshooting log file.', 'backup-backup'), 'error');
563 }
564
565 $this->handleErrorDuringBackup($msg, $file, $line, $progress);
566
567 $fullPath = BMI_TMP . DIRECTORY_SEPARATOR;
568 $tmp_files = glob($fullPath . '*.{tmp,gz,zip}', GLOB_BRACE);
569 if (is_array($tmp_files)) {
570 foreach ($tmp_files as $t_file) {
571 @unlink($t_file);
572 }
573 }
574 }
575
576 if ($action === 'restore-backup') {
577 $progress = isset($GLOBALS['bmi_migration_progress']) ? $GLOBALS['bmi_migration_progress'] : null;
578 if ($progress) {
579 $progress->log(sprintf(__('Fatal Error: %s', 'backup-backup'), $msg), 'error');
580 $progress->log($this->fetchFatalErrorVerbose($msg), 'verbose');
581 $progress->log(__('More information is available in the troubleshooting log file.', 'backup-backup'), 'error');
582 }
583 $this->handleErrorDuringRestore($msg, $file, $line, $progress);
584 }
585
586 $this->res(['status' => 'error', 'error' => $err]);
587 exit;
588 }
589
590 public function handleErrorDuringBackup($msg, $file, $line, &$progress) {
591 Logger::log('Due to fatal error backup handled correctly (closed and removed).');
592 Logger::log('Error message: ' . $msg);
593 Logger::log('Error file/line: ' . $file . ':' . $line);
594 if ($progress) {
595 $progress->log(__('Something bad happened on PHP side.', 'backup-backup'), 'error');
596 $progress->log(__('Unfortunately we had to remove the backup (if partly created).', 'backup-backup'), 'error');
597 $progress->log(__('Error message: ', 'backup-backup') . $msg, 'error');
598 $progress->log(__('Error file/line: ', 'backup-backup') . $file . ':' . $line, 'error');
599 if (strpos($msg, 'execution time') !== false) {
600 $progress->log(__('Probably we could not increase the execution time, please edit your php.ini manually', 'backup-backup'), 'error');
601 }
602 }
603
604 if ($progress) {
605 $progress->log(__("Aborting backup...", 'backup-backup'), 'step');
606 $progress->log('#002', 'END-CODE');
607 $progress->end();
608 }
609
610 BMI_Ajax::forceBackupToStop();
611 }
612
613
614 public function handleErrorDuringRestore($msg, $file, $line, &$progress) {
615 Logger::log('There was fatal error during restore.');
616 Logger::log('Error message: ' . $msg);
617 Logger::log('Error file/line: ' . $file . ':' . $line);
618 if ($progress) {
619 $progress->log(__('Something bad happened on PHP side.', 'backup-backup'), 'error');
620 $progress->log(__('Error message: ', 'backup-backup') . $msg, 'error');
621 $progress->log(__('Error file/line: ', 'backup-backup') . $file . ':' . $line, 'error');
622 }
623 if (file_exists(BMI_BACKUPS . DIRECTORY_SEPARATOR . '.migration_lock')) @unlink(BMI_BACKUPS . DIRECTORY_SEPARATOR . '.migration_lock');
624 if ($progress) {
625 $progress->log(__("Aborting & unlocking restore process...", 'backup-backup'), 'step');
626 $progress->end();
627 }
628
629 $lock = BMI_BACKUPS . '/.migration_lock';
630 if (file_exists($lock)) @unlink($lock);
631 BMI_Ajax::forceRestoreToStop();
632 }
633
634 public function submenu() {
635
636 // Menu icon
637 $icon_url = $this->get_asset('images', 'logo-min.png');
638
639 // Main menu slug
640 $parentSlug = 'backup-migration';
641
642 // Content
643 $content = [$this, 'settings_page'];
644
645 // Main menu hook
646 add_menu_page('Backup Migration', '<span id="bmi-menu">Backup Migration</span>', 'read', $parentSlug, $content, $icon_url, 98);
647
648 // Remove default submenu by menu
649 remove_submenu_page($parentSlug, $parentSlug);
650
651 }
652
653 public function settings_action($links) {
654 if (function_exists('is_multisite') && is_multisite()) {
655 $url = network_admin_url('/admin.php?page=backup-migration');
656 } else {
657 $url = admin_url('/admin.php?page=backup-migration');
658 }
659
660 $text = __('Manage', 'backup-backup');
661 $links['bmi-settings-link'] = '<a href="' . $url . '">' . $text . '</a>';
662
663 return $links;
664 }
665
666 /**
667 * Whitelist configuration files for Security Ninja plugin.
668 *
669 * This filter callback prevents Security Ninja from deleting or flagging
670 * Backup Migration configuration files as suspicious. These files are
671 * essential for the plugin's operation and should be preserved.
672 *
673 * @param array $whitelist Existing whitelist array from Security Ninja
674 * @return array Modified whitelist array with our configuration files added
675 */
676 public function securityninja_whitelist_config_files($whitelist = []) {
677 if (!is_array($whitelist)) $whitelist = [];
678
679 // Add all configuration files to the whitelist
680 $whitelist[] = BMI_CONFIG_DEFAULT;
681 $whitelist[] = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.autologin.php';
682 $whitelist[] = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.htaccess';
683 $whitelist[] = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.litespeed';
684
685 return $whitelist;
686 }
687
688 public function include_offline() {
689
690
691 // Handle offline tasks
692 if (!class_exists('BMI_Offline')) {
693
694 if (file_exists(BMI_INCLUDES . '/offline.php')) {
695 require_once BMI_INCLUDES . '/offline.php';
696 new BMI_Offline();
697 }
698 }
699
700 }
701
702 public function settings_page() {
703
704 // Set email if does not exist
705 if (!Dashboard\bmi_get_config('OTHER:EMAIL')) {
706 Dashboard\bmi_set_config('OTHER:EMAIL', get_bloginfo('admin_email'));
707 }
708
709 // Require The HTML
710 require_once BMI_INCLUDES . '/dashboard/settings.php';
711 }
712
713 public function backupbliss_space_issues() {
714 require_once BMI_INCLUDES . '/external/backupbliss.php';
715 $backupbliss = new BackupBliss();
716 $upload_issue_notice = false;
717 if ($backupbliss->canShowFailureWarnNotice()) {
718 $upload_issue_notice = $backupbliss->getNotice("upload_issue_space");
719 }
720 return $upload_issue_notice;
721 }
722
723 public function admin_init_hook() {
724 $this->hotfix_patches();
725 if (get_option('_bmi_redirect', false)) {
726 $this->fixLitespeed();
727 delete_option('_bmi_redirect');
728
729 $is_bulk_action = isset($_REQUEST['action']) && $_REQUEST['action'] === 'activate-selected';
730 $is_bulk_action_alt = isset($_REQUEST['action2']) && $_REQUEST['action2'] === 'activate-selected';
731
732 $is_multi_activate = isset($_GET['activate-multi']) && $_GET['activate-multi'] === 'true';
733
734 if ($is_bulk_action || $is_bulk_action_alt || $is_multi_activate) {
735 return;
736 }
737
738 if (function_exists('is_multisite') && is_multisite()) {
739 wp_safe_redirect(network_admin_url('admin.php?page=backup-migration'));
740 } else {
741 wp_safe_redirect(admin_url('admin.php?page=backup-migration'));
742 }
743 exit;
744 }
745 }
746
747 public function admin_notices() {
748 if (!in_array(get_current_screen()->id, ['toplevel_page_backup-migration', 'toplevel_page_backup-migration-network']) && get_option('bmi_display_email_issues', false)) {
749 ?>
750 <div class="notice notice-warning">
751 <p>
752 <?php esc_html_e('There was an error during automated backup, please', 'backup-backup'); ?>
753 <?php echo '<a href="' . esc_url(admin_url('/admin.php?page=backup-migration')) . '">' . esc_html(__('check that.', 'backup-backup')) . '</a>'; ?>
754 </p>
755 </div>
756 <?php
757 }
758 }
759
760 public function handle_crons() {
761 if (Dashboard\bmi_get_config('CRON:ENABLED') !== true) return;
762
763 $time = get_option('bmi_backup_check', 0);
764 if ((time() - $time) > 60) {
765 update_option('bmi_backup_check', time());
766
767 do_action('bmi_handle_cron_check');
768 }
769 }
770
771 /**
772 * Retrieves a list of active security plugins detected on the site.
773 *
774 * This function checks the list of currently active plugins and identifies
775 * common security plugins by their slugs. It returns a key-value array
776 * where keys are plugin slugs and values are their human-readable names.
777 *
778 * Supported plugins:
779 * - Wordfence
780 * - Sucuri Security
781 *
782 * @return array<string, string> Associative array of detected security plugins.
783 * Format: [ 'plugin_slug' => 'Plugin Name' ]
784 */
785 public static function get_active_security_plugins() {
786 $active_plugins = [];
787 $plugins = get_option('active_plugins', []);
788 if (is_array($plugins) && count($plugins) > 0) {
789 foreach ($plugins as $plugin) {
790 if (strpos($plugin, 'wordfence') !== false) {
791 $active_plugins['wordfence'] = 'Wordfence';
792 } elseif (strpos($plugin, 'security-ninja') !== false) {
793 $active_plugins['security-ninja'] = 'Security Ninja';
794 }
795 }
796 }
797 return $active_plugins;
798 }
799
800 public static function email_error($msg) {
801 Logger::log('Displaying some issues about email sending...');
802 update_option('bmi_display_email_issues', $msg);
803 }
804
805 public function backup_inproper_time($should_time) {
806 $plan_file = BMI_TMP . DIRECTORY_SEPARATOR . '.plan';
807 if (!file_exists($plan_file) || intval($should_time) < 1234567890) return;
808
809 $currentDate = date('Y-m-d');
810 if (get_option('bmi_last_email_notification', false) == $currentDate) {
811 return;
812 }
813
814 Logger::log('Sending notification about backup being late');
815 $email = Dashboard\bmi_get_config('OTHER:EMAIL') != false ? Dashboard\bmi_get_config('OTHER:EMAIL') : get_bloginfo('admin_email');
816 $subject = Dashboard\bmi_get_config('OTHER:EMAIL:TITLE');
817 $message = __("Automatic backup was not on time because the connection to the ping server was interrupted, and WP Cron was late, as there was no traffic on the site.", 'backup-backup') . "\n";
818 $message .= __("Backup was made on: ", 'backup-backup') . date('Y-m-d H:i:s') . __(', but should be on: ', 'backup-backup') . date('Y-m-d H:i:s', $should_time);
819 $message .= ' ' . __("(server time)", 'backup-backup');
820
821 Logger::debug($message);
822 if (!self::send_notification_mail($email, $subject, $message)) {
823 $issue = __("Couldn't send mail to you, please check server configuration.", 'backup-backup') . '<br>';
824 $issue .= '<b>' . __("Message you missed because of this: ", 'backup-backup') . '</b>' . $message;
825 self::email_error($issue);
826 }
827 }
828
829 public function handle_cron_check() {
830
831 if (Dashboard\bmi_get_config('CRON:ENABLED') !== true) return;
832
833 $now = time();
834 if (file_exists(BMI_TMP . DIRECTORY_SEPARATOR . '.last')) {
835 $last = @file_get_contents(BMI_TMP . DIRECTORY_SEPARATOR . '.last');
836 $last_status = explode('.', $last)[0];
837 $last_time = intval(explode('.', $last)[1]);
838 } else {
839 $last_time = 0;
840 $last_status = 0;
841 }
842
843 if (file_exists(BMI_TMP . DIRECTORY_SEPARATOR . '.plan')) {
844 $plan = intval(@file_get_contents(BMI_TMP . DIRECTORY_SEPARATOR . '.plan'));
845 if ($last_time < $plan && ((time() - $plan) > 7200)) {
846 if ($last_status !== '0') {
847 if (!wp_next_scheduled('bmi_do_backup_right_now')) {
848 wp_schedule_single_event(time(), 'bmi_do_backup_right_now');
849 }
850 }
851 }
852 }
853
854 }
855
856 public function get_next_cron($curr = false) {
857 if ($curr === false) {
858 $curr = time();
859 }
860
861 $time = Crons::calculate_date([
862 'type' => Dashboard\bmi_get_config('CRON:TYPE'),
863 'week' => Dashboard\bmi_get_config('CRON:WEEK'),
864 'day' => Dashboard\bmi_get_config('CRON:DAY'),
865 'hour' => Dashboard\bmi_get_config('CRON:HOUR'),
866 'minute' => Dashboard\bmi_get_config('CRON:MINUTE')
867 ], $curr);
868
869 return $time;
870 }
871
872 public function handle_cron_error($e) {
873 Logger::error(__("Automatic backup failed at time: ", 'backup-backup') . date('Y-m-d, H:i:s'));
874 if (is_object($e) || is_array($e)) {
875 Logger::error('Error: ' . $e->getMessage());
876 } else {
877 Logger::error('Error: ' . $e);
878 }
879
880 $notis = Dashboard\bmi_get_config('OTHER:EMAIL:NOTIS');
881 if (in_array($notis, [true, 'true'])) {
882 $email = Dashboard\bmi_get_config('OTHER:EMAIL') != false ? Dashboard\bmi_get_config('OTHER:EMAIL') : get_bloginfo('admin_email');
883 $subject = Dashboard\bmi_get_config('OTHER:EMAIL:TITLE');
884 $message = __("There was an error during automatic backup, please check the logs.", 'backup-backup');
885 if (is_string($e)) {
886 $message .= "\nError: " . $e;
887 }
888
889 if (!self::send_notification_mail($email, $subject, $message, true)) {
890 $issue = __("Couldn't send mail to you, please check server configuration.", 'backup-backup') . '<br>';
891 $issue .= '<b>' . __("Message you missed because of this: ", 'backup-backup') . '</b>' . $message;
892 self::email_error($issue);
893 }
894 }
895
896 if (file_exists(BMI_BACKUPS . '/.cron')) {
897 @unlink(BMI_BACKUPS . '/.cron');
898 }
899 }
900
901 public static function send_notification_mail($email, $subject, $message, $force = false) {
902
903 $currentDate = date('Y-m-d');
904 if (get_option('bmi_last_email_notification', false) == $currentDate && $force === false) {
905 Logger::log(__("Disallowing to send mail as today we already sent one.", 'backup-backup'));
906 return;
907 }
908
909 update_option('bmi_last_email_notification', $currentDate);
910
911 $email_fail = __("Could not send the email notification about that fail", 'backup-backup');
912
913 try {
914
915 if (wp_mail($email, $subject, $message)) {
916 Logger::log(__("Sent email notification to: ", 'backup-backup') . $email);
917
918 return true;
919 } else {
920 Logger::error($email_fail);
921 self::email_error(__("Couldn't send notification via email, please check the email and your server settings.", 'backup-backup'));
922
923 return false;
924 }
925
926 } catch (\Exception $e) {
927 Logger::error($email_fail);
928 self::email_error(__("Couldn't send notification via email due to error, please check plugin logs for more details.", 'backup-backup'));
929
930 return false;
931 } catch (\Throwable $e) {
932 Logger::error($email_fail);
933 self::email_error(__("Couldn't send notification via email due to error, please check plugin logs for more details.", 'backup-backup'));
934
935 return false;
936 }
937 }
938
939 public static function handle_after_cron() {
940 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'scanner' . DIRECTORY_SEPARATOR . 'backups.php';
941 require_once BMI_INCLUDES . '/external/external-storage-manager.php';
942
943 $backups = Backups::getInstance();
944 $externalStorageManager = \BMI\Plugin\External\BMI_External_Storage_Manager::getInstance();
945
946 $availableBackups = $backups->getAvailableBackups();
947 $list = $availableBackups['local'];
948
949 $cron_list = [];
950 $cron_dates = [];
951 $sortedMD5s = [];
952 foreach ($list as $key => $value) {
953 if ($list[$key][6] == true) {
954 if ($list[$key][5] == 'unlocked') {
955 $cron_list[$list[$key][1]] = $list[$key][0];
956 $cron_dates[] = $list[$key][1];
957 $sortedMD5s[] = [$list[$key][1], $list[$key][7]];
958 }
959 }
960 }
961
962 usort($cron_dates, function ($a, $b) {
963 return (strtotime($a) < strtotime($b)) ? -1 : 1;
964 });
965
966 $cron_dates = array_slice($cron_dates, 0, -(intval(Dashboard\bmi_get_config('CRON:KEEP'))));
967 foreach ($cron_dates as $key => $value) {
968 $name = $cron_list[$cron_dates[$key]];
969 $name = explode('#%&', $name)[1];
970 Logger::log(__("Removing backup due to keep rules: ", 'backup-backup') . $name);
971 @unlink(BMI_BACKUPS . DIRECTORY_SEPARATOR . $name);
972 }
973
974 // Auto External Removal
975 $sortedMD5s = [];
976 $externalStorages = ['gdrive', 'dropbox', 'onedrive', 'FTP', 'sftp', 'aws', 'wasabi', 'backupbliss'];
977 $currentDomain = sanitize_text_field(parse_url(home_url(), PHP_URL_HOST));
978 foreach ($externalStorages as $storage) {
979 if (isset($availableBackups['external'][$storage])) {
980 $storageList = $availableBackups['external'][$storage];
981 foreach ($storageList as $md5 => $data) {
982 if ($storageList[$md5][6] == true && $storageList[$md5][5] == 'unlocked' && $storageList[$md5][9] === $currentDomain) {
983 $sortedMD5s[] = [$storageList[$md5][1], $md5, $storageList[$md5][0]];
984 }
985 }
986 }
987 }
988 $sortedMD5s = array_intersect_key($sortedMD5s, array_unique(array_map('serialize', $sortedMD5s)));
989
990 usort($sortedMD5s, function ($a, $b) {
991 return (strtotime($a[0]) < strtotime($b[0])) ? -1 : 1;
992 });
993
994 $sortedMD5s = array_slice($sortedMD5s, 0, -(intval(Dashboard\bmi_get_config('CRON:KEEP'))));
995 foreach ($sortedMD5s as $index => $data) {
996 $md5 = $data[1];
997 $name = $data[2];
998 Logger::log(__("Removing external backup due to keep rules: ", 'backup-backup') . $name);
999 $externalStorageManager->deleteBackup($md5);
1000 }
1001 }
1002
1003 public function set_last_cron($status, $time) {
1004 $file = BMI_TMP . DIRECTORY_SEPARATOR . '.last';
1005 file_put_contents($file, $status . '.' . $time);
1006 }
1007
1008 public function readFileSensitive($file) {
1009 if (!file_exists($file)) {
1010 echo '';
1011 return;
1012 }
1013
1014 $file = new \SplFileObject($file);
1015 $file->seek($file->getSize());
1016 $total_lines = $file->key() + 1;
1017
1018 $current_directory = Dashboard\bmi_get_config('STORAGE::LOCAL::PATH');
1019 $backups_path = $this->fixSlashes($current_directory . DIRECTORY_SEPARATOR . 'backups');
1020 $scanned_directory_all = array_diff(scandir($backups_path), ['..', '.']);
1021 $scanned_directory = array_values(preg_grep('/((.*).zip)/i', $scanned_directory_all));
1022
1023 for ($i = 0; $i < $total_lines; ++$i) {
1024
1025 $file->seek($i);
1026 $line = $this->escapeSensitive($file->current(), $current_directory, $scanned_directory);
1027
1028 echo esc_html($line);
1029 unset($line);
1030
1031 }
1032
1033 }
1034
1035 public function escapeSensitive($line, $current_directory, $scanned_directory) {
1036
1037 global $table_prefix;
1038
1039 $dir_name = basename($current_directory);
1040
1041 $line = preg_replace('/\:\ ((.*)\.zip)/', ': *****.zip', $line);
1042 $line = preg_replace('/(\"filename\":(.*)\.zip)\"/', '"filename": "*****.zip"', $line);
1043 $line = preg_replace('/\"http(.*)\"/', '"***site_url***"', $line);
1044 $line = preg_replace('/\:\ http(.*)\n/', ": ***site_url***\n", $line);
1045 $line = preg_replace('/\"\d{10}\"/', '"***secret_login***"', $line);
1046 $line = str_replace(ABSPATH, '***ABSPATH***/', $line);
1047 $line = str_replace($dir_name, '***backup_path***', $line);
1048 $line = str_replace($table_prefix, '***_', $line);
1049 $line = preg_replace('/^(.*?&sk=).*$/', '$1***', $line);
1050
1051 for ($i = 0; $i < sizeof($scanned_directory); ++$i) {
1052
1053 $backup_name = $scanned_directory[$i];
1054 $line = str_replace($backup_name, '***some_backup***', $line);
1055
1056 }
1057
1058 return $line;
1059
1060 }
1061
1062 public function handle_cron_backup() {
1063
1064 $plan_file = BMI_TMP . DIRECTORY_SEPARATOR . '.plan';
1065 $last_file = BMI_TMP . DIRECTORY_SEPARATOR . '.last';
1066
1067 // Abort if disabled
1068 if (Dashboard\bmi_get_config('CRON:ENABLED') !== true) {
1069
1070
1071 if (file_exists($plan_file)) @unlink($plan_file);
1072 if (file_exists($last_file)) @unlink($last_file);
1073
1074 return;
1075
1076 }
1077
1078 if (!file_exists($plan_file)) return;
1079
1080 // Planned time
1081 $plan = intval(@file_get_contents(BMI_TMP . DIRECTORY_SEPARATOR . '.plan'));
1082
1083 // Check difference
1084 if ((time() - $plan) > 3600) {
1085 Logger::log('Backup failed to run on proper time, but running now.');
1086 Logger::log('Planned time: ' . date('Y-m-d H:i:s', $plan));
1087 $this->backup_inproper_time($plan);
1088 }
1089
1090 // Now
1091 $now = time();
1092 $this->set_last_cron('0', $now);
1093
1094 // Extend execution time
1095 if ($this->isFunctionEnabled('headers_sent') && $this->isFunctionEnabled('session_status')) {
1096 if (!headers_sent() && session_status() === PHP_SESSION_DISABLED) {
1097 if ($this->isFunctionEnabled('ignore_user_abort')) @ignore_user_abort(true);
1098 if ($this->isFunctionEnabled('set_time_limit')) @set_time_limit(16000);
1099 if ($this->isFunctionEnabled('ini_set')) {
1100 @ini_set('max_execution_time', '259200');
1101 @ini_set('max_input_time', '259200');
1102 }
1103 }
1104 }
1105
1106 if (strlen(session_id()) > 0) session_write_close();
1107
1108 Logger::log(__("Automatic backup called at time: ", 'backup-backup') . date('Y-m-d, H:i:s'));
1109
1110 try {
1111 require_once BMI_INCLUDES . '/ajax.php';
1112 $isBackup = (file_exists(BMI_BACKUPS . '/.running') && (time() - filemtime(BMI_BACKUPS . '/.running')) <= 65) ? true : false;
1113 $isCron = (file_exists(BMI_BACKUPS . '/.cron') && (time() - filemtime(BMI_BACKUPS . '/.cron')) <= 65) ? true : false;
1114 if ($isCron) {
1115 return;
1116 }
1117
1118 if ($isBackup) {
1119 $this->handle_cron_error(__("Could not make the backup: Backup already running, please wait till it complete.", 'backup-backup'));
1120 $this->set_last_cron('2', $now);
1121 } else {
1122 touch(BMI_BACKUPS . '/.cron');
1123
1124 if (!defined('BMI_DOING_SCHEDULED_BACKUP')) {
1125 define('BMI_DOING_SCHEDULED_BACKUP', true);
1126 }
1127 $this->scheduleWatchDogCron();
1128
1129 $GLOBALS['bmi_current_action'] = 'create-backup';
1130 $handler = new BMI_Ajax();
1131 $handler->resetLatestLogs();
1132 $backup = $handler->prepareAndMakeBackup(true);
1133
1134 if ($backup['status'] == 'success') {
1135 if (isset($backup['filename'])) {
1136 Logger::log(__("Automatic backup successed: ", 'backup-backup') . $backup['filename']);
1137 } else {
1138 Logger::log(__("Automatic backup successed", 'backup-backup'));
1139 }
1140 $this->set_last_cron('1', $now);
1141 } elseif ($backup['status'] == 'background') {
1142 Logger::log(__('Scheduled backup is running in background: ', 'backup-backup') . $backup['filename']);
1143 $this->set_last_cron('1', $now);
1144 } elseif ($backup['status'] == 'msg') {
1145 $this->handle_cron_error($backup['why']);
1146 $this->set_last_cron('3', $now);
1147 } else {
1148 $this->handle_cron_error(__("Could not make the backup due to internal server error.", 'backup-backup'));
1149 $this->set_last_cron('4', $now);
1150 }
1151 }
1152 } catch (\Exception $e) {
1153 $this->handle_cron_error($e);
1154 $this->set_last_cron('5', $now);
1155 } catch (\Throwable $e) {
1156 $this->handle_cron_error($e);
1157 $this->set_last_cron('5', $now);
1158 }
1159
1160 require_once BMI_INCLUDES . '/cron/handler.php';
1161 $time = $this->get_next_cron();
1162
1163 wp_clear_scheduled_hook('bmi_do_backup_right_now');
1164 wp_schedule_single_event($time, 'bmi_do_backup_right_now');
1165
1166 $file = BMI_TMP . DIRECTORY_SEPARATOR . '.plan';
1167 file_put_contents($file, $time);
1168 }
1169
1170 public function scheduleWatchDogCron() {
1171 wp_clear_scheduled_hook('bmi_watchdog_cron');
1172 wp_schedule_single_event(time() + 120, 'bmi_watchdog_cron'); // 2 minutes
1173 }
1174
1175 public function handleWatchDogCron() {
1176 if (!file_exists(BMI_BACKUPS . '/.running')) return; // backup process did not stalled
1177 $lastAction = filemtime(BMI_BACKUPS . '/.running');
1178 if ((time() - $lastAction) > 300) { // large window to prevent false positives
1179 $this->handleErrorDuringBackup(
1180 __('The backup process appears to have stopped unexpectedly and was marked as stalled.', 'backup-backup'),
1181 __FILE__,
1182 __LINE__
1183 ); // cleanup running and cron flags
1184 $this->handle_cron_error(
1185 __('The scheduled backup did not complete because the process became unresponsive. Please check the plugin logs for more details.', 'backup-backup')
1186 );
1187 $this->set_last_cron('5', $lastAction);
1188 } else {
1189 $this->scheduleWatchDogCron();
1190 }
1191 }
1192
1193 public function enqueue_scripts() {
1194
1195 // Global
1196 if (in_array(get_current_screen()->id, ['toplevel_page_backup-migration', 'toplevel_page_backup-migration-network', 'plugins', 'plugins-network'])) { ?>
1197 <script type="text/javascript">
1198 let stars = <?php echo json_encode(plugin_dir_url(BMI_ROOT_FILE)); ?> + 'admin/images/stars.gif';
1199 let css_star = "background:url('" + stars + "')";
1200 document.addEventListener("DOMContentLoaded", function(event) {
1201 jQuery('[data-slug="backup-migration-pro"]').find('strong').html('<span>Backup Migration <b style="color: orange; ' + css_star + '">Pro</b></span>');
1202 jQuery('[data-slug="backup-backup-pro"]').find('strong').html('<span>Backup Migration <b style="color: orange; ' + css_star + '">Pro</b></span>');
1203 });
1204 </script>
1205 <?php }
1206
1207 // Only for BM Settings
1208 if (!in_array(get_current_screen()->id, ['toplevel_page_backup-migration', 'toplevel_page_backup-migration-network', 'update-core', 'plugins', 'plugin-install', 'themes','customize', 'plugins-network', 'plugin-install-network', 'themes-network']) && $this->backupbliss_space_issues() === false) return;
1209 wp_enqueue_script('backup-migration-script', $this->get_asset('js', 'backup-migration.min.js'), ['jquery'], BMI_VERSION, true);
1210 wp_localize_script('backup-migration-script', 'bmiVariables', [
1211 'nonce' => wp_create_nonce('backup-migration-ajax'),
1212 'stgLoading' => __('Loading, please wait...', 'backup-backup'),
1213 'stgStagingDefaultName' => __('staging', 'backup-backup'),
1214 'urlCopies' => __('URL copied successfully', 'backup-backup'),
1215 'isBeforeUpdateEnabled' => dashboard\bmi_get_config('OTHER:TRIGGER:BEFORE:UPDATES') ? 'true' : 'false',
1216 'maxUploadSize' => $this->getMaxUploadSize()
1217 ]);
1218
1219 }
1220
1221 public function phpSizeToB($phpSize) {
1222
1223 $sSuffix = strtoupper(substr($phpSize, -1));
1224
1225 if (!in_array($sSuffix, array('P','T','G','M','K'))) {
1226 return (int) $phpSize;
1227 }
1228
1229 $iValue = substr($phpSize, 0, -1);
1230 switch ($sSuffix) {
1231 case 'P': $iValue *= 1024;
1232 case 'T': $iValue *= 1024;
1233 case 'G': $iValue *= 1024;
1234 case 'M': $iValue *= 1024;
1235 case 'K': $iValue *= 1024;
1236 break;
1237 }
1238
1239 return (int) $iValue;
1240
1241 }
1242
1243 public function getMaxUploadSize() {
1244 $ten = (10 * 1024 * 1024);
1245 $max = min($this->phpSizeToB(ini_get('post_max_size')), $this->phpSizeToB(ini_get('upload_max_filesize')), $ten);
1246 return intval($max / 1024 / 1024);
1247 }
1248
1249 public function enqueue_styles() {
1250
1251 // Global styles
1252 wp_enqueue_style('backup-migration-style-icon', $this->get_asset('css', 'bmi-plugin-icon.min.css'), [], BMI_VERSION);
1253
1254 // Only for BM Settings and Update Core page and if there's no backupbliss space issues do not include the stylesheet.
1255 if (!in_array(get_current_screen()->id, ['toplevel_page_backup-migration', 'toplevel_page_backup-migration-network', 'update-core', 'plugins', 'plugin-install', 'themes','customize', 'plugins-network', 'plugin-install-network', 'themes-network']) && $this->backupbliss_space_issues() === false) return;
1256
1257 // Enqueue the style
1258 wp_enqueue_style('backup-migration-style', $this->get_asset('css', 'bmi-plugin.min.css'), [], BMI_VERSION);
1259
1260
1261 // Solve conflict with wp-svg-icons thats breaks the dashboard sections.
1262 wp_dequeue_style('wp-svg-icons');
1263 }
1264
1265 public function handle_after_actions() {
1266
1267 // Handle After Migration actions
1268 $afterMigrationLock = BMI_TMP . DIRECTORY_SEPARATOR . '.migrationFinished';
1269 if (file_exists($afterMigrationLock)) {
1270 if (strpos(site_url(), 'tastewp') !== false) {
1271
1272 if (function_exists('wp_load_alloptions')) {
1273 wp_load_alloptions(true);
1274 }
1275
1276 update_option('__tastewp_redirection_performed', true);
1277 update_option('auto_smart_tastewp_redirect_performed', 1);
1278 update_option('tastewp_auto_activated', true);
1279 update_option('__tastewp_sub_requested', true);
1280
1281 }
1282
1283 unlink($afterMigrationLock);
1284 }
1285
1286 }
1287
1288 public function handle_downloading() {
1289 global $wpdb;
1290 @error_reporting(0);
1291 $autologin_file = BMI_BACKUPS . '/.autologin';
1292 $ip = '127.0.0.1';
1293 if (isset($_SERVER['HTTP_CLIENT_IP'])) {
1294 $ip = $_SERVER['HTTP_CLIENT_IP'];
1295 } else {
1296 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1297 $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
1298 }
1299 if ($ip === false) {
1300 if (isset($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR'];
1301 }
1302 }
1303 $allowed = ['BMI_BACKUP', 'BMI_BACKUP_LOGS', 'PROGRESS_LOGS', 'AFTER_RESTORE', 'CURL_BACKUP'];
1304 $get_bmi = !empty($_GET['backup-migration']) ? sanitize_text_field($_GET['backup-migration']) : false;
1305 $get_bid = !empty($_GET['bmi-id']) ? sanitize_text_field($_GET['bmi-id']) : false;
1306 $get_pid = !empty($_GET['progress-id']) ? sanitize_text_field($_GET['progress-id']) : false;
1307 $get_is_uncensored = !empty($_GET['uncensored']) ? sanitize_text_field($_GET['uncensored']) : false;
1308 $crons_enabled = !empty($_GET['crons']) ? sanitize_text_field($_GET['crons']) : false;
1309 $secret_key = !empty($_GET['sk']) ? sanitize_text_field($_GET['sk']) : false;
1310
1311 if (isset($get_bmi) && in_array($get_bmi, $allowed) && $secret_key !== false) {
1312 $is_valid_secret = ($secret_key === Dashboard\bmi_get_config('REQUEST:SECRET')) && $get_bmi === 'CURL_BACKUP';
1313 $is_valid_nonce = wp_verify_nonce($secret_key, 'bmi_download_nonce');
1314 $is_valid_token = ($secret_key === get_transient('bmi_download_token'));
1315
1316 if (isset($get_bid) && strlen($get_bid) > 0 && isset($secret_key) && ($is_valid_secret || $is_valid_nonce || $is_valid_token)) {
1317 $type = $get_bmi;
1318
1319 if ($type == 'AFTER_RESTORE' && isset($get_pid)) {
1320 if (file_exists($autologin_file)) {
1321 $autoLoginMD = file_get_contents($autologin_file);
1322 unlink($autologin_file);
1323 $autoLoginMD = explode('_', $autoLoginMD);
1324 $hashedToken = $autoLoginMD[0];
1325 $ip = $autoLoginMD[1];
1326 $time = $autoLoginMD[2];
1327
1328 if (!hash_equals($hashedToken, wp_hash($get_bid, 'bmi_autologin'))) {
1329 wp_die(__('Invalid autologin data', 'backup-backup'));
1330 }
1331
1332 if ($time + 300 < time()) {
1333 wp_die(__('Autologin data has expired', 'backup-backup'));
1334 }
1335
1336 if ($ip !== $_SERVER['REMOTE_ADDR']) {
1337 wp_die(__('Invalid IP', 'backup-backup'));
1338 }
1339
1340 // Clear Elementor cache after restore
1341 if (class_exists( '\Elementor\Plugin' ) ) {
1342 $elementor = \Elementor\Plugin::$instance;
1343 if ( isset( $elementor->files_manager ) && method_exists( $elementor->files_manager, 'clear_cache' ) ) {
1344 try {
1345 $elementor->files_manager->clear_cache();
1346 } catch ( \Exception $e ) {
1347 error_log( 'Elementor native cache clear failed: ' . $e->getMessage() );
1348 }
1349 }
1350 }
1351
1352 $query = new \WP_User_Query(['role' => 'Administrator', 'count_total' => false, 'fields' => 'all']);
1353 $sqlres = $query->get_results();
1354
1355 if (sizeof($sqlres) > 0 && isset($sqlres[0]->ID) && isset($sqlres[0]->user_login)) {
1356
1357 $user = $sqlres[0];
1358 $adminID = $sqlres[0]->ID;
1359 $adminLogin = $sqlres[0]->user_login;
1360
1361 remove_all_actions('wp_login', -1000);
1362 wp_load_alloptions(true);
1363 clean_user_cache(get_current_user_id());
1364 clean_user_cache($adminID);
1365 wp_clear_auth_cookie();
1366 wp_set_current_user($adminID, $adminLogin);
1367 do_action('wp_login', $adminLogin, $user);
1368 update_user_caches($user);
1369
1370 }
1371 $cronsEnabledParam = $crons_enabled ? "&crons=true" : "";
1372 if (function_exists('is_multisite') && is_multisite()) {
1373 $url = network_admin_url('admin.php?page=backup-migration' . $cronsEnabledParam);
1374 } else {
1375 $url = admin_url('admin.php?page=backup-migration' . $cronsEnabledParam);
1376 }
1377 header('Location: ' . $url);
1378 exit;
1379 }
1380 } else if ($type == 'BMI_BACKUP') {
1381 if (Dashboard\bmi_get_config('STORAGE::DIRECT::URL') === 'true' || current_user_can('administrator')) {
1382
1383 $backupname = $get_bid;
1384 $file = $this->fixSlashes(BMI_BACKUPS . DIRECTORY_SEPARATOR . $backupname);
1385
1386 $outsideDir = false;
1387 if (!(file_exists($file) && $this->fixSlashes(dirname($file)) == $this->fixSlashes(BMI_BACKUPS))) {
1388 $outsideDir = true;
1389 }
1390
1391 $isZip = false;
1392 if (function_exists('mime_content_type')) {
1393 $isZip = strpos(strtolower(mime_content_type($file)), 'zip') !== false;
1394 } elseif (function_exists('wp_check_filetype')) {
1395 $filetype = wp_check_filetype($file);
1396 if ($filetype && isset($filetype['ext']) && in_array(strtolower($filetype['ext']), ['zip', 'tar', 'gz', 'tar.gz'])) {
1397 $isZip = true;
1398 }
1399 } else {
1400 // fallback to extension check
1401 $lower = strtolower($file);
1402 if (
1403 substr($lower, -4) === '.zip' ||
1404 substr($lower, -4) === '.tar' ||
1405 substr($lower, -7) === '.tar.gz'
1406 ) {
1407 $isArchive = true;
1408 }
1409 }
1410
1411 if ($outsideDir || !$isZip) {
1412 header('HTTP/1.0 423 Locked');
1413 esc_html_e( "Incorrect usage of the query request.", 'backup-backup' );
1414 exit;
1415 }
1416
1417 if (Dashboard\bmi_get_config('OTHER:DOWNLOAD:DIRECT') == 'true') {
1418 if (file_exists(BMI_BACKUPS . DIRECTORY_SEPARATOR . '.htaccess')) @unlink(BMI_BACKUPS . DIRECTORY_SEPARATOR . '.htaccess');
1419 if (file_exists(dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . '.htaccess')) @unlink(dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . '.htaccess');
1420 $wpcontent = trailingslashit(WP_CONTENT_DIR);
1421 $wpcs = strlen($wpcontent);
1422 $url = $this->fixSlashes(content_url(substr($file, $wpcs)), '/');
1423 $path = wp_redirect($url);
1424 exit;
1425 }
1426
1427 // Prevent parent directory downloading
1428 if (ob_get_contents()) ob_end_clean();
1429
1430 if ($this->isFunctionEnabled('ignore_user_abort')) @ignore_user_abort(true);
1431 if ($this->isFunctionEnabled('set_time_limit')) @set_time_limit(16000);
1432 if ($this->isFunctionEnabled('headers_sent') && $this->isFunctionEnabled('session_status')) {
1433 if (!headers_sent() && session_status() === PHP_SESSION_DISABLED) {
1434 if ($this->isFunctionEnabled('ini_set')) {
1435 @ini_set('max_execution_time', '259200');
1436 @ini_set('max_input_time', '259200');
1437 @ini_set('memory_limit', '-1');
1438 if (@ini_get('zlib.output_compression')) {
1439 @ini_set('zlib.output_compression', 'Off');
1440 }
1441 }
1442 }
1443 }
1444
1445 if (strlen(session_id()) > 0) session_write_close();
1446
1447 $fp = @fopen($file, 'rb');
1448
1449 // header('X-Sendfile: ' . $file);
1450 // header('X-Sendfile-Type: X-Accel-Redirect');
1451 // header('X-Accel-Redirect: ' . $file);
1452 // header('X-Accel-Buffering: yes');
1453 header('Expires: 0');
1454 header('Pragma: public');
1455 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1456 header('Content-Disposition: attachment; filename="' . $backupname . '"');
1457 header('Content-Type: application/octet-stream');
1458 header('Content-Transfer-Encoding: binary');
1459 header('Content-Length: ' . filesize($file));
1460 header('Content-Description: File Transfer');
1461 http_response_code(200);
1462
1463 if (ob_get_level()) ob_end_clean();
1464
1465 fpassthru($fp);
1466 fclose($fp);
1467 exit;
1468
1469 } else {
1470 if (ob_get_contents()) ob_end_clean();
1471 header('HTTP/1.0 423 Locked');
1472 if (ob_get_level()) ob_end_clean();
1473 esc_html_e( "Backup download is restricted (allowed for admins only).", 'backup-backup' );
1474 exit;
1475 }
1476 } else if ($type == 'BMI_BACKUP_LOGS') {
1477
1478 // Only Admin can download backup logs
1479 if (!(current_user_can('administrator') || current_user_can('do_backups'))) return;
1480
1481 if (ob_get_contents()) ob_end_clean();
1482 $backupname = $get_bid;
1483 $file = $this->fixSlashes(BMI_BACKUPS . DIRECTORY_SEPARATOR . $backupname);
1484
1485 // Prevent parent directory downloading
1486 if (file_exists($file) && $this->fixSlashes(dirname($file)) == $this->fixSlashes(BMI_BACKUPS)) {
1487 require_once BMI_INCLUDES . '/zipper/zipping.php';
1488
1489 $zipper = new Zipper();
1490 $logs = $zipper->getZipFileContentPlain($file, 'bmi_logs_this_backup.log');
1491 header('Content-Type: text/plain');
1492
1493 if ($logs) {
1494 header('Content-Disposition: attachment; filename="' . substr($backupname, 0, -4) . '.log"');
1495 http_response_code(200);
1496 if (ob_get_level()) ob_end_clean();
1497
1498 $logs = explode('\n', $logs);
1499 $current_directory = Dashboard\bmi_get_config('STORAGE::LOCAL::PATH');
1500 $backups_path = $this->fixSlashes($current_directory . DIRECTORY_SEPARATOR . 'backups');
1501 $scanned_directory_all = array_diff(scandir($backups_path), ['..', '.']);
1502 $scanned_directory = array_values(preg_grep('/((.*).zip)/i', $scanned_directory_all));
1503
1504 for ($i = 0; $i < sizeof($logs); ++$i) {
1505
1506 $line = $logs[$i];
1507 echo esc_html($this->escapeSensitive($line, $current_directory, $scanned_directory)) . "\n";
1508
1509 }
1510
1511 exit;
1512 } else {
1513 if (ob_get_level()) ob_end_clean();
1514 header('HTTP/1.0 404 Not found');
1515 esc_html_e("There was an error during getting logs, this file is not right log file.", 'backup-backup');
1516 exit;
1517 }
1518 }
1519
1520 } else if ($type == 'PROGRESS_LOGS') {
1521 $allowed_progress = [
1522 'latest_full.log',
1523 'latest.log',
1524 'latest_progress.log',
1525 'latest_migration_full.log',
1526 'latest_migration.log',
1527 'latest_migration_progress.log',
1528 'latest_staging_full.log',
1529 'latest_staging.log',
1530 'latest_staging_progress.log',
1531 'complete_logs.log'
1532 ];
1533 if (isset($get_pid) && in_array($get_pid, $allowed_progress)) {
1534
1535 $restricted_progress = ['complete_logs.log'];
1536 if (in_array($get_pid, $restricted_progress)) {
1537
1538 // Only Admin can download backup logs
1539 if (!(current_user_can('administrator') || current_user_can('do_backups'))) return;
1540
1541 }
1542
1543 header('Content-Type: text/plain');
1544 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1545 http_response_code(200);
1546 if (ob_get_contents()) ob_end_clean();
1547 if ($get_pid == 'complete_logs.log') {
1548 $file = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log';
1549 if (ob_get_level()) ob_end_clean();
1550 $this->readFileSensitive($file);
1551 exit;
1552 } else if ($get_pid == 'latest_full.log') {
1553 $progress = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest_progress.' . BMI_LOGS_SUFFIX . '.log';
1554 $logs = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest.' . BMI_LOGS_SUFFIX . '.log';
1555 if ((file_exists($progress) && file_exists($logs) && ((time() - filemtime($progress)) < (60 * 1))) || current_user_can('administrator')) {
1556 if (ob_get_level()) ob_end_clean();
1557 readfile($progress);
1558 echo "\n";
1559 if (isset($get_is_uncensored) && $get_is_uncensored && current_user_can('administrator')) readfile($logs);
1560 else $this->readFileSensitive($logs);
1561 exit;
1562 } else {
1563 if (file_exists($progress) && !(time() - filemtime($progress)) < (60 * 1)) {
1564 if (ob_get_level()) ob_end_clean();
1565 echo esc_html(__("Due to security reasons access to this file is disabled at this moment.", 'backup-backup')) . "\n";
1566 echo esc_html(__("Human readable: file expired.", 'backup-backup'));
1567 exit;
1568 } else {
1569 if (ob_get_level()) ob_end_clean();
1570 echo '';
1571 exit;
1572 }
1573 }
1574 } else if ($get_pid == 'latest_migration_full.log') {
1575 $progress = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest_migration_progress.' . BMI_LOGS_SUFFIX . '.log';
1576 $logs = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest_migration.' . BMI_LOGS_SUFFIX . '.log';
1577 if ((file_exists($progress) && file_exists($logs) && ((time() - filemtime($progress)) < (60 * 1))) || current_user_can('administrator')) {
1578 if (ob_get_level()) ob_end_clean();
1579 readfile($progress);
1580 echo "\n";
1581 if (isset($get_is_uncensored) && $get_is_uncensored && current_user_can('administrator')) readfile($logs);
1582 else $this->readFileSensitive($logs);
1583 exit;
1584 } else {
1585 if (file_exists($progress) && !(time() - filemtime($progress)) < (60 * 1)) {
1586 if (ob_get_level()) ob_end_clean();
1587 echo esc_html(__("Due to security reasons access to this file is disabled at this moment.", 'backup-backup')) . "\n";
1588 echo esc_html(__("Human readable: file expired.", 'backup-backup'));
1589 exit;
1590 } else {
1591 if (ob_get_level()) ob_end_clean();
1592 echo '';
1593 exit;
1594 }
1595 }
1596 } else if ($get_pid == 'latest_staging_full.log') {
1597 $progress = BMI_STAGING . DIRECTORY_SEPARATOR . 'latest_staging_progress.' . BMI_LOGS_SUFFIX . '.log';
1598 $logs = BMI_STAGING . DIRECTORY_SEPARATOR . 'latest_staging.' . BMI_LOGS_SUFFIX . '.log';
1599 if ((file_exists($progress) && file_exists($logs) && ((time() - filemtime($progress)) < (60 * 1))) || current_user_can('administrator')) {
1600 if (ob_get_level()) ob_end_clean();
1601 readfile($progress);
1602 echo "\n";
1603 $this->readFileSensitive($logs);
1604 exit;
1605 } else {
1606 if (file_exists($progress) && !(time() - filemtime($progress)) < (60 * 1)) {
1607 if (ob_get_level()) ob_end_clean();
1608 echo esc_html(__("Due to security reasons access to this file is disabled at this moment.", 'backup-backup')) . "\n";
1609 echo esc_html(__("Human readable: file expired.", 'backup-backup'));
1610 exit;
1611 } else {
1612 if (ob_get_level()) ob_end_clean();
1613 echo '';
1614 exit;
1615 }
1616 }
1617 } else {
1618 $filename = substr($get_pid, 0, strrpos($get_pid, '.log')) . '.' . BMI_LOGS_SUFFIX . '.log';
1619 $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . $filename;
1620 if ($get_pid == 'latest_staging.log') $file = BMI_STAGING . DIRECTORY_SEPARATOR . $filename;
1621 if ($get_pid == 'latest_staging_progress.log') $file = BMI_STAGING . DIRECTORY_SEPARATOR . $filename;
1622 if (file_exists($file) && (((time() - filemtime($file)) < (60 * 1)) || current_user_can('administrator'))) {
1623 if (ob_get_level()) ob_end_clean();
1624
1625 if (isset($get_is_uncensored) && $get_is_uncensored && current_user_can('administrator')) readfile($file);
1626 else $this->readFileSensitive($file);
1627
1628 echo "\n";
1629 if ($get_pid == 'latest.log') $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest_progress.' . BMI_LOGS_SUFFIX . '.log';
1630 if ($get_pid == 'latest_migration.log') $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest_migration_progress.' . BMI_LOGS_SUFFIX . '.log';
1631 if ($get_pid == 'latest_staging.log') $file = BMI_STAGING . DIRECTORY_SEPARATOR . 'latest_staging_progress.' . BMI_LOGS_SUFFIX . '.log';
1632 echo esc_html(__("[DOWNLOAD GENERATED] File downloaded on (server time): ", 'backup-backup')) . esc_html(date('Y-m-d H:i:s')) . "\n";
1633 echo esc_html(__("[DOWNLOAD GENERATED] Last update (seconds): ", 'backup-backup')) . esc_html(time() - filemtime($file)) . esc_html(__(" seconds ago ", 'backup-backup')) . "\n";
1634 echo esc_html(__("[DOWNLOAD GENERATED] Last update (date): ", 'backup-backup')) . esc_html(date('Y-m-d H:i:s', filemtime($file))) . " \n";
1635 exit;
1636 } else {
1637 if (file_exists($file) && !(time() - filemtime($file)) < (60 * 1)) {
1638 if (ob_get_level()) ob_end_clean();
1639 echo esc_html(__("Due to security reasons access to this file is disabled at this moment.", 'backup-backup')) . "\n";
1640 echo esc_html(__("Human readable: file expired.", 'backup-backup'));
1641 exit;
1642 } else {
1643 if (ob_get_level()) ob_end_clean();
1644 echo '';
1645 exit;
1646 }
1647 }
1648 }
1649 }
1650 } else if ($type == 'CURL_BACKUP') {
1651
1652 // We tried to use nonces here, but turns out that WordPress does not work well with generating nonces for cURL session.
1653 // We also tries to use cookiejar etc. but for researchers, this function is "verified" by process identy.
1654 // It's similarly safe as nonce, but it works in case we need, nonces gets rejected after second request.
1655 //
1656 // At the end, user who indeed would like to abuse this functionality, he can't do anything than helping the site owner.
1657 // Free browser will keep the process ongoing, user won't receive any details other than "success" - as long as the user know the process identy.
1658
1659 try {
1660
1661 // Load bypasser
1662 require_once BMI_INCLUDES . '/backup-process.php';
1663 $request = new Bypasser($get_bid, BMI_CONFIG_DIR, trailingslashit(WP_CONTENT_DIR), BMI_BACKUPS, trailingslashit(ABSPATH), plugin_dir_path(BMI_ROOT_FILE));
1664
1665 if (sizeof($request->remote_settings) === 0) return;
1666
1667 // Handle request
1668 $request->handle_batch();
1669 exit;
1670
1671 } catch (\Exception $e) {
1672
1673 error_log('There was an error with Backup Migration plugin: ' . $e->getMessage());
1674 Logger::error(__('Error handler: ', 'backup-backup') . 'ajax#01' . '|' . $e->getMessage());
1675 error_log(strval($e));
1676
1677 } catch (\Throwable $t) {
1678
1679 error_log('There was an error with Backup Migration plugin: ' . $t->getMessage());
1680 Logger::error(__('Error handler: ', 'backup-backup') . 'ajax#01' . '|' . $t->getMessage());
1681 error_log(strval($t));
1682
1683 }
1684
1685 }
1686 }
1687 }
1688 }
1689
1690 public function deactivation() {
1691 // auto deactivate pro version if exists
1692 if (function_exists('deactivate_plugins')) {
1693 $plugin = 'backup-backup-pro/backup-backup-pro.php';
1694 if (is_plugin_active($plugin)) {
1695 add_action('update_option_active_plugins', function () {
1696 $plugin = 'backup-backup-pro/backup-backup-pro.php';
1697 deactivate_plugins($plugin);
1698 });
1699 }
1700 }
1701 $this->revertLitespeed();
1702 require_once BMI_INCLUDES . '/cron/bootstrap.php';
1703 \BMI\Plugin\CRON\TaskManager::boot();
1704 \BMI\Plugin\CRON\TaskManager::on_deactivation();
1705 wp_clear_scheduled_hook('bmip_keepalive_cron');
1706 Logger::log(__("Plugin has been deactivated", 'backup-backup'));
1707 }
1708
1709 public static function res($array) {
1710 $GLOBALS['BMI::RESPONSE::SENT'] = true;
1711 echo json_encode(Backup_Migration_Plugin::sanitize($array));
1712
1713 if (defined('BMI_USING_CLI_FUNCTIONALITY') && BMI_USING_CLI_FUNCTIONALITY === true) {
1714 Logger::log('CLI response:');
1715 Logger::log(json_encode(Backup_Migration_Plugin::sanitize($array)));
1716 }
1717
1718 exit;
1719 }
1720
1721 public static function getAvailableMemoryInBytes() {
1722
1723 $totalMemory = @ini_get('memory_limit');
1724 if ($totalMemory == -1) {
1725
1726 $totalMemory = 32 * 1024 * 1024;
1727
1728 } else {
1729
1730 if (strpos($totalMemory, 'M') !== false || strpos($totalMemory, 'm') !== false) {
1731 $totalMemory = intval($totalMemory) * 1024 * 1024;
1732 } else if (strpos($totalMemory, 'G') !== false || strpos($totalMemory, 'g') !== false) {
1733 $totalMemory = intval($totalMemory) * 1024 * 1024 * 1024;
1734 } else if (strpos($totalMemory, 'K') !== false || strpos($totalMemory, 'k') !== false) {
1735 $totalMemory = intval($totalMemory) * 1024;
1736 } else {
1737 $totalMemory = intval($totalMemory);
1738 }
1739
1740 }
1741
1742 $availableMemory = $totalMemory - memory_get_usage(true);
1743
1744 return $availableMemory;
1745
1746 }
1747
1748 public static function sanitize($data = []) {
1749 $array = [];
1750
1751 if (is_array($data) || is_object($data)) {
1752 foreach ($data as $key => $value) {
1753 $key = ((is_numeric($key))?intval($key):sanitize_text_field($key));
1754
1755 if (is_array($value) || is_object($value)) {
1756 $array[$key] = Backup_Migration_Plugin::sanitize($value);
1757 } else {
1758 $array[$key] = sanitize_text_field($value);
1759 }
1760 }
1761 } elseif (is_string($data)) {
1762 return sanitize_text_field($data);
1763 } elseif (is_bool($data)) {
1764 return $data;
1765 } elseif (is_null($data)) {
1766 return 'false';
1767 } else {
1768 Logger::log(__("Unknow AJAX Sanitize Type: ", 'backup-backup') . gettype($data));
1769 wp_die();
1770 }
1771
1772 return $array;
1773 }
1774
1775 public static function fixLitespeed() {
1776 $litepath = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.litespeed';
1777 $htpath = ABSPATH . DIRECTORY_SEPARATOR . '.htaccess';
1778 if (!is_writable($htpath)) return ['status' => 'success'];
1779 if (file_exists($htpath)) {
1780 Backup_Migration_Plugin::revertLitespeed();
1781 $litespeed = @file_get_contents($litepath);
1782 $htaccess = @file_get_contents($htpath);
1783 $htaccess = explode("\n", $htaccess);
1784 $litespeed = explode("\n", $litespeed);
1785
1786 $hasAlready = false;
1787 for ($i = 0; $i < sizeof($htaccess); ++$i) {
1788 if (strpos($htaccess[$i], 'Backup Migration') !== false) {
1789 $hasAlready = true;
1790
1791 break;
1792 }
1793 }
1794
1795 if ($hasAlready) {
1796 return ['status' => 'success'];
1797 }
1798 $htaccess[] = '';
1799 for ($i = 0; $i < sizeof($litespeed); ++$i) {
1800 $htaccess[] = $litespeed[$i];
1801 }
1802
1803 file_put_contents($htpath, implode("\n", $htaccess));
1804 } else {
1805 copy($litepath, $htpath);
1806 }
1807
1808 return ['status' => 'success'];
1809 }
1810
1811 public static function revertLitespeed() {
1812 $htpath = ABSPATH . DIRECTORY_SEPARATOR . '.htaccess';
1813 $addline = true;
1814
1815 if (!is_writable($htpath)) return ['status' => 'success'];
1816 $htaccess = @file_get_contents($htpath);
1817 $htaccess = explode("\n", $htaccess);
1818 $htFilter = [];
1819
1820 for ($i = 0; $i < sizeof($htaccess); ++$i) {
1821 if (strpos($htaccess[$i], 'Backup Migration START')) {
1822 $addline = false;
1823
1824 continue;
1825 } elseif (strpos($htaccess[$i], 'Backup Migration END')) {
1826 $addline = true;
1827
1828 continue;
1829 } else {
1830 if ($addline == true) {
1831 $htFilter[] = $htaccess[$i];
1832 }
1833 }
1834 }
1835
1836 file_put_contents($htpath, trim(implode("\n", $htFilter)));
1837
1838 return ['status' => 'success'];
1839 }
1840
1841 public static function humanSize($bytes) {
1842 if (is_int($bytes)) {
1843 $label = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
1844 for ($i = 0; $bytes >= 1024 && $i < (count($label) - 1); $bytes /= 1024, $i++);
1845
1846 return (round($bytes, 2) . " " . $label[$i]);
1847 } else return $bytes;
1848 }
1849
1850 public static function fixSlashes($str, $slash = false) {
1851 // Old version
1852 // $str = str_replace('\\\\', DIRECTORY_SEPARATOR, $str);
1853 // $str = str_replace('\\', DIRECTORY_SEPARATOR, $str);
1854 // $str = str_replace('\/', DIRECTORY_SEPARATOR, $str);
1855 // $str = str_replace('/', DIRECTORY_SEPARATOR, $str);
1856
1857 // if ($str[strlen($str) - 1] == DIRECTORY_SEPARATOR) {
1858 // $str = substr($str, 0, -1);
1859 // }
1860
1861 // Since 1.3.2
1862 $protocol = '';
1863 if ($slash == false) $slash = DIRECTORY_SEPARATOR;
1864 if (substr($str, 0, 7) == 'http://') $protocol = 'http://';
1865 else if (substr($str, 0, 8) == 'https://') $protocol = 'https://';
1866
1867 $str = substr($str, strlen($protocol));
1868 $str = preg_replace('/[\\\\\/]+/', $slash, $str);
1869 $str = untrailingslashit($str);
1870
1871 return $protocol . $str;
1872 }
1873
1874 public static function canShareLogsOrShouldAsk() {
1875
1876 return 'not-allowed';
1877
1878 // REMOVED CODE:
1879 // $isAllowed = get_option('BMI_LOGS_SHARING_IS_ALLOWED', 'unknown');
1880 // $isAllowedConfig = Dashboard\bmi_get_config('LOGS::SHARING');
1881 //
1882 // if ($isAllowed == 'unknown' || empty($isAllowedConfig)) return 'ask';
1883 // else if ($isAllowed === 'yes' && $isAllowedConfig === 'yes') {
1884 // return 'allowed';
1885 // } else if ($isAllowed === 'no' && $isAllowedConfig === 'no') {
1886 // return 'not-allowed';
1887 // } else return 'ask';
1888
1889 }
1890
1891 public static function getRecentSize() {
1892 $folderNames = [ 'BACKUP:DATABASE' => 'database',
1893 "BACKUP:FILES::PLUGINS" => 'plugins',
1894 "BACKUP:FILES::UPLOADS" => 'uploads',
1895 "BACKUP:FILES::THEMES" => 'themes',
1896 "BACKUP:FILES::OTHERS" => 'contents_others',
1897 "BACKUP:FILES::WP" => 'wordpress'
1898 ];
1899
1900 $size = 0;
1901 foreach ($folderNames as $setting => $fileName) {
1902 if (Dashboard\bmi_get_config($setting) === 'true') {
1903 $size += get_transient('bmi_latest_size_' . $fileName);
1904 }
1905
1906 }
1907 return $size;
1908 }
1909
1910 public static function merge_arrays(&$array1, &$array2) {
1911 for ($i = 0; $i < sizeof($array2); ++$i) {
1912 $array1[] = $array2[$i];
1913 }
1914 }
1915
1916 public static function getDefaultDisabledPaths() {
1917 require_once BMI_INCLUDES . '/staging/controller.php';
1918 $staging = new Staging('..ajax..');
1919 $stagingSites = $staging->getStagingSites(true);
1920 $stagingSitesPaths = [];
1921 // Get all directory names of staging sites
1922 foreach ($stagingSites as $index => $site) {
1923
1924 // Convert every directory to their location path
1925 $stagingSitesPaths[] = '***ABSPATH***/' . $site['name'];
1926
1927 }
1928
1929 $ignored_paths_default = [
1930 BMI_CONFIG_DIR,
1931 BMI_BACKUPS,
1932 BMI_ROOT_DIR,
1933 constant('BMI_PRO_ROOT_DIR'),
1934 "***ABSPATH***/wp-content/ai1wm-backups",
1935 "***ABSPATH***/wp-content/ai1wm-backups-old",
1936 "***ABSPATH***/wp-content/mwp-download",
1937 "***ABSPATH***/wp-content/uploads/wp-clone",
1938 "***ABSPATH***/wp-content/updraft",
1939 "***ABSPATH***/wp-content/backups-dup-pro",
1940 "***ABSPATH***/wp-content/wpvividbackups",
1941 "***ABSPATH***/wp-content/backup-guard",
1942 "***ABSPATH***/wp-content/backuply",
1943 "***ABSPATH***/wp-content/backups-dup-lite",
1944 "***ABSPATH***/wp-content/uploads/backupbuddy_backups",
1945 "***ABSPATH***/wp-content/uploads/wp-file-manager-pro",
1946 "***ABSPATH***/wp-content/uploads/wp-file-manager",
1947 "***ABSPATH***/wp-content/plugins/akeebabackupwp",
1948 "***ABSPATH***/wp-content/uploads/jetbackup",
1949 "***ABSPATH***/wp-content/uploads/backup-guard",
1950 "***ABSPATH***/wp-content/uploads/wp-migrate-db",
1951 "***ABSPATH***/wp-content/uploads/wpforms/.htaccess.cpmh3129",
1952 "***ABSPATH***/wp-content/uploads/gravity_forms/.htaccess.cpmh3129",
1953 "***ABSPATH***/.htaccess.cpmh3129",
1954 "***ABSPATH***/logs/traffic.html/.md5sums",
1955 "***ABSPATH***/wp-config.php",
1956 "***ABSPATH***/wp-content/backup-migration-config.php",
1957 ];
1958 $ignored_paths = array_merge($ignored_paths_default, $stagingSitesPaths);
1959 array_walk($ignored_paths, function(&$path){
1960 $path = self::fixSlashes(str_replace('***ABSPATH***', ABSPATH, $path));
1961 });
1962 return $ignored_paths;
1963 }
1964
1965 private function get_asset($base = '', $asset = '') {
1966 return BMI_ASSETS . '/' . $base . '/' . $asset;
1967 }
1968
1969 /**
1970 * Extend the execution time for the plugin
1971 *
1972 * @return void
1973 */
1974 public static function extend_execution_time() {
1975 if (self::isFunctionEnabled('headers_sent') && self::isFunctionEnabled('session_status')) {
1976 if (!headers_sent() && session_status() === PHP_SESSION_DISABLED) {
1977 if (self::isFunctionEnabled('ignore_user_abort')) @ignore_user_abort(true);
1978 if (self::isFunctionEnabled('set_time_limit')) @set_time_limit(16000);
1979 if (self::isFunctionEnabled('ini_set')) {
1980 @ini_set('max_execution_time', '259200');
1981 @ini_set('max_input_time', '259200');
1982 }
1983 }
1984 }
1985 }
1986
1987 public static function getRetryAfterIfAvailable($ch, $response)
1988 {
1989 $phpVersion = phpversion();
1990 $curlVersion = curl_version();
1991 // Available as of PHP 8.2.0 and cURL 7.66.0
1992 if (version_compare($phpVersion, '8.2.0', '>=') && version_compare($curlVersion['version'], '7.66.0', '>=')) {
1993 $retryAfter = curl_getinfo($ch, CURLINFO_RETRY_AFTER);
1994 if ($retryAfter !== false) {
1995 return $retryAfter;
1996 }
1997 }else {
1998 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1999 $header = substr($response, 0, $header_size);
2000 $headers = explode("\r\n", $header);
2001 foreach ($headers as $h) {
2002 if (preg_match('/^Retry-After:\s+(\d+)/i', $h, $matches)) {
2003 return intval($matches[1]);
2004 }
2005 }
2006 }
2007 return false;
2008
2009 }
2010
2011 public static function bmiNeedsUpdate($forPro = false) {
2012 if ($forPro) {
2013 if (!defined('BMI_BACKUP_PRO') || BMI_BACKUP_PRO != 1 ) {
2014 return false;
2015 }
2016 $plugin_file = plugin_basename(BMI_PRO_ROOT_FILE);
2017 } else {
2018 $plugin_file = plugin_basename(BMI_ROOT_FILE);
2019 }
2020
2021 $update_cache = get_site_transient('update_plugins');
2022
2023 if (!is_object($update_cache)) {
2024 $update_cache = new \stdClass();
2025 }
2026
2027 // Check if there's an update available in the cache
2028 if (!empty($update_cache->response) && !empty($update_cache->response[$plugin_file])) {
2029 return true;
2030 }
2031
2032 // If no update in cache, check if our version is older than the checked version
2033 if (!empty($update_cache->checked) && !empty($update_cache->checked[$plugin_file])) {
2034 if (version_compare(BMI_VERSION, $update_cache->checked[$plugin_file], '<')) {
2035 return true;
2036 }
2037 }
2038
2039 return false;
2040 }
2041
2042 public static function isPluginAutoUpdateEnabled( $plugin_file ) {
2043 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
2044 return in_array( $plugin_file, $auto_updates, true );
2045 }
2046
2047 public static function escapeSQLIDentifier($identifier) {
2048 global $wpdb;
2049
2050 // Use native %i if WordPress 6.2+ is available
2051 if (version_compare($GLOBALS['wp_version'], '6.2', '>=')) {
2052 return $wpdb->prepare('%i', $identifier);
2053 }
2054
2055 // Fallback for older WordPress versions
2056 $identifier = trim($identifier, '`');
2057 if (!preg_match('/^[a-zA-Z0-9_]+$/', $identifier)) {
2058 throw new \InvalidArgumentException("Invalid SQL identifier: " . esc_html($identifier));
2059 }
2060 return '`' . str_replace('`', '``', $identifier) . '`';
2061 }
2062
2063 public function fireBMIPAction($action, $actionParams = [], $executionParams = [], $executionType = 'INITIATOR_URL') {
2064 if (!defined('BMI_PRO_INC') || !file_exists(BMI_PRO_INC . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'class-bmi-pro-action-initiator.php')) {
2065 return;
2066 }
2067
2068 try {
2069 require_once BMI_PRO_INC . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'class-bmi-pro-action-initiator.php';
2070 $actionInitiator = new \BMI\Plugin\Services\ActionInitiator($action, $actionParams, Dashboard\bmi_get_config('REQUEST:SECRET'));
2071 $actionInitiator->execute($executionType, $executionParams);
2072 } catch (\Exception $e) {
2073 Logger::error(__('Error firing BMI Pro action: ', 'backup-backup') . $action . '|' . $e->getMessage());
2074 } catch (\Throwable $t) {
2075 Logger::error(__('Error firing BMI Pro action: ', 'backup-backup') . $action . '|' . $t->getMessage());
2076 }
2077 }
2078
2079 public function handleUploadComplete($md5) {
2080
2081 do_action('bmip_fire_action', 'HANDLE_BACKUP_UPLOADED_COMPLETE', ['md5' => $md5], ['timeout' => 0.01 , 'async' => false]);
2082
2083 }
2084
2085 /**
2086 * verifyFileMd5 - Verifies if the file has the same md5 as the one provided, can be used for integrity checks before restore downloaded backup
2087 *
2088 * @param {string} $filePath - Path to the file which md5 should be checked
2089 * @param {string} $expectedMd5 - Expected md5 hash of the file
2090 * @return bool - True if the file's md5 matches the expected md5, false otherwise
2091 */
2092 public static function verifyFileMd5($filePath, $expectedMd5) {
2093 if (!file_exists($filePath)) {
2094 Logger::error("File for MD5 verification does not exist: " . $filePath);
2095 return false;
2096 }
2097 require_once BMI_INCLUDES . '/services/class-file-hasher.php';
2098 $fileMd5 = FileHasher::compute($filePath);
2099
2100 if ($fileMd5 === $expectedMd5) {
2101 return true;
2102 } else {
2103 $manifestPath = BMI_BACKUPS . DIRECTORY_SEPARATOR . $expectedMd5 . '.json';
2104 if (file_exists($manifestPath)) {
2105 $manifestContent = file_get_contents($manifestPath);
2106 if ($manifestContent !== false) {
2107 $manifestData = json_decode($manifestContent, true);
2108 if (json_last_error() === JSON_ERROR_NONE && isset($manifestData['chained_hash_chunk_size'])) {
2109 $chunkSize = $manifestData['chained_hash_chunk_size'];
2110 $chainedHash = FileHasher::compute($filePath, FileHasher::CHAINED, $chunkSize);
2111 if ($chainedHash === $expectedMd5) {
2112 return true;
2113 } else {
2114 Logger::error("MD5 mismatch for file: " . $filePath . " using chained hash with chunk size " . $chunkSize . ".");
2115 return false;
2116 }
2117 } else {
2118 Logger::error("Invalid manifest JSON for MD5 verification: " . $manifestPath);
2119 return false;
2120 }
2121 } else {
2122 Logger::error("Failed to read manifest file for MD5 verification: " . $manifestPath);
2123 return false;
2124 }
2125 } else {
2126 Logger::error("Manifest file for MD5 verification does not exist: " . $manifestPath);
2127 return false;
2128 }
2129 }
2130 }
2131
2132 public function fetchFatalErrorVerbose($fatalErrorMsg) {
2133 if (empty($fatalErrorMsg)) return 'unknown_fatal_error';
2134
2135 $msgLower = strtolower($fatalErrorMsg);
2136
2137 if (strpos($msgLower, 'maximum execution time') !== false) {
2138 return 'max_execution_time_exceeded';
2139 }
2140
2141 if (strpos($msgLower, 'allowed memory size') !== false) {
2142 return 'memory_size_exhausted';
2143 }
2144
2145 return 'unknown_fatal_error';
2146 }
2147
2148 }
2149