PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.4.5
Backup Migration v1.4.5
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 / backup-process.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-process.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
backup-process.php
1289 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 @ini_set('session.gc_maxlifetime', '1200');
221 }
222
223 if (!isset($this->remote_settings['browser'])) $this->remote_settings['browser'] = false;
224
225 // Don't block server handler
226 // if ($this->isFunctionEnabled('session_write_close')) @session_write_close();
227
228 }
229
230 // Make sure it's impossible to unlink some files
231 public function unlinksafe($path) {
232
233 if (substr($path, 0, 7) == 'file://') {
234 $path = substr($path, 7);
235 }
236
237 $path = realpath($path);
238 if ($path === false) return;
239 if (strpos($path, 'wp-config.php') !== false) return;
240 if (substr($path, -8) == '/backups') return;
241
242 @unlink('file://' . $path);
243
244 }
245
246 // Human size from bytes
247 public static function humanSize($bytes) {
248 if (is_int($bytes)) {
249 $label = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
250 for ($i = 0; $bytes >= 1024 && $i < (count($label) - 1); $bytes /= 1024, $i++);
251
252 return (round($bytes, 2) . " " . $label[$i]);
253 } else return $bytes;
254 }
255
256 // Create new process
257 public function send_beat($manual = false, &$logger = null) {
258
259 if ($this->beatSent) return;
260 $this->beatSent = true;
261
262 if (is_null($logger)) $this->load_logger();
263 else if ($logger instanceof Output) $this->output = $logger;
264
265 try {
266
267 $header = array(
268 'Content-Accept:*/*',
269 'Access-Control-Allow-Origin:*'
270 );
271
272 $this->saveRemoteSettings();
273 $c = curl_init();
274 curl_setopt($c, CURLOPT_POST, 1);
275 curl_setopt($c, CURLOPT_COOKIESESSION, false);
276 curl_setopt($c, CURLOPT_TIMEOUT, 20);
277 curl_setopt($c, CURLOPT_VERBOSE, false);
278 curl_setopt($c, CURLOPT_HEADER, false);
279 curl_setopt($c, CURLOPT_URL, $this->url);
280 curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
281 curl_setopt($c, CURLOPT_MAXREDIRS, 10);
282 curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
283 curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 0);
284 curl_setopt($c, CURLOPT_SSL_VERIFYPEER, 0);
285 curl_setopt($c, CURLOPT_HTTPHEADER, $header);
286 curl_setopt($c, CURLOPT_CUSTOMREQUEST, 'POST');
287 curl_setopt($c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
288 curl_setopt($c, CURLOPT_USERAGENT, $this->useragent);
289
290 $r = curl_exec($c);
291
292 if ($manual === true && $logger !== null) {
293 if ($r === false) {
294 if (intval(curl_errno($c)) !== 28) {
295 Logger::error(print_r(curl_getinfo($c), true));
296 Logger::error(curl_errno($c) . ': ' . curl_error($c));
297 $this->output->log('There was something wrong with the request:', 'WARN');
298 $this->output->log(curl_errno($c) . ': ' . curl_error($c), 'WARN');
299 }
300 } else {
301 $this->output->log('Request sent successfully, without error returned.', 'SUCCESS');
302 }
303 }
304
305 curl_close($c);
306 if (isset($this->output)) $this->output->end();
307
308 } catch (\Exception $e) {
309
310 error_log($e->getMessage());
311 $this->output->log($e->getMessage(), 'ERROR');
312 if (isset($this->output)) $this->output->end();
313
314 } catch (\Throwable $e) {
315
316 error_log($e->getMessage());
317 $this->output->log($e->getMessage(), 'ERROR');
318 if (isset($this->output)) $this->output->end();
319
320 }
321
322 }
323
324 // Load backup logger
325 public function load_logger() {
326
327 if ($this->output instanceof Output) return;
328
329 require_once BMI_INCLUDES . '/logger.php';
330 require_once BMI_INCLUDES . '/progress/logger-only.php';
331
332 $this->output = new Output();
333 $this->output->start();
334
335 }
336
337 // Remove common files
338 public function remove_commons() {
339
340 if (is_null($this->fileList)) return;
341
342 // Remove list if exists
343 $identyfile = $this->identyfile;
344 $logfile = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_logs_this_backup.log';
345 $clidata = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_cli_data.json';
346 $settings_path = BMI_TMP . DIRECTORY_SEPARATOR . 'currentBackupConfig.php';
347 if (file_exists($this->fileList)) $this->unlinksafe($this->fileList);
348 if (file_exists($this->dbfile)) $this->unlinksafe($this->dbfile);
349 if (file_exists($this->manifest)) $this->unlinksafe($this->manifest);
350 if (file_exists($logfile)) $this->unlinksafe($logfile);
351 if (file_exists($clidata)) $this->unlinksafe($clidata);
352 if (file_exists($identyfile)) $this->unlinksafe($identyfile);
353 if (file_exists($identyfile . '-running')) $this->unlinksafe($identyfile . '-running');
354 if (file_exists($this->lock_cli)) $this->unlinksafe($this->lock_cli);
355 if (file_exists($settings_path)) $this->unlinksafe($settings_path);
356
357 // Remove backup
358 if (file_exists(BMI_BACKUPS . '/.running')) $this->unlinksafe(BMI_BACKUPS . '/.running');
359 if (file_exists(BMI_BACKUPS . '/.abort')) $this->unlinksafe(BMI_BACKUPS . '/.abort');
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 $this->_zip->addFile($files[$i], $this->cutDir($files[$i]));
747
748 }
749 } else {
750
751 // Final configuration
752 if (sizeof($files) > 0) {
753 $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);
754 }
755
756 }
757
758 if ($dbLog === false) {
759 $this->final_made = true;
760 }
761
762 } else {
763
764 if ($zipArchive) {
765
766 for ($i = 0; $i < sizeof($files); ++$i) {
767
768 $path = 'wordpress' . DIRECTORY_SEPARATOR . substr($files[$i], strlen($_abspath));
769
770 $path = str_replace('\\', '/', $path);
771
772 // Add the file
773 $this->_zip->addFile($files[$i], $path);
774
775 }
776
777 } else {
778
779 // Additional path
780 $add_path = 'wordpress' . DIRECTORY_SEPARATOR;
781
782 // Casual configuration
783 if (sizeof($files) > 0) {
784 $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);
785 }
786
787 }
788
789 }
790
791 // Check if there was any error
792 touch(BMI_BACKUPS . '/.running');
793 if ($zipArchive) {
794 if (!BMI_CLI_REQUEST || $final) {
795 $result = $this->_zip->close();
796
797 if ($result === true) {
798
799 // Remove batch
800 if ($file_list && file_exists($file_list)) {
801 $this->unlinksafe($file_list);
802 }
803
804 } else {
805
806 $this->send_error('Error, there is most likely not enough space for the backup.');
807 return false;
808
809 }
810 } else {
811
812 // Remove batch
813 if ($file_list && file_exists($file_list)) {
814 $this->unlinksafe($file_list);
815 }
816
817 }
818 } else {
819
820 if ($back == 0) {
821
822 $this->send_error($this->_lib->errorInfo(true));
823 return false;
824
825 } else {
826
827 if ($file_list && file_exists($file_list)) {
828 $this->unlinksafe($file_list);
829 }
830
831 }
832
833 }
834
835 } catch (\Exception $e) {
836
837 error_log($e->getMessage());
838 $this->send_error($e->getMessage());
839 return false;
840
841 } catch (\Throwable $e) {
842
843 error_log($e->getMessage());
844 $this->send_error($e->getMessage());
845 return false;
846
847 }
848
849 }
850
851 // ZIP one of the grouped files
852 public function zip_batch() {
853
854 $_dbfile = $this->dbfile;
855 $_db_dir_v2 = $this->db_dir_v2;
856 if (strpos($_dbfile, 'file://') !== false) $_dbfile = substr($_dbfile, 7);
857 if (strpos($_db_dir_v2, 'file://') !== false) $_db_dir_v2 = substr($_db_dir_v2, 7);
858
859 if ($this->it === 1) {
860
861 $files = [];
862 if (file_exists($this->dbfile)) {
863 $files[] = $_dbfile;
864 } elseif (file_exists($this->db_dir_v2) && is_dir($this->db_dir_v2)) {
865 $this->db_v2_engine = true;
866 $db_files = scandir($this->db_dir_v2);
867 foreach ($db_files as $i => $name) {
868 if (!($name == '.' || $name == '..')) {
869 $files[] = $_db_dir_v2 . DIRECTORY_SEPARATOR . $name;
870 }
871 }
872 }
873
874 if (sizeof($files) > 0) {
875 $this->add_files($files, false, false, true);
876 $this->output->log('Database added to the backup file.', 'SUCCESS');
877 $this->output->log('Performing site files backup...', 'STEP');
878 return true;
879 }
880
881 $this->output->log('Performing site files backup...', 'STEP');
882
883 }
884
885 $list_file = $this->load_batch();
886 if ($list_file === false) return true;
887 $files = explode("\r\n", file_get_contents($list_file));
888
889 $total_size = 0;
890 $parsed_files = [];
891
892 $absWo = ABSPATH;
893 $wpcDirWo = WP_CONTENT_DIR;
894
895 if (strpos($absWo, 'file://') !== false) $absWo = substr(ABSPATH, 7);
896 if (strpos($wpcDirWo, 'file://') !== false) $wpcDirWo = substr(WP_CONTENT_DIR, 7);
897
898 for ($i = 0; $i < sizeof($files); ++$i) {
899 if (strlen(trim($files[$i])) <= 1) {
900 $this->total_files--;
901 continue;
902 }
903
904 $files[$i] = explode(',', $files[$i]);
905 $last = sizeof($files[$i]) - 1;
906 $size = intval($files[$i][$last]);
907 unset($files[$i][$last]);
908 $files[$i] = implode(',', $files[$i]);
909
910 $file = null;
911 if ($files[$i][0] . $files[$i][1] . $files[$i][2] === '@1@') {
912 $file = $wpcDirWo . DIRECTORY_SEPARATOR . substr($files[$i], 3);
913 } else if ($files[$i][0] . $files[$i][1] . $files[$i][2] === '@2@') {
914 $file = $absWo . DIRECTORY_SEPARATOR . substr($files[$i], 3);
915 } else {
916 $file = $files[$i];
917 }
918
919 if (!file_exists($file)) {
920 $this->output->log('Removing this file from backup (it does not exist anymore): ' . $file, 'WARN');
921 $this->total_files--;
922 continue;
923 }
924
925 if (filesize($file) === 0) {
926 $this->output->log('Removing this file from backup (file size is equal to 0 bytes): ' . $file, 'WARN');
927 $this->total_files--;
928 continue;
929 }
930
931 $parsed_files[] = $file;
932 $total_size += $size;
933 unset($file);
934 }
935
936 unset($files);
937 if (sizeof($parsed_files) === 1) {
938 $this->output->log('Adding: ' . sizeof($parsed_files) . ' file...' . ' [Size: ' . $this->humanSize($total_size) . ']', 'INFO');
939 $this->output->log('Alone-file mode for: ' . $parsed_files[0] . ' file...', 'INFO');
940 } else $this->output->log('Adding: ' . sizeof($parsed_files) . ' files...' . ' [Size: ' . $this->humanSize($total_size) . ']', 'INFO');
941
942 if ((60 * (1024 * 1024)) < $total_size) $this->output->log('Current batch is quite large, it may take some time...', 'WARN');
943
944 $this->add_files($parsed_files, $list_file);
945 $this->filessofar += sizeof($parsed_files);
946
947 $currentBackupSize = filesize(BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupname);
948 $this->output->progress($this->filessofar . '/' . $this->total_files);
949 $this->output->log('Milestone: ' . $this->filessofar . '/' . $this->total_files . ' [' . $this->batches_left . ' batches left]', 'SUCCESS');
950 $this->output->log('Backup file size: ' . $this->humanSize($currentBackupSize), 'VERBOSE');
951 $this->output->log('Time since last batch: ' . (time() - $this->startOfBatch) . ' seconds.', 'VERBOSE');
952 $this->startOfBatch = time();
953
954 if ($this->backupSize > $currentBackupSize)
955 return $this->send_error('Backup size is smaller than it should be, it has been damaged, it may not be restorable, abort recommended.', true);
956
957 $this->backupSize = $currentBackupSize;
958
959 if ($this->final_batch === true) {
960 $this->output->log('Adding final files to this batch...', 'STEP');
961 $this->output->log('Adding manifest as addition...', 'INFO');
962
963 $additionalFiles = $this->get_final_batch();
964 $this->add_files($additionalFiles, false, true);
965 $this->log_final_batch();
966 return true;
967 }
968
969 }
970
971 // Shutdown callback
972 public function shutdown() {
973
974 if ($this->shutdownAlreadyInited) return;
975 $this->shutdownAlreadyInited = true;
976
977 // Check if there was any error
978 $err = error_get_last();
979 if ($err != null) {
980 Logger::error('Shuted down');
981 Logger::error(print_r($err, true));
982 $this->output->log('Background process had some issues, more details printed to global logs.', 'WARN');
983 }
984
985 // Remove lock
986 if (BMI_CLI_REQUEST && $this->lock_cli && file_exists($this->lock_cli)) {
987 $this->unlinksafe($this->lock_cli);
988 }
989
990 // Send next beat to handle next batch
991 if (BMI_CLI_REQUEST) {
992 $this->res = [ 'status' => 'success' ];
993 return true;
994 }
995
996 if (file_exists($this->identyfile)) {
997
998 // Set header for browser
999 if ($this->browserSide) {
1000
1001 $this->saveRemoteSettings();
1002
1003 // Content finished
1004 $this->sendResponse(false);
1005
1006 } else {
1007
1008 $this->send_beat();
1009
1010 }
1011
1012 } else {
1013 if (!$this->statusSent && !isset($GLOBALS['BMI::RESPONSE::SENT'])) {
1014 $this->statusSent = true;
1015 return BMP::res([ 'status' => 'success' ]);
1016 }
1017 }
1018
1019 }
1020
1021 public function sendResponse($finish = false, $error = false) {
1022 $res = [
1023 'status' => 'success',
1024 'backup_completed' => (($finish == true) ? 'true' : 'false'),
1025 'backup_process_error' => (($error == true) ? 'true' : 'false')
1026 ];
1027
1028 if ($error == true)
1029 $res['status'] = 'error';
1030
1031 $this->res = $res;
1032
1033 if (!$this->statusSent && !isset($GLOBALS['BMI::RESPONSE::SENT'])) {
1034 $this->statusSent = true;
1035 BMP::res($res);
1036 }
1037
1038 return $res;
1039 }
1040
1041 // Handle received batch
1042 public function handle_batch() {
1043
1044 // Check if aborted
1045 if (file_exists(BMI_BACKUPS . '/.abort')) {
1046 if (!isset($this->output)) $this->load_logger();
1047 $this->res = [ 'status' => 'error' ];
1048 $this->send_error('Backup aborted manually by user.', true);
1049 return;
1050 }
1051
1052 // Check if it was triggered by verified user
1053 if (!file_exists($this->identyfile)) {
1054 $this->res = [ 'status' => 'error' ];
1055 return;
1056 }
1057
1058 // Register shutdown
1059 register_shutdown_function([$this, 'shutdown']);
1060
1061 // Load logger
1062 $this->load_logger();
1063
1064 set_error_handler(function ($errno, $errstr, $errfile, $errline) {
1065 Logger::error('Bypasser error:');
1066 Logger::error($errno . ' - ' . $errstr);
1067 Logger::error($errfile . ' - ' . $errline);
1068
1069 $this->load_logger();
1070 $this->output->log('Bypasser error:', 'ERROR');
1071 $this->output->log($errno . ' - ' . $errstr, 'ERROR');
1072 $this->output->log($errfile . ' - ' . $errline, 'ERROR');
1073
1074 $this->res = [ 'status' => 'error' ];
1075 return;
1076 }, E_ERROR);
1077
1078 // Notice parent script
1079 touch($this->identyfile . '-running');
1080 touch(BMI_BACKUPS . '/.running');
1081
1082 // CLI case
1083 if (BMI_CLI_REQUEST) {
1084
1085 $this->output->log('Starting database backup exporter', 'STEP');
1086 $this->output->log('Database exporter started via CLI', 'VERBOSE');
1087 while ($this->dbit !== -1) {
1088 $this->databaseBackupMaker();
1089 }
1090
1091 // Log
1092 $this->output->log("PHP CLI initialized - process ran successfully", 'SUCCESS');
1093 $this->make_file_groups();
1094
1095 // Make ZIP
1096 $this->output->log('Making archive...', 'STEP');
1097 while (!$this->final_made) {
1098 touch($this->identyfile . '-running');
1099 touch(BMI_BACKUPS . '/.running');
1100 $this->it += 1;
1101 $this->zip_batch();
1102 }
1103
1104 } else {
1105
1106 // Background
1107 if ($this->dbit !== -1) {
1108
1109 if ($this->dbit === 0) {
1110 $this->output->log('Background process initialized', 'SUCCESS');
1111 $this->output->log('Starting database backup exporter', 'STEP');
1112 $this->output->log('Database exporter started via WEB REQUESTS', 'VERBOSE');
1113 }
1114
1115 $this->databaseBackupMaker();
1116 return $this->res;
1117
1118 } else {
1119
1120 if ($this->it === 0) {
1121
1122 $this->make_file_groups();
1123 $this->it += 1;
1124 $this->output->log('Making archive...', 'STEP');
1125
1126 } else {
1127
1128 $this->zip_batch();
1129 $this->it += 1;
1130
1131 }
1132
1133 }
1134
1135 }
1136
1137 }
1138
1139 public function fixSlashes($str, $slash = false) {
1140 // Old version
1141 // $str = str_replace('\\\\', DIRECTORY_SEPARATOR, $str);
1142 // $str = str_replace('\\', DIRECTORY_SEPARATOR, $str);
1143 // $str = str_replace('\/', DIRECTORY_SEPARATOR, $str);
1144 // $str = str_replace('/', DIRECTORY_SEPARATOR, $str);
1145
1146 // if ($str[strlen($str) - 1] == DIRECTORY_SEPARATOR) {
1147 // $str = substr($str, 0, -1);
1148 // }
1149
1150 // Since 1.3.2
1151 $protocol = '';
1152 if ($slash == false) $slash = DIRECTORY_SEPARATOR;
1153 if (substr($str, 0, 7) == 'http://') $protocol = 'http://';
1154 else if (substr($str, 0, 8) == 'https://') $protocol = 'https://';
1155
1156 $str = substr($str, strlen($protocol));
1157 $str = preg_replace('/[\\\\\/]+/', $slash, $str);
1158 $str = rtrim($str, '/\\' );
1159
1160 return $protocol . $str;
1161 }
1162
1163 public function isFunctionEnabled($func) {
1164 $disabled = explode(',', ini_get('disable_functions'));
1165 $isDisabled = in_array($func, $disabled);
1166 if (!$isDisabled && function_exists($func)) return true;
1167 else return false;
1168 }
1169
1170 // Database batch maker and dumper
1171 // We need WP instance for that to get access to wpdb
1172 public function databaseBackupMaker() {
1173
1174 if ($this->dbit === -1) {
1175 $this->res = [ 'status' => 'error' ];
1176 return;
1177 }
1178
1179 // DB File Name for that type of backup
1180 $dbbackupname = 'bmi_database_backup.sql';
1181 $database_file = $this->fixSlashes(BMI_TMP . DIRECTORY_SEPARATOR . $dbbackupname);
1182
1183 if (Dashboard\bmi_get_config('BACKUP:DATABASE') == 'true') {
1184
1185 if (Dashboard\bmi_get_config('OTHER:BACKUP:DB:SINGLE:FILE') == 'true') {
1186
1187 // Require Database Manager
1188 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'manager.php';
1189
1190 // Log what's going on
1191 $this->output->log('Making single-file database backup (using deprecated engine, due to used settings)', 'STEP');
1192
1193 // Get database dump
1194 $databaser = new Database(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
1195 $databaser->exportDatabase($dbbackupname);
1196 $this->output->log("Database size: " . $this->humanSize(filesize($database_file)), 'INFO');
1197 $this->output->log('Database (single-file) backup finished.', 'SUCCESS');
1198
1199 $this->dbitJustFinished = true;
1200 $this->dbit = -1;
1201
1202 $this->res = [ 'status' => 'success' ];
1203 return true;
1204
1205 } else {
1206
1207 // Log what's going on
1208 if ($this->dbit === 0) {
1209 $this->output->log("Making database backup (using v3 engine, requires at least v1.2.2 to restore)", 'STEP');
1210 $this->output->log("Iterating database...", 'INFO');
1211 }
1212
1213 // Require Database Manager
1214 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'better-backup-v3.php';
1215
1216 $database_file_dir = $this->fixSlashes((dirname($database_file))) . DIRECTORY_SEPARATOR;
1217 $better_database_files_dir = $database_file_dir . 'db_tables';
1218 $better_database_files_dir = str_replace('file:', 'file://', $better_database_files_dir);
1219
1220 if (!is_dir($better_database_files_dir)) @mkdir($better_database_files_dir, 0755, true);
1221 $db_exporter = new BetterDatabaseExport($better_database_files_dir, $this->output, $this->dbit, intval($this->backupstart));
1222
1223 $dbBatchingEnabled = false;
1224 if (Dashboard\bmi_get_config('OTHER:BACKUP:DB:BATCHING') == 'true') {
1225 $dbBatchingEnabled = true;
1226 } else {
1227 if ($this->dbit === 0) {
1228 $this->output->log("Database batching is disabled in options, consider to use this option if your database backup fails.", 'WARN');
1229 }
1230 }
1231
1232 if (BMI_CLI_REQUEST === true || $dbBatchingEnabled === false) {
1233
1234 $results = $db_exporter->export();
1235
1236 $this->output->log("Database backup finished", 'SUCCESS');
1237 $this->dbitJustFinished = true;
1238 $this->dbit = -1;
1239 $this->dblast = 0;
1240
1241 } else {
1242
1243 $results = $db_exporter->export($this->dbit, $this->dblast);
1244
1245 $this->dbit = intval($results['batchingStep']);
1246 $this->dblast = intval($results['finishedQuery']);
1247 $dbFinished = $results['dumpCompleted'];
1248
1249 if ($dbFinished == true) {
1250 $this->output->log("Database backup finished", 'SUCCESS');
1251 $this->dbitJustFinished = true;
1252 $this->dbit = -1;
1253 }
1254
1255 }
1256
1257 $this->res = [ 'status' => 'success' ];
1258 return true;
1259
1260 }
1261
1262 } else {
1263
1264 $this->output->log('Database will not be dumped due to user settings.', 'INFO');
1265 $this->dbitJustFinished = true;
1266 $this->dbit = -1;
1267
1268 $this->res = [ 'status' => 'success' ];
1269 return true;
1270
1271 }
1272
1273 }
1274
1275 public function actionsAfterProcess($success = false) {
1276
1277 Logger::log("Backup file created successfully via backup-process.php");
1278 BMP::handle_after_cron();
1279
1280 return null;
1281
1282 }
1283
1284 public function __destruct() {
1285 $this->shutdown();
1286 }
1287
1288 }
1289