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