PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.3.6
Backup Migration v1.3.6
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 / bypasser.php
backup-backup / includes Last commit date
banner 2 years ago check 2 years ago cli 2 years ago cron 2 years ago dashboard 2 years ago database 2 years ago extracter 2 years ago htaccess 2 years ago progress 2 years ago scanner 2 years ago staging 2 years ago uploader 2 years ago zipper 2 years ago .htaccess 2 years ago activation.php 2 years ago ajax.php 2 years ago analyst.php 2 years ago backup-heart.php 2 years ago bypasser.php 2 years ago cli-handler.php 2 years ago compatibility.php 2 years ago config.php 2 years ago constants.php 2 years ago initializer.php 2 years ago logger.php 2 years ago restore-batching.php 2 years ago
bypasser.php
1242 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\Heart;
5
6 // Usage
7 use BMI\Plugin\BMI_Logger AS Logger;
8 use BMI\Plugin\Progress\BMI_ZipProgress AS Output;
9 use BMI\Plugin\Checker\System_Info as SI;
10 use BMI\Plugin\Dashboard as Dashboard;
11 use BMI\Plugin\Database\BMI_Database as Database;
12 use BMI\Plugin\Database\BMI_Database_Exporter as BetterDatabaseExport;
13 use BMI\Plugin\Backup_Migration_Plugin as BMP;
14 use BMI\Plugin\BMI_Pro_Core as Pro_Core;
15 use BMI\Plugin AS BMI;
16
17 // Exit on direct access
18 if (!(defined('BMI_CURL_REQUEST') || defined('ABSPATH'))) exit;
19
20 // Fixes for some cases
21 require_once BMI_INCLUDES . '/compatibility.php';
22
23 /**
24 * Main class to handle heartbeat of the backup
25 */
26 class BMI_Backup_Heart {
27
28 public $it;
29 public $dbit;
30 public $abs;
31 public $dir;
32 public $url;
33 public $curl;
34 public $config;
35 public $content;
36 public $backups;
37 public $dblast;
38 public $output;
39
40 public $identy;
41 public $manifest;
42 public $backupname;
43 public $safelimit;
44 public $total_files;
45 public $rev;
46 public $backupstart;
47 public $filessofar;
48 public $identyfile;
49 public $browserSide;
50
51 public $identyFolder;
52 public $fileList;
53 public $dbfile;
54 public $db_dir_v2;
55 public $db_v2_engine;
56
57 public $headersSet;
58 public $final_made;
59 public $final_batch;
60 public $dbitJustFinished;
61 public $lock_cli;
62
63 public $_zip;
64 public $_lib;
65 public $batches_left;
66
67 // Prepare the request details
68 function __construct($curl = false, $config = false, $content = false, $backups = false, $abs = false, $dir = false, $url = false, $remote_settings = [], $it = 0, $dbit = 0, $dblast = 0) {
69
70 if (isset($remote_settings['bmitmp'])) {
71 if (!defined('BMI_TMP')) define('BMI_TMP', $remote_settings['bmitmp']);
72 }
73
74 $this->it = intval($it);
75 $this->dbit = intval($dbit);
76 $this->abs = $abs;
77 $this->dir = $dir;
78 $this->url = $url;
79 $this->curl = $curl;
80 $this->config = $config;
81 $this->content = $content;
82 $this->backups = $backups;
83 $this->dblast = $dblast;
84
85 $this->identy = $remote_settings['identy'];
86 $this->manifest = $remote_settings['manifest'];
87 $this->backupname = $remote_settings['backupname'];
88 $this->safelimit = intval($remote_settings['safelimit']);
89 $this->total_files = $remote_settings['total_files'];
90 $this->rev = intval($remote_settings['rev']);
91 $this->backupstart = $remote_settings['start'];
92 $this->filessofar = intval($remote_settings['filessofar']);
93 $this->identyfile = BMI_TMP . DIRECTORY_SEPARATOR . '.' . $this->identy;
94 $this->browserSide = ($remote_settings['browser'] === true || $remote_settings['browser'] === 'true') ? true : false;
95
96 $this->identyFolder = BMI_TMP . DIRECTORY_SEPARATOR . 'bg-' . $this->identy;
97 $this->fileList = BMI_TMP . DIRECTORY_SEPARATOR . 'files_latest.list';
98 $this->dbfile = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_database_backup.sql';
99 $this->db_dir_v2 = BMI_TMP . DIRECTORY_SEPARATOR . 'db_tables';
100 $this->db_v2_engine = false;
101
102 $this->headersSet = false;
103 $this->final_made = false;
104 $this->final_batch = false;
105 $this->dbitJustFinished = false;
106
107 $this->lock_cli = BMI_BACKUPS . '/.backup_cli_lock';
108 if ($this->it > 1) @touch($this->lock_cli);
109
110 }
111
112 // Human size from bytes
113 public static function humanSize($bytes) {
114 if (is_int($bytes)) {
115 $label = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
116 for ($i = 0; $bytes >= 1024 && $i < (count($label) - 1); $bytes /= 1024, $i++);
117
118 return (round($bytes, 2) . " " . $label[$i]);
119 } else return $bytes;
120 }
121
122 // Create new process
123 public function send_beat($manual = false, &$logger = null) {
124
125 try {
126
127 $header = array(
128 // 'Content-Type:Application/x-www-form-urlencoded',
129 'Content-Accept:*/*',
130 'Access-Control-Allow-Origin:*',
131 'Content-ConfigDir:' . $this->config,
132 'Content-Content:' . $this->content,
133 'Content-Backups:' . $this->backups,
134 'Content-Identy:' . $this->identy,
135 'Content-Url:' . $this->url,
136 'Content-Abs:' . $this->abs,
137 'Content-Dir:' . $this->dir,
138 'Content-Manifest:' . $this->manifest,
139 'Content-Name:' . $this->backupname,
140 'Content-Safelimit:' . $this->safelimit,
141 'Content-Start:' . $this->backupstart,
142 'Content-Filessofar:' . $this->filessofar,
143 'Content-Total:' . $this->total_files,
144 'Content-Rev:' . $this->rev,
145 'Content-It:' . $this->it,
146 'Content-Dbit:' . $this->dbit,
147 'Content-Dblast:' . $this->dblast,
148 'Content-Bmitmp:' . BMI_TMP,
149 'Content-Browser:' . $this->browserSide ? 'true' : 'false'
150 );
151
152 // if (!defined('CURL_HTTP_VERSION_2_0')) {
153 // define('CURL_HTTP_VERSION_2_0', CURL_HTTP_VERSION_1_0);
154 // }
155
156 // $ckfile = tempnam(BMI_TMP, "CURLCOOKIE");
157 $c = curl_init();
158 curl_setopt($c, CURLOPT_POST, 1);
159 curl_setopt($c, CURLOPT_TIMEOUT, 10);
160 // curl_setopt($c, CURLOPT_NOBODY, true);
161 curl_setopt($c, CURLOPT_VERBOSE, false);
162 curl_setopt($c, CURLOPT_HEADER, false);
163 // curl_setopt($c, CURLOPT_COOKIEJAR, $ckfile);
164 curl_setopt($c, CURLOPT_URL, $this->url);
165 curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
166 curl_setopt($c, CURLOPT_MAXREDIRS, 10);
167 curl_setopt($c, CURLOPT_COOKIESESSION, true);
168 // curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 1);
169 curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
170 curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 0);
171 curl_setopt($c, CURLOPT_SSL_VERIFYPEER, 0);
172 curl_setopt($c, CURLOPT_HTTPHEADER, $header);
173 curl_setopt($c, CURLOPT_CUSTOMREQUEST, 'POST');
174 curl_setopt($c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
175 // curl_setopt($c, CURLOPT_USERAGENT, 'BMI_HEART_TIMEOUT_BYPASS_' . $this->it);
176 curl_setopt($c, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
177
178 $r = curl_exec($c);
179
180 if ($manual === true && $logger !== null) {
181 if ($r === false) {
182 if (intval(curl_errno($c)) !== 28) {
183 Logger::error(print_r(curl_getinfo($c), true));
184 Logger::error(curl_errno($c) . ': ' . curl_error($c));
185 $logger->log('There was something wrong with the request:', 'WARN');
186 $logger->log(curl_errno($c) . ': ' . curl_error($c), 'WARN');
187 }
188 } else {
189 $logger->log('Request sent successfully, without error returned.', 'SUCCESS');
190 }
191 }
192
193 curl_close($c);
194 // if (file_exists($ckfile)) @unlink($ckfile);
195 if (isset($this->output)) $this->output->end();
196
197 } catch (Exception $e) {
198
199 error_log($e->getMessage());
200 if (isset($this->output)) $this->output->end();
201
202 } catch (Throwable $e) {
203
204 error_log($e->getMessage());
205 if (isset($this->output)) $this->output->end();
206
207 }
208
209 }
210
211 // Load backup logger
212 public function load_logger() {
213
214 require_once BMI_INCLUDES . '/logger.php';
215 require_once BMI_INCLUDES . '/progress/logger-only.php';
216
217 $this->output = new Output();
218 $this->output->start();
219
220 }
221
222 // Remove common files
223 public function remove_commons() {
224
225 // Remove list if exists
226 $identyfile = $this->identyfile;
227 $logfile = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_logs_this_backup.log';
228 $clidata = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_cli_data.json';
229 if (file_exists($this->fileList)) @unlink($this->fileList);
230 if (file_exists($this->dbfile)) @unlink($this->dbfile);
231 if (file_exists($this->manifest)) @unlink($this->manifest);
232 if (file_exists($logfile)) @unlink($logfile);
233 if (file_exists($clidata)) @unlink($clidata);
234 if (file_exists($identyfile)) @unlink($identyfile);
235 if (file_exists($identyfile . '-running')) @unlink($identyfile . '-running');
236 if (file_exists($this->lock_cli)) @unlink($this->lock_cli);
237
238 // Remove backup
239 if (file_exists(BMI_BACKUPS . '/.running')) @unlink(BMI_BACKUPS . '/.running');
240 if (file_exists(BMI_BACKUPS . '/.abort')) @unlink(BMI_BACKUPS . '/.abort');
241
242 // Remove group folder
243 if (file_exists($this->identyFolder)) {
244 $files = glob($this->identyFolder . '/*');
245 foreach ($files as $file) if (is_file($file)) unlink($file);
246 @rmdir($this->identyFolder);
247 }
248
249 // Remove tmp database files
250 if (file_exists($this->db_dir_v2) && is_dir($this->db_dir_v2)) {
251 $files = glob($this->db_dir_v2 . '/*');
252 foreach ($files as $file) if (is_file($file)) unlink($file);
253 if (is_dir($this->db_dir_v2)) @rmdir($this->db_dir_v2);
254 }
255
256 }
257
258 // Make success
259 public function send_success() {
260
261 // Set header for browser
262 if ($this->browserSide && $this->headersSet === false) {
263
264 // Content finished
265 header('Content-Finished: true');
266 header('Content-It: ' . ($this->it + 1));
267 header('Content-Dbit: ' . $this->dbit);
268 header('Content-Dblast: ' . $this->dblast);
269 header('Content-Filessofar: ' . $this->filessofar);
270 http_response_code(200);
271 $this->headersSet = true;
272
273 }
274
275 // Display the success
276 $this->output->log('Backup completed successfully!', 'SUCCESS');
277 $this->output->log('#001', 'END-CODE');
278
279 // Remove common files
280 $this->remove_commons();
281
282 // End logger
283 if (isset($this->output)) @$this->output->end();
284
285 $this->actionsAfterProcess(true);
286
287 // End the process
288 exit;
289
290 }
291
292 // Make error
293 public function send_error($reason = false, $abort = false) {
294
295 // Set header for browser
296 if ($this->browserSide && $this->headersSet === false) {
297
298 // Content finished
299 header('Content-Finished: false');
300 header('Content-It: ' . ($this->it + 1));
301 header('Content-Dbit: ' . $this->dbit);
302 header('Content-Dblast: ' . $this->dblast);
303 header('Content-Filessofar: ' . $this->filessofar);
304 http_response_code(200);
305 $this->headersSet = true;
306
307 }
308
309 // Log error
310 $this->output->log('Something went wrong with background process... ' . '(part: ' . $this->it . ')', 'ERROR');
311 if ($reason !== false) $this->output->log('Reason: ' . $reason, 'ERROR');
312 $this->output->log('Removing backup files... ', 'ERROR');
313
314 // Remove common files
315 $this->remove_commons();
316
317 // Remove backup
318 if (file_exists(BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupname)) @unlink(BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupname);
319
320 // Abort step
321 $this->output->log('Aborting backup... ', 'STEP');
322 if ($abort === false) $this->output->log('#002', 'END-CODE');
323 else $this->output->log('#003', 'END-CODE');
324 if (isset($this->output)) @$this->output->end();
325
326 $this->actionsAfterProcess();
327 exit;
328
329 }
330
331 // Group files for batches
332 public function make_file_groups() {
333
334 if (!(file_exists($this->fileList) && is_readable($this->fileList))) {
335 return $this->send_error('File list is not accessible or does not exist, try to run your backup process once again.', true);
336 }
337
338 $this->output->log('Making batches for each process...', 'STEP');
339 $list_path = $this->fileList;
340
341 $file = fopen($list_path, 'r');
342 $this->output->log('Reading list file...', 'INFO');
343 $first_line = explode('_', fgets($file));
344 $files = intval($first_line[0]);
345 $firstmax = intval($first_line[1]);
346
347 if ($files > 0) {
348 $batches = 100;
349 if ($files <= 200) $batches = 100;
350 if ($files > 200) $batches = 200;
351 if ($files > 1600) $batches = 400;
352 if ($files > 3200) $batches = 800;
353 if ($files > 6400) $batches = 1600;
354 if ($files > 12800) $batches = 3200;
355 if ($files > 25600) $batches = 5000;
356 if ($files > 30500) $batches = 10000;
357 if ($files > 60500) $batches = 20000;
358 if ($files > 90500) $batches = 40000;
359
360 $this->output->log('Each batch will contain up to ' . $batches . ' files.', 'INFO');
361 $this->output->log('Large files takes more time, you will be notified about those.', 'INFO');
362
363 $folder = $this->identyFolder;
364 mkdir($folder, 0755, true);
365
366 $limitcrl = 96;
367 if (BMI_CLI_REQUEST === true) {
368 $limitcrl = 512;
369 if ($files > 30000) $limitcrl = 1024;
370 }
371
372 $i = 0; $bigs = 0; $prev = 0; $currsize = 0;
373 while (($line = fgets($file)) !== false) {
374
375 $line = explode(',', $line);
376 $last = sizeof($line) - 1;
377 $size = intval($line[$last]);
378 unset($line[$last]);
379 $line = implode(',', $line);
380
381 $i++;
382 if ($firstmax != -1 && $i > $firstmax) $bigs++;
383 $suffix = intval(ceil(abs($i / $batches))) + $bigs;
384
385 if ($prev == $suffix) {
386 $currsize += $size;
387 } else {
388 $currsize = $size;
389 $prev = $suffix;
390 }
391
392 $skip = false;
393 if ($currsize > ($limitcrl * (1024 * 1024))) $skip = true;
394
395 $groupFile = $folder . DIRECTORY_SEPARATOR . $this->identy . '-' . $suffix . '.files';
396 $group = fopen($groupFile, 'a');
397 fwrite($group, $line . ',' . $size . "\r\n");
398 fclose($group);
399
400 if ($skip === true) $bigs++;
401 unset($line);
402
403 }
404
405 fclose($file);
406 usleep(100);
407 if (file_exists($this->fileList)) @unlink($this->fileList);
408
409 } else {
410
411 $this->output->log('No file found to be backed up, omitting files.', 'INFO');
412
413 }
414
415 if (file_exists($this->fileList)) @unlink($this->fileList);
416 $this->output->log('Batches completed...', 'SUCCESS');
417
418 }
419
420 // Final batch
421 public function get_final_batch() {
422
423 $db_root_dir = BMI_TMP . DIRECTORY_SEPARATOR;
424 $logs = $db_root_dir . 'bmi_logs_this_backup.log';
425
426 $log_file = fopen($logs, 'w');
427 fwrite($log_file, file_get_contents(BMI_BACKUPS . DIRECTORY_SEPARATOR . 'latest.log'));
428 fclose($log_file);
429
430 $files = [$logs, $this->manifest];
431
432 return $files;
433
434 }
435
436 // Final logs
437 public function log_final_batch() {
438
439 $this->output->log('Finalizing backup', 'STEP');
440 $this->output->log('Closing files and archives', 'STEP');
441 $this->output->log('Archiving of ' . $this->total_files . ' files took: ' . number_format(microtime(true) - floatval($this->backupstart), 2) . 's', 'INFO');
442
443 if (!BMI_CLI_REQUEST) {
444 if (!$this->browserSide) sleep(1);
445 }
446
447 if (file_exists(BMI_BACKUPS . '/.abort')) {
448 $this->send_error('Backup aborted manually by user.', true);
449 return;
450 }
451
452 $this->send_success();
453
454 }
455
456 // Load batch
457 public function load_batch() {
458
459 if (!(file_exists($this->identyFolder) && is_dir($this->identyFolder))) {
460 return $this->send_error('Temporary directory does not exist, please start the backup once again.', true);
461 }
462
463 $allFiles = scandir($this->identyFolder);
464 $files = array_slice((array) $allFiles, 2);
465 if (sizeof($files) > 0) {
466
467 $largest = $files[0]; $prev_size = 0;
468 for ($i = 0; $i < sizeof($files); ++$i) {
469 $curr_size = filesize($this->identyFolder . DIRECTORY_SEPARATOR . $files[$i]);
470 if ($curr_size > $prev_size) {
471 $largest = $files[$i];
472 $prev_size = $curr_size;
473 }
474 }
475 $this->batches_left = sizeof($files);
476
477 if (sizeof($files) == 1) {
478 $this->final_batch = true;
479 }
480
481 return $this->identyFolder . DIRECTORY_SEPARATOR . $largest;
482
483 } else {
484
485 $this->log_final_batch();
486 return false;
487
488 }
489
490 }
491
492 // Cut Path for ZIP structure
493 public function cutDir($file) {
494
495 if (substr($file, -4) === '.sql') {
496
497 if ($this->db_v2_engine == true) {
498
499 return 'db_tables' . DIRECTORY_SEPARATOR . basename($file);
500
501 } else {
502
503 return basename($file);
504
505 }
506
507 } else {
508
509 return basename($file);
510
511 }
512
513 }
514
515 // Add files to ZIP – The Backup
516 public function add_files($files = [], $file_list = false, $final = false, $dbLog = false) {
517
518 try {
519
520 // TODO: Remove false && or replace with option in settings to switch
521 if (false && (class_exists('\ZipArchive') || class_exists('ZipArchive'))) {
522
523 // Initialize Zip
524 if (!isset($this->_zip)) {
525 $this->_zip = new \ZipArchive();
526 }
527
528 if ($this->_zip) {
529
530 // Show what's in use
531 if ($this->it === 1) {
532 $this->output->log('Using ZipArchive module to create the Archive.', 'INFO');
533 if ($dbLog == true) {
534 $this->output->log('Adding database SQL file(s) to the backup file.', 'STEP');
535 }
536 }
537
538 // Open / create ZIP file
539 $back = BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupname;
540 if (BMI_CLI_REQUEST) {
541 if (!isset($this->zip_initialized)) {
542 $this->_zip->open($back, \ZipArchive::CREATE);
543 }
544 } else {
545 if (file_exists($back)) $this->_zip->open($back);
546 else $this->_zip->open($back, \ZipArchive::CREATE);
547 }
548
549 // Final operation
550 if ($final || $dbLog) {
551
552 // Add files
553 for ($i = 0; $i < sizeof($files); ++$i) {
554
555 if (file_exists($files[$i]) && is_readable($files[$i]) && !is_link($files[$i])) {
556
557 // Add the file
558 $this->_zip->addFile($files[$i], $this->cutDir($files[$i]));
559
560 } else {
561
562 $this->output->log('This file is not readable, it will not be included in the backup: ' . $files[$i], 'WARN');
563
564 }
565
566 }
567
568 if ($dbLog === false) {
569 $this->final_made = true;
570 }
571
572 } else {
573
574 // Add files
575 for ($i = 0; $i < sizeof($files); ++$i) {
576
577 if (file_exists($files[$i]) && is_readable($files[$i]) && !is_link($files[$i])) {
578
579 // Calculate Path in ZIP
580 $path = 'wordpress' . DIRECTORY_SEPARATOR . substr($files[$i], strlen(ABSPATH));
581
582 // Add the file
583 $this->_zip->addFile($files[$i], $path);
584
585 } else {
586
587 $this->output->log('This file is not readable, it will not be included in the backup: ' . $files[$i], 'WARN');
588
589 }
590
591 }
592
593 }
594
595 // Close archive and prepare next batch
596 touch(BMI_BACKUPS . '/.running');
597 if (!BMI_CLI_REQUEST || $final) {
598 $result = $this->_zip->close();
599
600 if ($result === true) {
601
602 // Remove batch
603 if ($file_list && file_exists($file_list)) {
604 @unlink($file_list);
605 }
606
607 } else {
608
609 $this->send_error('Error, there is most likely not enough space for the backup.');
610 return false;
611
612 }
613 } else {
614
615 // Remove batch
616 if ($file_list && file_exists($file_list)) {
617 @unlink($file_list);
618 }
619
620 }
621
622 } else {
623 $this->send_error('ZipArchive error, please contact support - your site may be special case.');
624 }
625
626 } else {
627
628 // Check if PclZip exists
629 if (!class_exists('PclZip')) {
630 if (!defined('PCLZIP_TEMPORARY_DIR')) {
631 $bmi_tmp_dir = BMI_TMP;
632 if (!file_exists($bmi_tmp_dir)) {
633 @mkdir($bmi_tmp_dir, 0775, true);
634 }
635
636 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
637 }
638 }
639
640 // Require the LIB and check if it's compatible
641 $alternative = dirname($this->dir) . '/backup-backup-pro/includes/pcl.php';
642 if ($this->rev === 1 || !file_exists($alternative)) {
643 require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
644 } else {
645 require_once $alternative;
646 if ($this->it === 1) {
647 $this->output->log('Using dedicated PclZIP for Pro', 'INFO');
648 }
649 }
650
651 // Get/Create the Archive
652 if (!isset($this->_lib)) {
653 $this->_lib = new \PclZip(BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupname);
654 }
655
656 if (!$this->_lib) {
657 $this->send_error('PHP-ZIP: Permission Denied or zlib cannot be found');
658 return;
659 }
660
661 if (sizeof($files) <= 0) {
662 return false;
663 }
664
665 $back = 0;
666 $files = array_filter($files, function ($path) {
667 if (is_readable($path) && file_exists($path) && !is_link($path)) return true;
668 else {
669 $this->output->log("Excluding file that cannot be read: " . $path, 'warn');
670 return false;
671 }
672 });
673
674 // Add files
675 if ($final) {
676
677 // Final configuration
678 if (sizeof($files) > 0) {
679 $back = $this->_lib->add($files, PCLZIP_OPT_REMOVE_PATH, BMI_TMP . DIRECTORY_SEPARATOR, PCLZIP_OPT_TEMP_FILE_THRESHOLD, $this->safelimit);
680 }
681 $this->final_made = true;
682
683 } else {
684
685 // Additional path
686 $add_path = 'wordpress' . DIRECTORY_SEPARATOR;
687
688 // Casual configuration
689 if (sizeof($files) > 0) {
690 $back = $this->_lib->add($files, PCLZIP_OPT_REMOVE_PATH, ABSPATH, PCLZIP_OPT_ADD_PATH, $add_path, PCLZIP_OPT_TEMP_FILE_THRESHOLD, $this->safelimit);
691 }
692
693 }
694
695 // Check if there was any error
696 touch(BMI_BACKUPS . '/.running');
697 if ($back == 0) {
698
699 $this->send_error($this->_lib->errorInfo(true));
700 return false;
701
702 } else {
703
704 if ($file_list && file_exists($file_list)) {
705 @unlink($file_list);
706 }
707
708 }
709
710 }
711
712 } catch (\Exception $e) {
713
714 $this->send_error($e->getMessage());
715 return false;
716
717 } catch (\Throwable $e) {
718
719 $this->send_error($e->getMessage());
720 return false;
721
722 }
723
724 }
725
726 // ZIP one of the grouped files
727 public function zip_batch() {
728
729 if ($this->it === 1) {
730
731 $files = [];
732 if (file_exists($this->dbfile)) {
733 $files[] = $this->dbfile;
734 } elseif (file_exists($this->db_dir_v2) && is_dir($this->db_dir_v2)) {
735 $this->db_v2_engine = true;
736 $db_files = scandir($this->db_dir_v2);
737 foreach ($db_files as $i => $name) {
738 if (!($name == '.' || $name == '..')) {
739 $files[] = $this->db_dir_v2 . DIRECTORY_SEPARATOR . $name;
740 }
741 }
742 }
743
744 if (sizeof($files) > 0) {
745 $this->add_files($files, false, false, true);
746 $this->output->log('Database added to the backup file.', 'SUCCESS');
747 $this->output->log('Performing site files backup...', 'STEP');
748 return true;
749 }
750
751 $this->output->log('Performing site files backup...', 'STEP');
752
753 }
754
755 $list_file = $this->load_batch();
756 if ($list_file === false) return true;
757 $files = explode("\r\n", file_get_contents($list_file));
758
759 $total_size = 0;
760 $parsed_files = [];
761
762 for ($i = 0; $i < sizeof($files); ++$i) {
763 if (strlen(trim($files[$i])) <= 1) {
764 $this->total_files--;
765 continue;
766 }
767
768 $files[$i] = explode(',', $files[$i]);
769 $last = sizeof($files[$i]) - 1;
770 $size = intval($files[$i][$last]);
771 unset($files[$i][$last]);
772 $files[$i] = implode(',', $files[$i]);
773
774 $file = null;
775 if ($files[$i][0] . $files[$i][1] . $files[$i][2] === '@1@') {
776 $file = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . substr($files[$i], 3);
777 } else if ($files[$i][0] . $files[$i][1] . $files[$i][2] === '@2@') {
778 $file = ABSPATH . DIRECTORY_SEPARATOR . substr($files[$i], 3);
779 } else {
780 $file = $files[$i];
781 }
782
783 if (!file_exists($file)) {
784 $this->output->log('Removing this file from backup (it does not exist anymore): ' . $file, 'WARN');
785 $this->total_files--;
786 continue;
787 }
788
789 if (filesize($file) === 0) {
790 $this->output->log('Removing this file from backup (file size is equal to 0 bytes): ' . $file, 'WARN');
791 $this->total_files--;
792 continue;
793 }
794
795 $parsed_files[] = $file;
796 $total_size += $size;
797 unset($file);
798 }
799
800 unset($files);
801 if (sizeof($parsed_files) === 1) {
802 $this->output->log('Adding: ' . sizeof($parsed_files) . ' file...' . ' [Size: ' . $this->humanSize($total_size) . ']', 'INFO');
803 $this->output->log('Alone-file mode for: ' . $parsed_files[0] . ' file...', 'INFO');
804 } else $this->output->log('Adding: ' . sizeof($parsed_files) . ' files...' . ' [Size: ' . $this->humanSize($total_size) . ']', 'INFO');
805
806 if ((60 * (1024 * 1024)) < $total_size) $this->output->log('Current batch is quite large, it may take some time...', 'WARN');
807
808 $this->add_files($parsed_files, $list_file);
809 $this->filessofar += sizeof($parsed_files);
810
811 $this->output->progress($this->filessofar . '/' . $this->total_files);
812 $this->output->log('Milestone: ' . $this->filessofar . '/' . $this->total_files . ' [' . $this->batches_left . ' batches left]', 'SUCCESS');
813
814 if ($this->final_batch === true) {
815 $this->output->log('Adding final files to this batch...', 'STEP');
816 $this->output->log('Adding manifest as addition...', 'INFO');
817
818 $additionalFiles = $this->get_final_batch();
819 $this->add_files($additionalFiles, false, true);
820 $this->log_final_batch();
821 return true;
822 }
823
824 }
825
826 // Shutdown callback
827 public function shutdown() {
828
829 // Check if there was any error
830 $err = error_get_last();
831 if ($err != null) {
832 Logger::error('Shuted down');
833 Logger::error(print_r($err, true));
834 $this->output->log('Background process had some issues, more details printed to global logs.', 'WARN');
835 }
836
837 // Remove lock
838 if (file_exists($this->lock_cli)) {
839 @unlink($this->lock_cli);
840 }
841
842 // Send next beat to handle next batch
843 if (BMI_CLI_REQUEST) return;
844 if (file_exists($this->identyfile)) {
845
846 if ($this->dbit === -1 && $this->dbitJustFinished == false) {
847 $this->it += 1;
848 }
849
850 // Set header for browser
851 if ($this->browserSide && $this->headersSet === false) {
852
853 // Content finished
854 header('Content-Finished: false');
855 header('Content-It: ' . $this->it);
856 header('Content-Dbit: ' . $this->dbit);
857 header('Content-Dblast: ' . $this->dblast);
858 header('Content-Filessofar: ' . $this->filessofar);
859 http_response_code(200);
860 $this->headersSet = true;
861
862 } else {
863
864 usleep(100);
865 $this->send_beat();
866
867 }
868
869 }
870
871 }
872
873 // Handle received batch
874 public function handle_batch() {
875
876 // Check if aborted
877 if (file_exists(BMI_BACKUPS . '/.abort')) {
878 if (!isset($this->output)) $this->load_logger();
879 $this->send_error('Backup aborted manually by user.', true);
880 return;
881 }
882
883 // Handle cURL
884 if ($this->curl == true) {
885
886 // Check if it was triggered by verified user
887 if (!file_exists($this->identyfile)) {
888 return;
889 }
890
891 // Register shutdown
892 register_shutdown_function([$this, 'shutdown']);
893
894 // Load logger
895 $this->load_logger();
896
897 set_error_handler(function ($errno, $errstr, $errfile, $errline) {
898 Logger::error('Bypasser error:');
899 Logger::error($errno . ' - ' . $errstr);
900 Logger::error($errfile . ' - ' . $errline);
901 });
902
903 // Notice parent script
904 touch($this->identyfile . '-running');
905 touch(BMI_BACKUPS . '/.running');
906
907 // CLI case
908 if (BMI_CLI_REQUEST) {
909
910 $this->output->log('Starting database backup exporter', 'STEP');
911 $this->output->log('Database exporter started via CLI', 'VERBOSE');
912 while ($this->dbit !== -1) {
913 $this->databaseBackupMaker();
914 }
915
916 // Log
917 $this->output->log("PHP CLI initialized - process ran successfully", 'SUCCESS');
918 $this->make_file_groups();
919
920 // Make ZIP
921 $this->output->log('Making archive...', 'STEP');
922 while (!$this->final_made) {
923 touch($this->identyfile . '-running');
924 touch(BMI_BACKUPS . '/.running');
925 $this->it++;
926 $this->zip_batch();
927 }
928
929 } else {
930
931 // Background
932 if ($this->dbit !== -1) {
933
934 if ($this->dbit === 0) {
935 $this->output->log('Background process initialized', 'SUCCESS');
936 $this->output->log('Starting database backup exporter', 'STEP');
937 $this->output->log('Database exporter started via WEB REQUESTS', 'VERBOSE');
938 }
939
940 $this->databaseBackupMaker();
941
942 } else {
943
944 if ($this->it === 0) {
945
946 $this->make_file_groups();
947 $this->output->log('Making archive...', 'STEP');
948
949 } else $this->zip_batch();
950
951 }
952
953 }
954
955 }
956
957 }
958
959 public function fixSlashes($str, $slash = false) {
960 // Old version
961 // $str = str_replace('\\\\', DIRECTORY_SEPARATOR, $str);
962 // $str = str_replace('\\', DIRECTORY_SEPARATOR, $str);
963 // $str = str_replace('\/', DIRECTORY_SEPARATOR, $str);
964 // $str = str_replace('/', DIRECTORY_SEPARATOR, $str);
965
966 // if ($str[strlen($str) - 1] == DIRECTORY_SEPARATOR) {
967 // $str = substr($str, 0, -1);
968 // }
969
970 // Since 1.3.2
971 $protocol = '';
972 if ($slash == false) $slash = DIRECTORY_SEPARATOR;
973 if (substr($str, 0, 7) == 'http://') $protocol = 'http://';
974 else if (substr($str, 0, 8) == 'https://') $protocol = 'https://';
975
976 $str = substr($str, strlen($protocol));
977 $str = preg_replace('/[\\\\\/]+/', $slash, $str);
978 $str = rtrim($str, '/\\' );
979
980 return $protocol . $str;
981 }
982
983 // Database batch maker and dumper
984 // We need WP instance for that to get access to wpdb
985 public function databaseBackupMaker() {
986
987 if ($this->dbit === -1) return;
988
989 $this->loadWordPressAndBackupPlugin();
990
991 // DB File Name for that type of backup
992 $dbbackupname = 'bmi_database_backup.sql';
993 $database_file = $this->fixSlashes(BMI_TMP . DIRECTORY_SEPARATOR . $dbbackupname);
994
995 if (Dashboard\bmi_get_config('BACKUP:DATABASE') == 'true') {
996
997 if (Dashboard\bmi_get_config('OTHER:BACKUP:DB:SINGLE:FILE') == 'true') {
998
999 // Require Database Manager
1000 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'manager.php';
1001
1002 // Log what's going on
1003 $this->output->log('Making single-file database backup (using deprecated engine, due to used settings)', 'STEP');
1004
1005 // Get database dump
1006 $databaser = new Database(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
1007 $databaser->exportDatabase($dbbackupname);
1008 $this->output->log("Database size: " . $this->humanSize(filesize($database_file)), 'INFO');
1009 $this->output->log('Database (single-file) backup finished.', 'SUCCESS');
1010
1011 $this->dbitJustFinished = true;
1012 $this->dbit = -1;
1013 return true;
1014
1015 } else {
1016
1017 // Log what's going on
1018 if ($this->dbit === 0) {
1019 $this->output->log("Making database backup (using v3 engine, requires at least v1.2.2 to restore)", 'STEP');
1020 $this->output->log("Iterating database...", 'INFO');
1021 }
1022
1023 // Require Database Manager
1024 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'better-backup-v3.php';
1025
1026 $database_file_dir = $this->fixSlashes((dirname($database_file))) . DIRECTORY_SEPARATOR;
1027 $better_database_files_dir = $database_file_dir . 'db_tables';
1028
1029 if (!is_dir($better_database_files_dir)) @mkdir($better_database_files_dir, 0755, true);
1030 $db_exporter = new BetterDatabaseExport($better_database_files_dir, $this->output, $this->dbit, intval($this->backupstart));
1031
1032 $dbBatchingEnabled = false;
1033 if (Dashboard\bmi_get_config('OTHER:BACKUP:DB:BATCHING') == 'true') {
1034 $dbBatchingEnabled = true;
1035 } else {
1036 if ($this->dbit === 0) {
1037 $this->output->log("Database batching is disabled in options, consider to use this option if your database backup fails.", 'WARN');
1038 }
1039 }
1040
1041 if (BMI_CLI_REQUEST === true || $dbBatchingEnabled === false) {
1042
1043 $this->output->log('Exporting database via bypasser.php @ CLI', 'VERBOSE');
1044 $results = $db_exporter->export();
1045
1046 $this->output->log("Database backup finished", 'SUCCESS');
1047 $this->dbitJustFinished = true;
1048 $this->dbit = -1;
1049 $this->dblast = 0;
1050
1051 } else {
1052
1053 $this->output->log('Exporting database via bypasser.php @ WEB REQUEST', 'VERBOSE');
1054 $results = $db_exporter->export($this->dbit, $this->dblast);
1055
1056 $this->dbit = intval($results['batchingStep']);
1057 $this->dblast = intval($results['finishedQuery']);
1058 $dbFinished = $results['dumpCompleted'];
1059
1060 if ($dbFinished == true) {
1061 $this->output->log("Database backup finished", 'SUCCESS');
1062 $this->dbitJustFinished = true;
1063 $this->dbit = -1;
1064 }
1065
1066 }
1067
1068 return true;
1069
1070 }
1071
1072 } else {
1073
1074 $this->output->log('Database will not be dumped due to user settings.', 'INFO');
1075 $this->dbitJustFinished = true;
1076 $this->dbit = -1;
1077 return true;
1078
1079 }
1080
1081 }
1082
1083 public function loadWordPressAndBackupPlugin() {
1084
1085 // Define how WP should load
1086 define('WP_USE_THEMES', false);
1087 define('SHORTINIT', true);
1088
1089 // Set path to our plugin's main file
1090 $bmiPluginPathToLoad = $this->fixSlashes(dirname(__DIR__) . '/backup-backup.php');
1091 $bmiPluginPathToLoadPro = $this->fixSlashes(dirname(dirname(__DIR__)) . '/backup-backup-pro/backup-backup-pro.php');
1092
1093 // Use WP Globals and load WordPress
1094 global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header;
1095 require_once $this->bmi_find_wordpress_base_path() . DIRECTORY_SEPARATOR . 'wp-load.php';
1096 global $wp_version;
1097
1098 // Load directory WordPress constants
1099 require_once ABSPATH . WPINC . '/formatting.php';
1100 require_once ABSPATH . WPINC . '/meta.php';
1101 wp_plugin_directory_constants();
1102
1103 // Allow to register activation hook and realpath
1104 $GLOBALS['wp_plugin_paths'] = array();
1105 $GLOBALS['shortcode_tags'] = array();
1106
1107 // Load all dependencies of WordPress for Backup plugin
1108 $dependencies = [
1109 ABSPATH . WPINC . '/l10n.php',
1110 ABSPATH . WPINC . '/plugin.php',
1111 ABSPATH . WPINC . '/link-template.php',
1112 ABSPATH . WPINC . '/class-wp-textdomain-registry.php',
1113 ABSPATH . WPINC . '/class-wp-locale.php',
1114 ABSPATH . WPINC . '/class-wp-locale-switcher.php',
1115 ABSPATH . WPINC . '/session.php',
1116 ABSPATH . WPINC . '/pluggable.php',
1117 ABSPATH . WPINC . '/class-wp-ajax-response.php',
1118 ABSPATH . WPINC . '/capabilities.php',
1119 ABSPATH . WPINC . '/class-wp-roles.php',
1120 ABSPATH . WPINC . '/class-wp-role.php',
1121 ABSPATH . WPINC . '/class-wp-user.php',
1122 ABSPATH . WPINC . '/class-wp-query.php',
1123 ABSPATH . WPINC . '/query.php',
1124 ABSPATH . WPINC . '/general-template.php',
1125 ABSPATH . WPINC . '/http.php',
1126 ABSPATH . WPINC . '/class-http.php',
1127 ABSPATH . WPINC . '/class-wp-http.php',
1128 ABSPATH . WPINC . '/class-wp-http-streams.php',
1129 ABSPATH . WPINC . '/class-wp-http-curl.php',
1130 ABSPATH . WPINC . '/class-wp-http-proxy.php',
1131 ABSPATH . WPINC . '/class-wp-http-cookie.php',
1132 ABSPATH . WPINC . '/class-wp-http-encoding.php',
1133 ABSPATH . WPINC . '/class-wp-http-response.php',
1134 ABSPATH . WPINC . '/class-wp-http-requests-response.php',
1135 ABSPATH . WPINC . '/class-wp-http-requests-hooks.php',
1136 ABSPATH . WPINC . '/widgets.php',
1137 ABSPATH . WPINC . '/class-wp-widget.php',
1138 ABSPATH . WPINC . '/class-wp-widget-factory.php',
1139 ABSPATH . WPINC . '/class-wp-user-request.php',
1140 ABSPATH . WPINC . '/user.php',
1141 ABSPATH . WPINC . '/class-wp-user-query.php',
1142 ABSPATH . WPINC . '/class-wp-session-tokens.php',
1143 ABSPATH . WPINC . '/class-wp-user-meta-session-tokens.php',
1144 ABSPATH . WPINC . '/rest-api.php',
1145 ABSPATH . WPINC . '/kses.php',
1146 ABSPATH . WPINC . '/theme.php',
1147 ABSPATH . WPINC . '/rewrite.php',
1148 ABSPATH . WPINC . '/class-wp-block-editor-context.php',
1149 ABSPATH . WPINC . '/class-wp-block-type.php',
1150 ABSPATH . WPINC . '/class-wp-block-pattern-categories-registry.php',
1151 ABSPATH . WPINC . '/class-wp-block-patterns-registry.php',
1152 ABSPATH . WPINC . '/class-wp-block-styles-registry.php',
1153 ABSPATH . WPINC . '/class-wp-block-type-registry.php',
1154 ABSPATH . WPINC . '/class-wp-block.php',
1155 ABSPATH . WPINC . '/class-wp-block-list.php',
1156 ABSPATH . WPINC . '/class-wp-block-parser-block.php',
1157 ABSPATH . WPINC . '/class-wp-block-parser-frame.php',
1158 ABSPATH . WPINC . '/class-wp-block-parser.php',
1159 ABSPATH . WPINC . '/blocks.php',
1160 ABSPATH . WPINC . '/blocks/index.php',
1161 ];
1162
1163 for ($i = 0; $i < sizeof($dependencies); ++$i) {
1164 $dependency = $dependencies[$i];
1165 if (strpos($dependency, 'class-http.php') && version_compare($wp_version, '5.9.0', '>=')) {
1166 continue;
1167 }
1168 if (strpos($dependency, 'session.php') && version_compare($wp_version, '4.7.0', '>=')) {
1169 continue;
1170 }
1171 if (file_exists($dependency)) require_once $dependency;
1172 }
1173
1174 // Load Cookie Constants
1175 wp_cookie_constants();
1176
1177 // Load SSL Constants for DB export
1178 wp_ssl_constants();
1179
1180 // Register Translation
1181 if (class_exists('WP_Textdomain_Registry')) {
1182 $GLOBALS['wp_textdomain_registry'] = new \WP_Textdomain_Registry();
1183 }
1184
1185 if (is_readable($bmiPluginPathToLoadPro)) {
1186 wp_register_plugin_realpath($bmiPluginPathToLoadPro);
1187 include_once $bmiPluginPathToLoadPro;
1188
1189 require_once BMI_PRO_ROOT_DIR . '/classes/core' . ((BMI_PRO_DEBUG) ? '.to-enc' : '') . '.php';
1190 $bmi_pro_instance = new Pro_Core();
1191 $bmi_pro_instance->initialize();
1192 }
1193
1194 // Register our backup plugin and load its contents
1195 wp_register_plugin_realpath($bmiPluginPathToLoad);
1196 include_once $bmiPluginPathToLoad;
1197
1198 // Enable our plugin WITHOUT calling plugins_loaded hook – it's important
1199 require_once BMI_ROOT_DIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'constants.php';
1200
1201 // Initialize backup-migration
1202 if (!class_exists('Backup_Migration_Plugin')) {
1203
1204 // Require initializator
1205 require_once BMI_ROOT_DIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'initializer.php';
1206
1207 // Initialize entire plugin
1208 $bmi_instance = new BMI\Backup_Migration_Plugin();
1209 $bmi_instance->initialize();
1210
1211 }
1212
1213 }
1214
1215 public function actionsAfterProcess($success = false) {
1216
1217 $this->loadWordPressAndBackupPlugin();
1218 BMP::handle_after_cron();
1219
1220 return null;
1221
1222 }
1223
1224 public function bmi_find_wordpress_base_path() {
1225
1226 $dir = dirname(__FILE__);
1227 $previous = null;
1228
1229 do {
1230
1231 if (file_exists($dir . '/wp-load.php') && file_exists($dir . '/wp-config.php')) return $dir;
1232 if ($previous == $dir) break;
1233 $previous = $dir;
1234
1235 } while ($dir = dirname($dir));
1236
1237 return null;
1238
1239 }
1240
1241 }
1242