PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.3.0
Backup Migration v1.3.0
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 / staging / local.php
backup-backup / includes / staging Last commit date
controller.php 2 years ago local.php 2 years ago tastewp.php 2 years ago
local.php
948 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\Staging;
5
6 // Use
7 use BMI\Plugin\Backup_Migration_Plugin as BMP;
8 use BMI\Plugin\Checker\BMI_Checker as Checker;
9 use BMI\Plugin\Database\BMI_Search_Replace_Engine as BMISearchReplace;
10
11 // Exit on direct access
12 if (!defined('ABSPATH')) exit;
13
14 // Require controller
15 require_once BMI_INCLUDES . '/staging/controller.php';
16
17 /**
18 * Subclass of main staging controller (local handler)
19 */
20 class BMI_StagingLocal extends BMI_Staging {
21
22 protected $data = [];
23 protected $step = 0;
24 private $filesList = null;
25 private $dirsList = null;
26 private $rootLength = 0;
27 private $excludedDirectories = [];
28
29 public function __construct($name, $initialize = false) {
30
31 parent::__construct(...func_get_args());
32 if ($initialize) $this->initialization();
33
34 }
35
36 public function continue() {
37
38 if (isset($this->siteConfig['step'])) {
39 $this->step = intval($this->siteConfig['step']);
40 } else {
41 $this->step = null;
42 }
43
44 if (!is_numeric($this->step)) {
45
46 // End with error
47 $this->log(__('Step code was not provided for the request, prevents continuation of the process...'), 'ERROR');
48 $this->log('Step code was not provided for the request, prevents continuation of the process...', 'VERBOSE');
49 $this->log('#201', 'END-CODE');
50 return ['status' => 'error'];
51
52 }
53
54 // Default error
55 $translatedMainError = __('Something unexpected happened, we need to abort the process.', 'backup-backup');
56 $englishMainError = 'Something unexpected happened, we need to abort the process (#207).';
57
58 // Step controller
59 if ($this->step == 1) $this->prepareFilesAndDatabase();
60 else if ($this->step == 2) $this->duplicateDatabase();
61 else if ($this->step == 3) $this->searchReplace();
62 else if ($this->step == 4) $this->databaseFinishedCopyFiles();
63 else if ($this->step == 5) $this->setupWpConfigAndLoginScript();
64 else if ($this->step == 6) $this->performFinish();
65 else if (isset($this->step) && is_numeric($this->step)) $this->sendSuccess();
66 else $this->returnError($translatedMainError, $englishMainError);
67
68 }
69
70 private function getAllLiveTables() {
71
72 global $wpdb;
73 $tables = [];
74
75 $allTables = $wpdb->get_results('SHOW TABLES');
76
77 foreach ($allTables as $table) {
78 foreach ($table as $name) $tables[] = $name;
79 }
80
81 return $tables;
82
83 }
84
85 private function checkIfPrefixCanBeUsed($tables, $prefix) {
86
87 $sizeOfPrefix = strlen($prefix);
88
89 for ($i = 0; $i < sizeof($tables); ++$i) {
90 $name = $tables[$i];
91
92 if (substr($name, 0, $sizeOfPrefix) == $prefix) return false;
93 if (strpos($name, $prefix)) return false;
94 }
95
96 return $prefix;
97
98 }
99
100 private function generateUniquePrefix() {
101 return 'b' . date('m') . 'mi' . date('d') . '_stg' . substr(time(), -5) . '_';
102 }
103
104 private function generateDatabasePrefix() {
105
106 $tables = $this->getAllLiveTables();
107 $prefix = $this->generateUniquePrefix();
108
109 $i = 0;
110
111 while ($this->checkIfPrefixCanBeUsed($tables, $prefix) === false && $i <= 3) {
112 sleep(1);
113 $prefix = $this->generateUniquePrefix();
114
115 $i++;
116 }
117
118 if ($i >= 3) return 'error';
119 return $prefix;
120
121 }
122
123 private function createDatabasePrefixAndInitDir() {
124
125 $path = trailingslashit(ABSPATH) . $this->name;
126
127 $this->log(__('Preparing constants and staging details', 'backup-backup'), 'STEP');
128
129 // Directory creation
130 $this->log(__('Creating root directory of new site', 'backup-backup'));
131 if (file_exists($path) && is_dir($path)) {
132 $translated = __('Seems like desired directory of staging site already exist, try with different staging site name.', 'backup-backup');
133 $english = 'Seems like desired directory of staging site already exist, try with different staging site name.';
134 $this->returnError($translated, $english);
135 }
136
137 @mkdir($path, 0755);
138 touch($path . DIRECTORY_SEPARATOR . '.bmi_staging');
139
140 $this->log(__('Path of new website:', 'backup-backup') . ' ' . $path, 'SUCCESS');
141
142 // Generation of database prefix
143 $this->log(__('Generating database prefix', 'backup-backup'));
144 $dbPrefix = $this->generateDatabasePrefix();
145 if ($dbPrefix == 'error') {
146 $translated = __('There was an error during database prefix generation, maybe all generated were already used, try again.', 'backup-backup');
147 $english = 'There was an error during database prefix generation, maybe all generated were already used, try again.';
148 $this->returnError($translated, $english);
149 }
150 $this->log(__('Prefix of new site will be:', 'backup-backup') . ' ' . $dbPrefix, 'SUCCESS');
151
152 $this->config[$this->name]['name'] = $this->name;
153 $this->config[$this->name]['prefix'] = $dbPrefix;
154
155 // Password for first log in
156 $this->log(__('Generating first log in password', 'backup-backup'));
157 $password = $this->getRandomPassword();
158 $this->log(__('Password created and saved.', 'backup-backup'), 'SUCCESS');
159
160 // Set initial configuration
161 $this->log(__('Initializing configuration of the website', 'backup-backup'), 'STEP');
162 $this->initialConfiguration($dbPrefix, $password);
163 $this->log(__('Configuration created', 'backup-backup'), 'SUCCESS');
164
165 }
166
167 private function initialConfiguration($dbPrefix, $password) {
168
169 global $table_prefix;
170
171 $ip = $this->getIpAddress();
172
173 // Basic for all
174 $this->siteConfig['name'] = $this->name; // Name of that staging site
175 $this->siteConfig['url'] = home_url($this->name); // URL to the website
176 $this->siteConfig['root_source'] = trailingslashit(ABSPATH); // ABSPATH of source website
177 $this->siteConfig['root_staging'] = untrailingslashit(ABSPATH) . DIRECTORY_SEPARATOR . $this->name; // ABSPATH of staging site
178 $this->siteConfig['db_prefix'] = $dbPrefix; // Database prefix of that site
179 $this->siteConfig['creation_date'] = time(); // Creation date and time of that staging site
180 $this->siteConfig['password'] = $password; // Password for password-less login
181 $this->siteConfig['creator_ip'] = $ip; // IP of user who created it
182 $this->siteConfig['login_ip'] = $ip; // IP for autologin script (limit password-less login to only that IP)
183 $this->siteConfig['login_user_id'] = get_current_user_id(); // User ID for password less authentication
184 $this->siteConfig['source_home_url'] = home_url(); // Homepage URL of source website
185 $this->siteConfig['source_site_url'] = site_url(); // Website (admin) URL of source website
186 $this->siteConfig['source_db_prefix'] = $table_prefix; // Database prefix of source website
187
188 // TasteWP
189 $this->siteConfig['communication_secret'] = 'local'; // Local if it's not TasteWP website // Secret code to get authless access to website
190 $this->siteConfig['expiration_time'] = 'never'; // Never if it's not TasteWP website // Expiration time of the website
191
192 }
193
194 private function getExcludedFilesAndDirectories() {
195
196 $excludedDirectories = $this->getAllStagingSiteDirectories();
197 $excludedDirectories[] = '.';
198 $excludedDirectories[] = '..';
199 $excludedDirectories[] = 'wp-config.php';
200 $excludedDirectories[] = '.DS_Store';
201 $excludedDirectories[] = '.quarantine';
202 $excludedDirectories[] = '.git';
203 $excludedDirectories[] = '.tmb';
204 $excludedDirectories[] = 'node_modules';
205 $excludedDirectories[] = 'debug.log';
206
207 return $excludedDirectories;
208
209 }
210
211 private function getAbspathFiles($path) {
212
213 $excludedDirectories = $this->getExcludedFilesAndDirectories();
214 $topFiles = array_diff(scandir($path), $excludedDirectories);
215
216 return $topFiles;
217
218 }
219
220 private function processFiles($prefixPath, &$files) {
221
222 $dirs = [];
223
224 foreach ($files as $index => $file) {
225 $path = $prefixPath . $file;
226 if (strpos($path, BMI_BACKUPS_ROOT) !== false) continue;
227 if (is_link($path)) continue;
228 if (is_dir($path)) {
229 if (!(is_readable($path) && is_writable($path))) continue;
230 if (file_exists(trailingslashit($path) . '.bmi_staging')) continue;
231 $this->siteConfig['total_size'] += 4096;
232 $this->siteConfig['total_directories'] += 1;
233 fwrite($this->dirsList, $file . "\n");
234 $dirs[] = $path;
235 } else if (is_file($path)) {
236 if (!is_readable($path)) continue;
237 $this->siteConfig['total_size'] += filesize($path);
238 $this->siteConfig['total_files'] += 1;
239 fwrite($this->filesList, $file . "\n");
240 }
241 }
242
243 return $dirs;
244
245 }
246
247 private function processDirectoryRecursively($dir) {
248
249 if (!(is_readable($dir) && is_writable($dir))) return;
250
251 $files = array_diff(scandir($dir), $this->excludedDirectories);
252
253 foreach ($files as $key => $value) {
254 $path = $dir . DIRECTORY_SEPARATOR . $value;
255 if (strpos($path, BMI_BACKUPS_ROOT) !== false) continue;
256 if (is_link($path)) continue;
257 if (is_dir($path)) {
258 $this->siteConfig['total_size'] += 4096;
259 $this->siteConfig['total_directories'] += 1;
260 fwrite($this->dirsList, substr($path, $this->rootLength) . "\n");
261 if (!file_exists(trailingslashit($path) . '.bmi_staging')) {
262 $this->processDirectoryRecursively($path);
263 }
264 } else if (is_file($path)) {
265 if (!is_readable($path)) continue;
266 $this->siteConfig['total_size'] += filesize($path);
267 $this->siteConfig['total_files'] += 1;
268 fwrite($this->filesList, substr($path, $this->rootLength) . "\n");
269 }
270 }
271
272 }
273
274 private function processFilesRecursively($leftDirs) {
275
276 $this->rootLength = strlen(trailingslashit(ABSPATH));
277 $this->excludedDirectories = $this->getExcludedFilesAndDirectories();
278
279 for ($i = 0; $i < sizeof($leftDirs); ++$i) {
280 $this->processDirectoryRecursively($leftDirs[$i]);
281 }
282
283 }
284
285 private function getTablesForDuplication() {
286
287 global $wpdb, $table_prefix;
288
289 $currentPrefixes = $this->getAllStagingSitePrefixes();
290 $relatedTables = [];
291
292 $sql = "SELECT (DATA_LENGTH + INDEX_LENGTH) as `size`, TABLE_NAME AS `name` FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s;";
293 $sql = $wpdb->prepare($sql, array(DB_NAME));
294
295 $tables = $wpdb->get_results($sql);
296 foreach ($tables as $tableObject) {
297
298 $name = $tableObject->name;
299 $size = $tableObject->size;
300
301 if (substr($name, 0, strlen($table_prefix)) != $table_prefix) continue;
302
303 $tableOfStagingSite = false;
304 for ($i = 0; $i < sizeof($currentPrefixes); ++$i) {
305 $subPrefix = $currentPrefixes[$i];
306 if (substr($name, 0, strlen($subPrefix)) == $subPrefix) {
307 $tableOfStagingSite = true;
308 break;
309 }
310 }
311 if ($tableOfStagingSite) continue;
312
313 $this->siteConfig['total_db_size'] += intval($size);
314 $relatedTables[$name] = $this->siteConfig['db_prefix'] . substr($name, strlen($table_prefix));
315
316 }
317
318 return $relatedTables;
319
320 }
321
322 private function updateUserRolesInOptions() {
323
324 global $wpdb;
325
326 // Update option name
327 $sql = "UPDATE %i SET `option_name` = %s WHERE `option_name` = %s;";
328 $newOptionTable = $this->siteConfig['db_prefix'] . 'options';
329 $newOptionName = $this->siteConfig['db_prefix'] . 'user_roles';
330 $oldOptionName = $this->siteConfig['source_db_prefix'] . 'user_roles';
331
332 $sql = $wpdb->prepare($sql, [$newOptionTable, $newOptionName, $oldOptionName]);
333 $wpdb->query($sql);
334
335 if ($wpdb->last_error !== '') {
336 $translated = __('There was an error during update of user roles:', 'backup-backup') . ' ' . $wpdb->last_error;
337 $english = 'There was an error during update of user roles:' . ' ' . $wpdb->last_error;
338 $this->returnError($translated, $english);
339 }
340
341 }
342
343 private function duplicateTable($source, $destination) {
344
345 global $wpdb;
346
347 // Create new table
348 $sql = "CREATE TABLE %i LIKE %i;";
349 $sql = $wpdb->prepare($sql, [$destination, $source]);
350 $wpdb->query($sql);
351
352 if ($wpdb->last_error !== '') {
353 $translated = __('There was an error during database table creation:', 'backup-backup') . ' ' . $wpdb->last_error;
354 $english = 'There was an error during database table creation:' . ' ' . $wpdb->last_error;
355 $this->returnError($translated, $english);
356 }
357
358 // Duplicate data
359 $sql = "INSERT INTO %i SELECT * from %i;";
360 $sql = $wpdb->prepare($sql, [$destination, $source]);
361 $wpdb->query($sql);
362
363 if ($wpdb->last_error !== '') {
364 $translated = __('There was an error during database table data duplication:', 'backup-backup') . ' ' . $wpdb->last_error;
365 $english = 'There was an error during database table data duplication:' . ' ' . $wpdb->last_error;
366 $this->returnError($translated, $english);
367 }
368
369 }
370
371 private function parseDomain($domain, $removeWWW = true) {
372
373 if (substr($domain, 0, 8) == 'https://') $domain = substr($domain, 8);
374 if (substr($domain, 0, 7) == 'http://') $domain = substr($domain, 7);
375 if ($removeWWW === true) {
376 if (substr($domain, 0, 4) == 'www.') $domain = substr($domain, 4);
377 }
378 $domain = untrailingslashit($domain);
379
380 return $domain;
381
382 }
383
384 private function performSearchReplace($table, $from, $to, $start = 0, $end = 0, $limitColumns = false) {
385
386 $replaceEngine = new BMISearchReplace([$table], $start, $end, true);
387
388 $stats = $replaceEngine->perform($from, $to, $limitColumns);
389
390 $this->siteConfig['totalTables'] += $stats['tables'];
391 $this->siteConfig['totalRows'] += $stats['rows'];
392 $this->siteConfig['totalChanges'] += $stats['change'];
393 $this->siteConfig['totalUpdates'] += $stats['updates'];
394
395 $this->siteConfig['currentTableTotalUpdates'] += $stats['updates'];
396 $this->siteConfig['currentSearchReplacePage'] = $stats['currentPage'];
397 $this->siteConfig['totalSearchReplacePages'] = $stats['totalPages'];
398
399 $replaceEngine = null;
400
401 }
402
403 public function requestDelete() {
404 return $this->abort(true);
405 }
406
407 // Step: 0 (initialization of the staging site, create entry)
408 private function initialization() {
409
410 $this->log('Step 0 - Initialization of the process', 'VERBOSE');
411
412 $this->log(__('Name of subsite:', 'backup-backup') . ' ' . $this->name);
413 $this->log(__('Expected URL of subsite:', 'backup-backup') . ' ' . home_url($this->name));
414
415 $this->printInitialLogs();
416
417 $this->createDatabasePrefixAndInitDir();
418 if ($this->wasError) return;
419
420 // Set progress to do something
421 $this->progress(4);
422
423 // Set next step to be 1
424 $this->setContinuation(1);
425
426 }
427
428 // Step: 1 (preparation of file list and database recipes)
429 private function prepareFilesAndDatabase() {
430
431 // Create list of all files and directories
432 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
433 $this->log(__('Scanning all files on your website, it may take a while...', 'backup-backup'), 'STEP');
434
435 $pathDirsListFile = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.staging_directories';
436 $pathFilesListFile = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.staging_files';
437
438 $this->siteConfig['total_size'] = 0;
439 $this->siteConfig['total_files'] = 0;
440 $this->siteConfig['total_directories'] = 0;
441
442 if (file_exists($pathDirsListFile)) @unlink($pathDirsListFile);
443 if (file_exists($pathFilesListFile)) @unlink($pathFilesListFile);
444
445 $this->filesList = fopen($pathFilesListFile, 'a');
446 $this->dirsList = fopen($pathDirsListFile, 'a');
447
448 $topLevelFiles = $this->getAbspathFiles(ABSPATH);
449 $leftDirs = $this->processFiles(trailingslashit(ABSPATH), $topLevelFiles);
450
451 $this->processFilesRecursively($leftDirs);
452
453 fclose($this->filesList);
454 fclose($this->dirsList);
455
456 $this->log(__('All files scanned and prepared for duplication...', 'backup-backup'), 'SUCCESS');
457 $this->progress(10);
458
459 $this->setContinuation(1, 2);
460 }
461
462 // Display details about scan and check database size
463 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
464 $this->log(__('Looking for database tables that should be duplicated and their sizes...', 'backup-backup'), 'STEP');
465
466 $this->siteConfig['total_db_size'] = 0;
467 $this->siteConfig['tables'] = $this->getTablesForDuplication();
468 $this->siteConfig['amountOfTables'] = sizeof($this->siteConfig['tables']);
469 $this->siteConfig['sumOfTotalSize'] = intval($this->siteConfig['total_db_size']) + intval($this->siteConfig['total_size']);
470
471 $this->log(__('Amount of files to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['total_files']);
472 $this->log(__('Amount of directories to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['total_directories']);
473 $this->log(__('Amount of tables to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['amountOfTables']);
474 $this->log(__('Size of database to duplicate:', 'backup-backup') . ' ' . BMP::humanSize(intval($this->siteConfig['total_db_size'])));
475 $this->log(__('Size of files to duplicate:', 'backup-backup') . ' ' . BMP::humanSize(intval($this->siteConfig['total_size'])));
476 $this->log(__('Total duplication size:', 'backup-backup') . ' ' . BMP::humanSize($this->siteConfig['sumOfTotalSize']));
477 $this->log(__('Tables prepared for duplication...', 'backup-backup'), 'SUCCESS');
478
479 $this->log(__('Checking if there is enough space for the staging site...', 'backup-backup'), 'STEP');
480 $this->log(__('Space checking may take a while', 'backup-backup'));
481
482 $this->progress(15);
483
484 $this->setContinuation(1, 3);
485 }
486
487 // Check if there is enough space for duplication
488 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) {
489 require_once BMI_INCLUDES . '/check/checker.php';
490 $checker = new Checker($this->logger);
491
492 $bytes = intval(intval($this->siteConfig['sumOfTotalSize']) * 1.1);
493 $this->log(str_replace('%s2', BMP::humanSize($bytes), str_replace('%s1', $bytes, __('Checking in total: %s1 bytes (%s2)', 'backup-backup'))));
494
495 if (!$checker->check_free_space($bytes, true)) {
496
497 $translated = __('There is not enough space on your server in order to create staging site.', 'backup-backup');
498 $english = 'There is not enough space on your server in order to create staging site.';
499 return $this->returnError($translated, $english);
500
501 } else {
502
503 $this->log(__("Confirmed, there is more than enough space, checked: ", 'backup-backup') . ($bytes) . __(" bytes", 'backup-backup'), 'SUCCESS');
504
505 }
506
507 $this->progress(20);
508
509 // Set next batch to start step 2
510 $this->log('Setting new step for next request to 2 @ batch 1', 'VERBOSE');
511 $this->setContinuation(2);
512
513 }
514
515 }
516
517 // Step: 2 (duplicate database)
518 private function duplicateDatabase() {
519
520 if (!isset($this->siteConfig['finishedTables'])) {
521 $this->siteConfig['finishedTables'] = [];
522 }
523
524 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
525 $this->log(__('Duplicating database tables...', 'backup-backup'), 'STEP');
526 }
527
528 $startTime = time();
529 foreach ($this->siteConfig['tables'] as $source_table => $destination_table) {
530
531 $this->duplicateTable($source_table, $destination_table);
532 $this->log(str_replace('%s1', $source_table, str_replace('%s2', $destination_table, __('Table %s1 cloned as %s2', 'backup-backup'))));
533
534 $this->siteConfig['finishedTables'][] = $destination_table;
535 unset($this->siteConfig['tables'][$source_table]);
536
537 $processPercentage = intval((sizeof($this->siteConfig['finishedTables']) / $this->siteConfig['amountOfTables']) * 20);
538 $this->progress(20 + $processPercentage);
539
540 if ((time() - $startTime) > 5) {
541 $this->setContinuation(3, (intval($this->siteConfig['batch']) + 1));
542 break;
543 }
544
545 }
546
547 if (sizeof($this->siteConfig['tables']) == 0) {
548 unset($this->siteConfig['tables']);
549 }
550
551 if (!isset($this->siteConfig['tables'])) {
552 $this->log(__('All tables were successfully duplicated', 'backup-backup'), 'SUCCESS');
553 $this->setContinuation(3);
554 }
555
556 }
557
558 // Step: 3 (search & replace of new tables)
559 private function searchReplace() {
560
561 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'search-replace.php';
562
563 // Domain
564 $sourceURL = $this->parseDomain($this->siteConfig['source_home_url']);
565 $destinationURL = $this->parseDomain($this->siteConfig['url']);
566
567 // Paths
568 $sourceABSPATH = untrailingslashit($this->siteConfig['root_source']);
569 $destinationABSPATH = untrailingslashit($this->siteConfig['root_staging']);
570
571 // Items to be replaced
572 $replaces = [
573 ['from' => $sourceABSPATH, 'to' => $destinationABSPATH],
574 ['from' => $sourceURL, 'to' => $destinationURL],
575 ['from' => $this->siteConfig['source_db_prefix'], 'to' => $this->siteConfig['db_prefix']]
576 ];
577
578 $variants = [
579 __("Batch for path adjustment (%s/%s) updated: %s fields.", 'backup-backup'),
580 __("Batch for domain adjustments (%s/%s) updated: %s fields.", 'backup-backup'),
581 __("Batch for prefix key adjustments (%s/%s) updated: %s fields.", 'backup-backup')
582 ];
583
584 $variantsEmpty = [
585 __("Path replacements are not required for table: %s", 'backup-backup'),
586 __("Domain replacements are not required for table: %s", 'backup-backup'),
587 __("Prefix key replacements are not required for table: %s", 'backup-backup')
588 ];
589
590 // Table for replacement
591 if (isset($this->siteConfig['finishedTables']) && sizeof($this->siteConfig['finishedTables']) > 0) {
592
593 // Get first table from duplicated tables
594 $table = $this->siteConfig['finishedTables'][0];
595
596 } else {
597
598 // All tables processed, search replace finished
599 unset($this->siteConfig['finishedTables']);
600 $this->setContinuation(4);
601
602 }
603
604 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
605 $this->log(__('Performing database search replace on duplicated tables...', 'backup-backup'), 'STEP');
606
607 $pagesize = '?';
608 if (defined('BMI_MAX_SEARCH_REPLACE_PAGE')) $pagesize = BMI_MAX_SEARCH_REPLACE_PAGE;
609 $this->log(__('Page size for that process: ', 'backup-backup') . $pagesize, 'INFO');
610
611 $this->siteConfig['totalTables'] = 0;
612 $this->siteConfig['totalRows'] = 0;
613 $this->siteConfig['totalChanges'] = 0;
614 $this->siteConfig['totalUpdates'] = 0;
615
616 $this->siteConfig['currentSearchReplaceItem'] = 0;
617 $this->siteConfig['currentTableTotalUpdates'] = 0;
618 $this->siteConfig['currentSearchReplacePage'] = 0;
619 $this->siteConfig['totalSearchReplacePages'] = 0;
620
621 $this->setContinuation(3, 2);
622 }
623
624 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
625
626 // Give details of current process
627 if ($this->siteConfig['currentSearchReplaceItem'] == 0 && $this->siteConfig['currentSearchReplacePage'] == 0) {
628 $this->log(sprintf(__('Performing search replace for table: %s', 'backup-backup'), $table), 'STEP');
629 }
630
631 // Control time of the process
632 $startTime = time();
633 $tableCompleted = false;
634 $maxExecutionTime = 5;
635
636 for ($i = $this->siteConfig['currentSearchReplaceItem']; $i < sizeof($replaces); ++$i) {
637
638 $this->siteConfig['currentSearchReplaceItem'] = $i;
639 if ((time() - $startTime) >= $maxExecutionTime) break;
640 $tableCompleted = false;
641
642 $from = $replaces[$i]['from'];
643 $to = $replaces[$i]['to'];
644
645 while ($tableCompleted === false && (time() - $startTime) < $maxExecutionTime) {
646
647 // Progress
648 $start = $this->siteConfig['currentSearchReplacePage'];
649 $end = $this->siteConfig['totalSearchReplacePages'];
650
651 // Path replace for current table
652 if ($i == 2) {
653 if (strpos($table, 'usermeta') !== false) {
654 $this->performSearchReplace($table, $from, $to, $start, $end, ['meta_key']);
655 }
656 } else {
657 $this->performSearchReplace($table, $from, $to, $start, $end);
658 }
659
660 // Handle batch logs
661 if ($this->siteConfig['currentTableTotalUpdates'] == 0 && $this->siteConfig['totalSearchReplacePages'] == 0) {
662 $this->log(sprintf($variantsEmpty[$i], $table), 'INFO');
663 } else {
664 $st = $this->siteConfig['currentSearchReplacePage'];
665 $en = $this->siteConfig['totalSearchReplacePages'];
666 $this->log(sprintf($variants[$i], $st, $en, $this->siteConfig['currentTableTotalUpdates']));
667 $this->siteConfig['currentTableTotalUpdates'] = 0;
668 }
669
670 // Done of that table
671 if ($this->siteConfig['currentSearchReplacePage'] >= $this->siteConfig['totalSearchReplacePages']) {
672 if ($i >= sizeof($replaces) - 1) {
673 unset($this->siteConfig['finishedTables'][0]);
674 $this->siteConfig['finishedTables'] = array_values($this->siteConfig['finishedTables']);
675 $this->log(sprintf(__('Search replace for table %s finished', 'backup-backup'), $table), 'SUCCESS');
676 $this->siteConfig['currentSearchReplaceItem'] = 0;
677
678 $processPercentage = intval((($this->siteConfig['amountOfTables'] - sizeof($this->siteConfig['finishedTables'])) / $this->siteConfig['amountOfTables']) * 20);
679 $this->progress(40 + $processPercentage);
680 }
681
682 $this->siteConfig['currentTableTotalUpdates'] = 0;
683 $this->siteConfig['currentSearchReplacePage'] = 0;
684 $this->siteConfig['totalSearchReplacePages'] = 0;
685 $tableCompleted = true;
686 }
687
688 }
689
690 if (!$tableCompleted) break;
691
692 }
693
694 if ($tableCompleted && sizeof($this->siteConfig['finishedTables']) == 0) $this->setContinuation(3, 3); // If path replacement finished
695 else $this->setContinuation(3, 2); // If path raplecement didnt finish, continue next batch (time exceed 5 seconds)
696
697 }
698
699 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) $this->setContinuation(4);
700
701 }
702
703 // Step: 4 (summary of S&R and file duplication)
704 private function databaseFinishedCopyFiles() {
705
706 $totalBatchExecution = 5; // 5 seconds
707 $milestoneUpdate = 500; // per 500 files/directories
708 $batchLimit = $milestoneUpdate + 1; // Limit of files/directories per batch
709
710 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
711
712 // Remove unused variables
713 unset($this->siteConfig['currentSearchReplaceItem']);
714 unset($this->siteConfig['currentTableTotalUpdates']);
715 unset($this->siteConfig['currentSearchReplacePage']);
716 unset($this->siteConfig['totalSearchReplacePages']);
717
718 // Display summary of search replace
719 $this->log(__('Displaying summary of search replace process', 'backup-backup'), 'STEP');
720 $this->log(sprintf(__('Search replace processed %s tables in total', 'backup-backup'), $this->siteConfig['totalTables']));
721 $this->log(sprintf(__('In total it processed %s rows', 'backup-backup'), $this->siteConfig['totalRows']));
722 $this->log(sprintf(__('After all it changed only %s columns', 'backup-backup'), $this->siteConfig['totalChanges']));
723 $this->log(sprintf(__('Which results in %s changes in total of all cells', 'backup-backup'), $this->siteConfig['totalUpdates']));
724
725 // Remove unused variables
726 unset($this->siteConfig['totalTables']);
727 unset($this->siteConfig['totalRows']);
728 unset($this->siteConfig['totalChanges']);
729 unset($this->siteConfig['totalUpdates']);
730
731 // Update user roles in options table
732 $this->updateUserRolesInOptions();
733
734 $this->log(__('Search replace of all tables finished successfully', 'backup-backup'), 'SUCCESS');
735
736 // Go to new process without new batch request
737 $this->siteConfig['batch'] = 2;
738
739 }
740
741 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
742
743 if (!isset($this->siteConfig['dirSeek']) || $this->siteConfig['dirSeek'] == 0) {
744 $this->log(__('Duplication of directories to staging site', 'backup-backup'), 'STEP');
745 $this->siteConfig['dirSeek'] = 0;
746 }
747
748 $pathDirsListFile = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.staging_directories';
749
750 $file = new \SplFileObject($pathDirsListFile);
751 $file->seek($file->getSize());
752
753 $startTime = time();
754 $currentSeekedElements = 0;
755 $totalLines = $file->key() + 1;
756 $stagingRoot = trailingslashit($this->siteConfig['root_staging']);
757 $file->seek($this->siteConfig['dirSeek']);
758
759 while (!$file->eof()) {
760
761 if ((time() - $startTime) > $totalBatchExecution || $currentSeekedElements > $batchLimit) break;
762
763 $file->seek($this->siteConfig['dirSeek']);
764 $path = $stagingRoot . trim($file->current());
765
766 if (!(file_exists($path) && is_dir($path))) @mkdir($path);
767 if ($this->siteConfig['dirSeek'] % $milestoneUpdate === 0 && $this->siteConfig['dirSeek'] != 0) {
768 $processPercentageTotal = number_format(($this->siteConfig['dirSeek'] / $this->siteConfig['total_directories']) * 100, 2);
769 $this->log(sprintf(
770 __('Directory creation milestone: %s/%s (%s)', 'backup-backup'),
771 $this->siteConfig['dirSeek'],
772 $this->siteConfig['total_directories'],
773 $processPercentageTotal . '%'
774 ));
775 $processPercentage = intval(($this->siteConfig['dirSeek'] / $this->siteConfig['total_directories']) * 5);
776 $this->progress(60 + $processPercentage);
777 }
778
779 $this->siteConfig['dirSeek']++;
780 $currentSeekedElements++;
781
782 }
783
784 if ($this->siteConfig['dirSeek'] >= $this->siteConfig['total_directories']) {
785 unlink($pathDirsListFile);
786 unset($this->siteConfig['dirSeek']);
787 $this->log(sprintf(
788 __('Directory creation milestone: %s/%s (%s)', 'backup-backup'),
789 $this->siteConfig['total_directories'],
790 $this->siteConfig['total_directories'],
791 '100%'
792 ));
793 $this->progress(65);
794 $this->setContinuation(4, 3);
795 } else {
796 $this->setContinuation(4, 2);
797 }
798
799 }
800
801 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) {
802
803 if (!isset($this->siteConfig['fileSeek']) || $this->siteConfig['fileSeek'] == 0) {
804 $this->log(__('Duplication of files to staging site', 'backup-backup'), 'STEP');
805 $this->log(__('Duplication of files make take a while', 'backup-backup'));
806 $this->log(__('Disclaimer: ZIP Archives are not getting cloned on staging sites', 'backup-backup'), 'WARN');
807 $this->siteConfig['fileSeek'] = 0;
808 }
809
810 $pathFilesListFile = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.staging_files';
811
812 $file = new \SplFileObject($pathFilesListFile);
813 $file->seek($file->getSize());
814
815 $startTime = time();
816 $currentSeekedElements = 0;
817 $totalLines = $file->key() + 1;
818 $sourceRoot = trailingslashit($this->siteConfig['root_source']);
819 $stagingRoot = trailingslashit($this->siteConfig['root_staging']);
820 $file->seek($this->siteConfig['fileSeek']);
821 $disallowedExtensions = ['zip', 'tar', 'gz', 'tmp', 'rar', '7z'];
822
823 while (!$file->eof()) {
824
825 if ((time() - $startTime) > $totalBatchExecution || $currentSeekedElements > $batchLimit) break;
826
827 $file->seek($this->siteConfig['fileSeek']);
828 $path = trim($file->current());
829
830 if (file_exists($sourceRoot . $path) && !file_exists($stagingRoot . $path)) {
831 $arrayWithExtension = explode('.', $path);
832 $ext = strtolower(array_pop($arrayWithExtension));
833 if (!in_array($ext, $disallowedExtensions)) {
834 @copy($sourceRoot . $path, $stagingRoot . $path);
835 }
836 }
837
838 if ($this->siteConfig['fileSeek'] % $milestoneUpdate === 0 && $this->siteConfig['fileSeek'] != 0) {
839 $processPercentageTotal = number_format(($this->siteConfig['fileSeek'] / $this->siteConfig['total_files']) * 100, 2);
840 $this->log(sprintf(
841 __('File duplication milestone: %s/%s (%s)', 'backup-backup'),
842 $this->siteConfig['fileSeek'],
843 $this->siteConfig['total_files'],
844 $processPercentageTotal . '%'
845 ));
846 $processPercentage = intval(($this->siteConfig['fileSeek'] / $this->siteConfig['total_files']) * 25);
847 $this->progress(65 + $processPercentage);
848 }
849
850 $this->siteConfig['fileSeek']++;
851 $currentSeekedElements++;
852
853 }
854
855 if ($this->siteConfig['fileSeek'] >= $this->siteConfig['total_files']) {
856 unlink($pathFilesListFile);
857 unset($this->siteConfig['fileSeek']);
858 $this->log(sprintf(
859 __('File duplication milestone: %s/%s (%s)', 'backup-backup'),
860 $this->siteConfig['total_files'],
861 $this->siteConfig['total_files'],
862 '100%'
863 ));
864 $this->progress(90);
865 $this->setContinuation(5);
866 } else {
867 $this->setContinuation(4, 3);
868 }
869
870 }
871
872 }
873
874 // Step: 5 (setup passwordless login script and paste adjusted wp-config)
875 private function setupWpConfigAndLoginScript() {
876
877 $this->copyOverPasswordLessScript();
878
879 $this->log(__('Inserting wp-config.php file for staging site', 'backup-backup'), 'STEP');
880
881 $sourceWPConfig = trailingslashit($this->siteConfig['root_source']) . 'wp-config.php';
882 $destinationWPConfig = trailingslashit($this->siteConfig['root_staging']) . 'wp-config.php';
883
884 $previousPrefix = $this->siteConfig['source_db_prefix'];
885 $destinationPrefix = $this->siteConfig['db_prefix'];
886
887 $previousRoot = untrailingslashit($this->siteConfig['root_source']);
888 $destinationRoot = untrailingslashit($this->siteConfig['root_staging']);
889
890 $sourceURL = $this->parseDomain($this->siteConfig['source_home_url']);
891 $destinationURL = $this->parseDomain($this->siteConfig['url']);
892
893 $wpconfig = file_get_contents($sourceWPConfig);
894
895 // Table Prefix
896 $this->log(__('Replacing table prefix in wp-config.php', 'backup-backup'));
897 if (strpos($wpconfig, '"' . $previousPrefix . '";') !== false) {
898 $wpconfig = str_replace('"' . $previousPrefix . '";', '"' . $destinationPrefix . '";', $wpconfig);
899 } elseif (strpos($wpconfig, "'" . $previousPrefix . "';") !== false) {
900 $wpconfig = str_replace("'" . $previousPrefix . "';", "'" . $destinationPrefix . "';", $wpconfig);
901 }
902
903 // Paths e.g. for wp_debug_log
904 $this->log(__('Adjusting paths in wp-config.php', 'backup-backup'));
905 if (strpos($wpconfig, '"' . $previousRoot . '";') !== false) {
906 $wpconfig = str_replace('"' . $previousRoot . '";', '"' . $destinationRoot . '";', $wpconfig);
907 } elseif (strpos($wpconfig, "'" . $previousRoot . "';") !== false) {
908 $wpconfig = str_replace("'" . $previousRoot . "';", "'" . $destinationRoot . "';", $wpconfig);
909 }
910
911 // Paths e.g. for wp_home & wp_siteurl
912 $this->log(__('Adjusting domains in wp-config.php', 'backup-backup'));
913 $wpconfig = explode("\n", $wpconfig);
914 for ($i = 0; $i < sizeof($wpconfig); ++$i) {
915
916 $line = $wpconfig[$i];
917
918 if (strpos($line, 'WP_SITEURL') !== false || strpos($line, 'WP_HOME') !== false) {
919 $wpconfig[$i] = str_replace($sourceURL, $destinationURL, $line);
920 }
921
922 }
923
924 $wpconfig = implode("\n", $wpconfig);
925
926 file_put_contents($destinationWPConfig, $wpconfig);
927 $this->log(__('File adjustments for wp-config.php finished successfully', 'backup-backup'), 'SUCCESS');
928
929 $this->setContinuation(6);
930
931 }
932
933 // Step: 6 (cleanup and misc)
934 private function performFinish() {
935
936 $this->cleanup();
937 $this->setContinuation(7);
938
939 }
940
941 public function __destruct() {
942
943 parent::__destruct();
944
945 }
946
947 }
948