PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.3.6
Backup Migration v1.3.6
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 / 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
972 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 'bmstg' . substr(time(), -4) . '_';
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($abspath = false) {
195
196 $excludedDirectories = [];
197
198 if ($abspath) {
199 $excludedDirectories = $this->getAllStagingSiteDirectories();
200 }
201
202 $excludedDirectories[] = '.';
203 $excludedDirectories[] = '..';
204 $excludedDirectories[] = 'wp-config.php';
205 $excludedDirectories[] = '.DS_Store';
206 $excludedDirectories[] = '.quarantine';
207 $excludedDirectories[] = '.git';
208 $excludedDirectories[] = '.tmb';
209 $excludedDirectories[] = 'node_modules';
210 $excludedDirectories[] = 'debug.log';
211
212 return $excludedDirectories;
213
214 }
215
216 private function getAbspathFiles($path) {
217
218 $excludedDirectories = $this->getExcludedFilesAndDirectories(true);
219 $topFiles = array_diff(scandir($path), $excludedDirectories);
220
221 return $topFiles;
222
223 }
224
225 private function processFiles($prefixPath, &$files) {
226
227 $dirs = [];
228
229 foreach ($files as $index => $file) {
230 $path = $prefixPath . $file;
231 if (strpos($path, BMI_BACKUPS_ROOT) !== false) continue;
232 if (is_link($path)) continue;
233 if (is_dir($path)) {
234 if (!(is_readable($path) && is_writable($path))) continue;
235 if (file_exists(trailingslashit($path) . '.bmi_staging')) continue;
236 $this->siteConfig['total_size'] += 4096;
237 $this->siteConfig['total_directories'] += 1;
238 fwrite($this->dirsList, $file . "\n");
239 $dirs[] = $path;
240 } else if (is_file($path)) {
241 if (!is_readable($path)) continue;
242 $this->siteConfig['total_size'] += filesize($path);
243 $this->siteConfig['total_files'] += 1;
244 fwrite($this->filesList, $file . "\n");
245 }
246 }
247
248 return $dirs;
249
250 }
251
252 private function processDirectoryRecursively($dir) {
253
254 if (!(is_readable($dir) && is_writable($dir))) return;
255
256 $files = array_diff(scandir($dir), $this->excludedDirectories);
257
258 foreach ($files as $key => $value) {
259 $path = $dir . DIRECTORY_SEPARATOR . $value;
260 if (strpos($path, BMI_BACKUPS_ROOT) !== false) continue;
261 if (is_link($path)) continue;
262 if (is_dir($path)) {
263 $this->siteConfig['total_size'] += 4096;
264 $this->siteConfig['total_directories'] += 1;
265 fwrite($this->dirsList, substr($path, $this->rootLength) . "\n");
266 if (!file_exists(trailingslashit($path) . '.bmi_staging')) {
267 $this->processDirectoryRecursively($path);
268 }
269 } else if (is_file($path)) {
270 if (!is_readable($path)) continue;
271 $this->siteConfig['total_size'] += filesize($path);
272 $this->siteConfig['total_files'] += 1;
273 fwrite($this->filesList, substr($path, $this->rootLength) . "\n");
274 }
275 }
276
277 }
278
279 private function processFilesRecursively($leftDirs) {
280
281 $this->rootLength = strlen(trailingslashit(ABSPATH));
282 $this->excludedDirectories = $this->getExcludedFilesAndDirectories();
283
284 for ($i = 0; $i < sizeof($leftDirs); ++$i) {
285 $this->processDirectoryRecursively($leftDirs[$i]);
286 }
287
288 }
289
290 private function getTablesForDuplication() {
291
292 global $wpdb, $table_prefix;
293
294 $currentPrefixes = $this->getAllStagingSitePrefixes();
295 $relatedTables = [];
296
297 $sql = "SELECT (DATA_LENGTH + INDEX_LENGTH) as `size`, TABLE_NAME AS `name` FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s;";
298 $sql = $wpdb->prepare($sql, array(DB_NAME));
299
300 $tables = $wpdb->get_results($sql);
301 foreach ($tables as $tableObject) {
302
303 $name = $tableObject->name;
304 $size = $tableObject->size;
305
306 if (substr($name, 0, strlen($table_prefix)) != $table_prefix) {
307 $this->log('Ommiting this table: ' . $name, 'verbose');
308 continue;
309 } else {
310 $this->log('Adding this table: ' . $name, 'verbose');
311 }
312
313 $tableOfStagingSite = false;
314 for ($i = 0; $i < sizeof($currentPrefixes); ++$i) {
315 $subPrefix = $currentPrefixes[$i];
316 if ($table_prefix != $subPrefix && substr($name, 0, strlen($subPrefix)) == $subPrefix) {
317 $tableOfStagingSite = true;
318 break;
319 }
320 }
321 if ($tableOfStagingSite) {
322 $this->log('Excluding this table as part of staging site: ' . $name, 'verbose');
323 continue;
324 }
325
326 $this->siteConfig['total_db_size'] += intval($size);
327 $relatedTables[$name] = $this->siteConfig['db_prefix'] . substr($name, strlen($table_prefix));
328
329 }
330
331 return $relatedTables;
332
333 }
334
335 private function updateUserRolesInOptions() {
336
337 global $wpdb;
338
339 // Update option name
340 $sql = "UPDATE %i SET `option_name` = %s WHERE `option_name` = %s;";
341 $newOptionTable = $this->siteConfig['db_prefix'] . 'options';
342 $newOptionName = $this->siteConfig['db_prefix'] . 'user_roles';
343 $oldOptionName = $this->siteConfig['source_db_prefix'] . 'user_roles';
344
345 $sql = $wpdb->prepare($sql, [$newOptionTable, $newOptionName, $oldOptionName]);
346 $wpdb->query($sql);
347
348 if ($wpdb->last_error !== '') {
349 $translated = __('There was an error during update of user roles:', 'backup-backup') . ' ' . $wpdb->last_error;
350 $english = 'There was an error during update of user roles:' . ' ' . $wpdb->last_error;
351 return $this->returnError($translated, $english);
352 }
353
354 $sql = "DELETE FROM %i WHERE `option_name` = 'BMI::STORAGE::LOCAL::PATH';";
355 $sql = $wpdb->prepare($sql, [$newOptionTable]);
356 $wpdb->query($sql);
357
358 if ($wpdb->last_error !== '') {
359 $translated = __('There was an error during BMI config hard removal:', 'backup-backup') . ' ' . $wpdb->last_error;
360 $english = 'There was an error during BMI config hard removal:' . ' ' . $wpdb->last_error;
361 return $this->returnError($translated, $english);
362 }
363
364 }
365
366 private function duplicateTable($source, $destination) {
367
368 global $wpdb;
369
370 // Create new table
371 // $sql = "CREATE TABLE %i LIKE %i;";
372 $sql = "CREATE TABLE %i AS SELECT * FROM %i;";
373 $sql = $wpdb->prepare($sql, [$destination, $source]);
374 $wpdb->query($sql);
375
376 if ($wpdb->last_error !== '') {
377 $translated = __('There was an error during database table creation:', 'backup-backup') . ' ' . $wpdb->last_error;
378 $english = 'There was an error during database table creation:' . ' ' . $wpdb->last_error;
379 $this->returnError($translated, $english);
380 }
381
382 // Duplicate data
383 // $sql = "INSERT INTO %i SELECT * from %i;";
384 // $sql = $wpdb->prepare($sql, [$destination, $source]);
385 // $wpdb->query($sql);
386
387 // if ($wpdb->last_error !== '') {
388 // $translated = __('There was an error during database table data duplication:', 'backup-backup') . ' ' . $wpdb->last_error;
389 // $english = 'There was an error during database table data duplication:' . ' ' . $wpdb->last_error;
390 // $this->returnError($translated, $english);
391 // }
392
393 }
394
395 private function parseDomain($domain, $removeWWW = true) {
396
397 if (substr($domain, 0, 8) == 'https://') $domain = substr($domain, 8);
398 if (substr($domain, 0, 7) == 'http://') $domain = substr($domain, 7);
399 if ($removeWWW === true) {
400 if (substr($domain, 0, 4) == 'www.') $domain = substr($domain, 4);
401 }
402 $domain = untrailingslashit($domain);
403
404 return $domain;
405
406 }
407
408 private function performSearchReplace($table, $from, $to, $start = 0, $end = 0, $limitColumns = false) {
409
410 $replaceEngine = new BMISearchReplace([$table], $start, $end, true);
411
412 $stats = $replaceEngine->perform($from, $to, $limitColumns);
413
414 $this->siteConfig['totalTables'] += $stats['tables'];
415 $this->siteConfig['totalRows'] += $stats['rows'];
416 $this->siteConfig['totalChanges'] += $stats['change'];
417 $this->siteConfig['totalUpdates'] += $stats['updates'];
418
419 $this->siteConfig['currentTableTotalUpdates'] += $stats['updates'];
420 $this->siteConfig['currentSearchReplacePage'] = $stats['currentPage'];
421 $this->siteConfig['totalSearchReplacePages'] = $stats['totalPages'];
422
423 $replaceEngine = null;
424
425 }
426
427 public function requestDelete() {
428 return $this->abort(true);
429 }
430
431 // Step: 0 (initialization of the staging site, create entry)
432 private function initialization() {
433
434 $this->log('Step 0 - Initialization of the process', 'VERBOSE');
435
436 $this->log(__('Name of subsite:', 'backup-backup') . ' ' . $this->name);
437 $this->log(__('Expected URL of subsite:', 'backup-backup') . ' ' . home_url($this->name));
438
439 $this->printInitialLogs();
440
441 $this->createDatabasePrefixAndInitDir();
442 if ($this->wasError) return;
443
444 // Set progress to do something
445 $this->progress(4);
446
447 // Set next step to be 1
448 $this->setContinuation(1);
449
450 }
451
452 // Step: 1 (preparation of file list and database recipes)
453 private function prepareFilesAndDatabase() {
454
455 // Create list of all files and directories
456 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
457 $this->log(__('Scanning all files on your website, it may take a while...', 'backup-backup'), 'STEP');
458
459 $pathDirsListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_directories';
460 $pathFilesListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_files';
461
462 $this->siteConfig['total_size'] = 0;
463 $this->siteConfig['total_files'] = 0;
464 $this->siteConfig['total_directories'] = 0;
465
466 if (file_exists($pathDirsListFile)) @unlink($pathDirsListFile);
467 if (file_exists($pathFilesListFile)) @unlink($pathFilesListFile);
468
469 $this->filesList = fopen($pathFilesListFile, 'a');
470 $this->dirsList = fopen($pathDirsListFile, 'a');
471
472 $topLevelFiles = $this->getAbspathFiles(ABSPATH);
473 $leftDirs = $this->processFiles(trailingslashit(ABSPATH), $topLevelFiles);
474
475 $this->processFilesRecursively($leftDirs);
476
477 fclose($this->filesList);
478 fclose($this->dirsList);
479
480 $this->log(__('All files scanned and prepared for duplication...', 'backup-backup'), 'SUCCESS');
481 $this->progress(10);
482
483 $this->setContinuation(1, 2);
484 }
485
486 // Display details about scan and check database size
487 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
488 $this->log(__('Looking for database tables that should be duplicated and their sizes...', 'backup-backup'), 'STEP');
489
490 $this->siteConfig['total_db_size'] = 0;
491 $this->siteConfig['tables'] = $this->getTablesForDuplication();
492 $this->siteConfig['amountOfTables'] = sizeof($this->siteConfig['tables']);
493 $this->siteConfig['sumOfTotalSize'] = intval($this->siteConfig['total_db_size']) + intval($this->siteConfig['total_size']);
494
495 $this->log(__('Amount of files to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['total_files']);
496 $this->log(__('Amount of directories to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['total_directories']);
497 $this->log(__('Amount of tables to duplicate:', 'backup-backup') . ' ' . $this->siteConfig['amountOfTables']);
498 $this->log(__('Size of database to duplicate:', 'backup-backup') . ' ' . BMP::humanSize(intval($this->siteConfig['total_db_size'])));
499 $this->log(__('Size of files to duplicate:', 'backup-backup') . ' ' . BMP::humanSize(intval($this->siteConfig['total_size'])));
500 $this->log(__('Total duplication size:', 'backup-backup') . ' ' . BMP::humanSize($this->siteConfig['sumOfTotalSize']));
501 $this->log(__('Tables prepared for duplication...', 'backup-backup'), 'SUCCESS');
502
503 $this->log(__('Checking if there is enough space for the staging site...', 'backup-backup'), 'STEP');
504 $this->log(__('Space checking may take a while', 'backup-backup'));
505
506 $this->progress(15);
507
508 $this->setContinuation(1, 3);
509 }
510
511 // Check if there is enough space for duplication
512 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) {
513 require_once BMI_INCLUDES . '/check/checker.php';
514 $checker = new Checker($this->logger);
515
516 $bytes = intval(intval($this->siteConfig['sumOfTotalSize']) * 1.1);
517 $this->log(str_replace('%s2', BMP::humanSize($bytes), str_replace('%s1', $bytes, __('Checking in total: %s1 bytes (%s2)', 'backup-backup'))));
518
519 if (!$checker->check_free_space($bytes, true)) {
520
521 $translated = __('There is not enough space on your server in order to create staging site.', 'backup-backup');
522 $english = 'There is not enough space on your server in order to create staging site.';
523 return $this->returnError($translated, $english);
524
525 } else {
526
527 $this->log(__("Confirmed, there is more than enough space, checked: ", 'backup-backup') . ($bytes) . __(" bytes", 'backup-backup'), 'SUCCESS');
528
529 }
530
531 $this->progress(20);
532
533 // Set next batch to start step 2
534 $this->log('Setting new step for next request to 2 @ batch 1', 'VERBOSE');
535 $this->setContinuation(2);
536
537 }
538
539 }
540
541 // Step: 2 (duplicate database)
542 private function duplicateDatabase() {
543
544 if (!isset($this->siteConfig['finishedTables'])) {
545 $this->siteConfig['finishedTables'] = [];
546 }
547
548 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
549 $this->log(__('Duplicating database tables...', 'backup-backup'), 'STEP');
550 }
551
552 $startTime = time();
553 foreach ($this->siteConfig['tables'] as $source_table => $destination_table) {
554
555 $this->duplicateTable($source_table, $destination_table);
556 $this->log(str_replace('%s1', $source_table, str_replace('%s2', $destination_table, __('Table %s1 cloned as %s2', 'backup-backup'))));
557
558 $this->siteConfig['finishedTables'][] = $destination_table;
559 unset($this->siteConfig['tables'][$source_table]);
560
561 $processPercentage = intval((sizeof($this->siteConfig['finishedTables']) / $this->siteConfig['amountOfTables']) * 20);
562 $this->progress(20 + $processPercentage);
563
564 if ((time() - $startTime) >= 4) {
565 $this->setContinuation(2, (intval($this->siteConfig['batch']) + 1));
566 break;
567 }
568
569 }
570
571 if (sizeof($this->siteConfig['tables']) == 0) {
572 unset($this->siteConfig['tables']);
573 }
574
575 if (!isset($this->siteConfig['tables'])) {
576 $this->log(__('All tables were successfully duplicated', 'backup-backup'), 'SUCCESS');
577 $this->setContinuation(3);
578 }
579
580 }
581
582 // Step: 3 (search & replace of new tables)
583 private function searchReplace() {
584
585 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'search-replace.php';
586
587 // Domain
588 $sourceURL = $this->parseDomain($this->siteConfig['source_home_url']);
589 $destinationURL = $this->parseDomain($this->siteConfig['url']);
590
591 // Paths
592 $sourceABSPATH = untrailingslashit($this->siteConfig['root_source']);
593 $destinationABSPATH = untrailingslashit($this->siteConfig['root_staging']);
594
595 // Items to be replaced
596 $replaces = [
597 ['from' => $sourceABSPATH, 'to' => $destinationABSPATH],
598 ['from' => $sourceURL, 'to' => $destinationURL],
599 ['from' => $this->siteConfig['source_db_prefix'], 'to' => $this->siteConfig['db_prefix']]
600 ];
601
602 $variants = [
603 __("Batch for path adjustment (%s/%s) updated: %s fields.", 'backup-backup'),
604 __("Batch for domain adjustments (%s/%s) updated: %s fields.", 'backup-backup'),
605 __("Batch for prefix key adjustments (%s/%s) updated: %s fields.", 'backup-backup')
606 ];
607
608 $variantsEmpty = [
609 __("Path replacements are not required for table: %s", 'backup-backup'),
610 __("Domain replacements are not required for table: %s", 'backup-backup'),
611 __("Prefix key replacements are not required for table: %s", 'backup-backup')
612 ];
613
614 // Table for replacement
615 if (isset($this->siteConfig['finishedTables']) && sizeof($this->siteConfig['finishedTables']) > 0) {
616
617 // Get first table from duplicated tables
618 $table = $this->siteConfig['finishedTables'][0];
619
620 } else {
621
622 // All tables processed, search replace finished
623 unset($this->siteConfig['finishedTables']);
624 $this->setContinuation(4);
625
626 }
627
628 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
629 $this->log(__('Performing database search replace on duplicated tables...', 'backup-backup'), 'STEP');
630
631 $pagesize = '?';
632 if (defined('BMI_MAX_SEARCH_REPLACE_PAGE')) $pagesize = BMI_MAX_SEARCH_REPLACE_PAGE;
633 $this->log(__('Page size for that process: ', 'backup-backup') . $pagesize, 'INFO');
634
635 $this->siteConfig['totalTables'] = 0;
636 $this->siteConfig['totalRows'] = 0;
637 $this->siteConfig['totalChanges'] = 0;
638 $this->siteConfig['totalUpdates'] = 0;
639
640 $this->siteConfig['currentSearchReplaceItem'] = 0;
641 $this->siteConfig['currentTableTotalUpdates'] = 0;
642 $this->siteConfig['currentSearchReplacePage'] = 0;
643 $this->siteConfig['totalSearchReplacePages'] = 0;
644
645 $this->setContinuation(3, 2);
646 }
647
648 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
649
650 // Give details of current process
651 if ($this->siteConfig['currentSearchReplaceItem'] == 0 && $this->siteConfig['currentSearchReplacePage'] == 0) {
652 $this->log(sprintf(__('Performing search replace for table: %s', 'backup-backup'), $table), 'STEP');
653 }
654
655 // Control time of the process
656 $startTime = time();
657 $tableCompleted = false;
658 $maxExecutionTime = 5;
659
660 for ($i = $this->siteConfig['currentSearchReplaceItem']; $i < sizeof($replaces); ++$i) {
661
662 $this->siteConfig['currentSearchReplaceItem'] = $i;
663 if ((time() - $startTime) >= $maxExecutionTime) break;
664 $tableCompleted = false;
665
666 $from = $replaces[$i]['from'];
667 $to = $replaces[$i]['to'];
668
669 while ($tableCompleted === false && (time() - $startTime) < $maxExecutionTime) {
670
671 // Progress
672 $start = $this->siteConfig['currentSearchReplacePage'];
673 $end = $this->siteConfig['totalSearchReplacePages'];
674
675 // Path replace for current table
676 if ($i == 2) {
677 if (strpos($table, 'usermeta') !== false) {
678 $this->performSearchReplace($table, $from, $to, $start, $end, ['meta_key']);
679 }
680 } else {
681 $this->performSearchReplace($table, $from, $to, $start, $end);
682 }
683
684 // Handle batch logs
685 if ($this->siteConfig['currentTableTotalUpdates'] == 0 && $this->siteConfig['totalSearchReplacePages'] == 0) {
686 $this->log(sprintf($variantsEmpty[$i], $table), 'INFO');
687 } else {
688 $st = $this->siteConfig['currentSearchReplacePage'];
689 $en = $this->siteConfig['totalSearchReplacePages'];
690 $this->log(sprintf($variants[$i], $st, $en, $this->siteConfig['currentTableTotalUpdates']));
691 $this->siteConfig['currentTableTotalUpdates'] = 0;
692 }
693
694 // Done of that table
695 if ($this->siteConfig['currentSearchReplacePage'] >= $this->siteConfig['totalSearchReplacePages']) {
696 if ($i >= sizeof($replaces) - 1) {
697 unset($this->siteConfig['finishedTables'][0]);
698 $this->siteConfig['finishedTables'] = array_values($this->siteConfig['finishedTables']);
699 $this->log(sprintf(__('Search replace for table %s finished', 'backup-backup'), $table), 'SUCCESS');
700 $this->siteConfig['currentSearchReplaceItem'] = 0;
701
702 $processPercentage = intval((($this->siteConfig['amountOfTables'] - sizeof($this->siteConfig['finishedTables'])) / $this->siteConfig['amountOfTables']) * 20);
703 $this->progress(40 + $processPercentage);
704 }
705
706 $this->siteConfig['currentTableTotalUpdates'] = 0;
707 $this->siteConfig['currentSearchReplacePage'] = 0;
708 $this->siteConfig['totalSearchReplacePages'] = 0;
709 $tableCompleted = true;
710 }
711
712 }
713
714 if (!$tableCompleted) break;
715
716 }
717
718 if ($tableCompleted && sizeof($this->siteConfig['finishedTables']) == 0) $this->setContinuation(3, 3); // If path replacement finished
719 else $this->setContinuation(3, 2); // If path raplecement didnt finish, continue next batch (time exceed 5 seconds)
720
721 }
722
723 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) $this->setContinuation(4);
724
725 }
726
727 // Step: 4 (summary of S&R and file duplication)
728 private function databaseFinishedCopyFiles() {
729
730 $totalBatchExecution = 5; // 5 seconds
731 $milestoneUpdate = 500; // per 500 files/directories
732 $batchLimit = $milestoneUpdate + 1; // Limit of files/directories per batch
733
734 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 1) {
735
736 // Remove unused variables
737 unset($this->siteConfig['currentSearchReplaceItem']);
738 unset($this->siteConfig['currentTableTotalUpdates']);
739 unset($this->siteConfig['currentSearchReplacePage']);
740 unset($this->siteConfig['totalSearchReplacePages']);
741
742 // Display summary of search replace
743 $this->log(__('Displaying summary of search replace process', 'backup-backup'), 'STEP');
744 $this->log(sprintf(__('Search replace processed %s tables in total', 'backup-backup'), $this->siteConfig['totalTables']));
745 $this->log(sprintf(__('In total it processed %s rows', 'backup-backup'), $this->siteConfig['totalRows']));
746 $this->log(sprintf(__('After all it changed only %s columns', 'backup-backup'), $this->siteConfig['totalChanges']));
747 $this->log(sprintf(__('Which results in %s changes in total of all cells', 'backup-backup'), $this->siteConfig['totalUpdates']));
748
749 // Remove unused variables
750 unset($this->siteConfig['totalTables']);
751 unset($this->siteConfig['totalRows']);
752 unset($this->siteConfig['totalChanges']);
753 unset($this->siteConfig['totalUpdates']);
754
755 // Update user roles in options table
756 $this->updateUserRolesInOptions();
757
758 $this->log(__('Search replace of all tables finished successfully', 'backup-backup'), 'SUCCESS');
759
760 // Go to new process without new batch request
761 $this->siteConfig['batch'] = 2;
762
763 }
764
765 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 2) {
766
767 if (!isset($this->siteConfig['dirSeek']) || $this->siteConfig['dirSeek'] == 0) {
768 $this->log(__('Duplication of directories to staging site', 'backup-backup'), 'STEP');
769 $this->siteConfig['dirSeek'] = 0;
770 }
771
772 $pathDirsListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_directories';
773
774 $file = new \SplFileObject($pathDirsListFile);
775 $file->seek($file->getSize());
776
777 $startTime = time();
778 $currentSeekedElements = 0;
779 $totalLines = $file->key() + 1;
780 $stagingRoot = trailingslashit($this->siteConfig['root_staging']);
781 $file->seek($this->siteConfig['dirSeek']);
782
783 while (!$file->eof()) {
784
785 if ((time() - $startTime) > $totalBatchExecution || $currentSeekedElements > $batchLimit) break;
786
787 $file->seek($this->siteConfig['dirSeek']);
788 $path = $stagingRoot . trim($file->current());
789
790 if (!(file_exists($path) && is_dir($path))) @mkdir($path);
791 if ($this->siteConfig['dirSeek'] % $milestoneUpdate === 0 && $this->siteConfig['dirSeek'] != 0) {
792 $processPercentageTotal = number_format(($this->siteConfig['dirSeek'] / $this->siteConfig['total_directories']) * 100, 2);
793 $this->log(sprintf(
794 __('Directory creation milestone: %s/%s (%s)', 'backup-backup'),
795 $this->siteConfig['dirSeek'],
796 $this->siteConfig['total_directories'],
797 $processPercentageTotal . '%'
798 ));
799 $processPercentage = intval(($this->siteConfig['dirSeek'] / $this->siteConfig['total_directories']) * 5);
800 $this->progress(60 + $processPercentage);
801 }
802
803 $this->siteConfig['dirSeek']++;
804 $currentSeekedElements++;
805
806 }
807
808 if ($this->siteConfig['dirSeek'] >= $this->siteConfig['total_directories']) {
809 unlink($pathDirsListFile);
810 unset($this->siteConfig['dirSeek']);
811 $this->log(sprintf(
812 __('Directory creation milestone: %s/%s (%s)', 'backup-backup'),
813 $this->siteConfig['total_directories'],
814 $this->siteConfig['total_directories'],
815 '100%'
816 ));
817 $this->progress(65);
818 $this->setContinuation(4, 3);
819 } else {
820 $this->setContinuation(4, 2);
821 }
822
823 }
824
825 if (isset($this->siteConfig['batch']) && $this->siteConfig['batch'] == 3) {
826
827 if (!isset($this->siteConfig['fileSeek']) || $this->siteConfig['fileSeek'] == 0) {
828 $this->log(__('Duplication of files to staging site', 'backup-backup'), 'STEP');
829 $this->log(__('Duplication of files make take a while', 'backup-backup'));
830 $this->log(__('Disclaimer: ZIP Archives are not getting cloned on staging sites', 'backup-backup'), 'WARN');
831 $this->siteConfig['fileSeek'] = 0;
832 }
833
834 $pathFilesListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_files';
835
836 $file = new \SplFileObject($pathFilesListFile);
837 $file->seek($file->getSize());
838
839 $startTime = time();
840 $currentSeekedElements = 0;
841 $totalLines = $file->key() + 1;
842 $sourceRoot = trailingslashit($this->siteConfig['root_source']);
843 $stagingRoot = trailingslashit($this->siteConfig['root_staging']);
844 $file->seek($this->siteConfig['fileSeek']);
845 $disallowedExtensions = ['zip', 'tar', 'gz', 'tmp', 'rar', '7z'];
846
847 while (!$file->eof()) {
848
849 if ((time() - $startTime) > $totalBatchExecution || $currentSeekedElements > $batchLimit) break;
850
851 $file->seek($this->siteConfig['fileSeek']);
852 $path = trim($file->current());
853
854 if (file_exists($sourceRoot . $path) && !file_exists($stagingRoot . $path)) {
855 $arrayWithExtension = explode('.', $path);
856 $ext = strtolower(array_pop($arrayWithExtension));
857 if (!in_array($ext, $disallowedExtensions) && strpos($path, 'backup-migration-config.php') === false) {
858 @copy($sourceRoot . $path, $stagingRoot . $path);
859 }
860 }
861
862 if ($this->siteConfig['fileSeek'] % $milestoneUpdate === 0 && $this->siteConfig['fileSeek'] != 0) {
863 $processPercentageTotal = number_format(($this->siteConfig['fileSeek'] / $this->siteConfig['total_files']) * 100, 2);
864 $this->log(sprintf(
865 __('File duplication milestone: %s/%s (%s)', 'backup-backup'),
866 $this->siteConfig['fileSeek'],
867 $this->siteConfig['total_files'],
868 $processPercentageTotal . '%'
869 ));
870 $processPercentage = intval(($this->siteConfig['fileSeek'] / $this->siteConfig['total_files']) * 25);
871 $this->progress(65 + $processPercentage);
872 }
873
874 $this->siteConfig['fileSeek']++;
875 $currentSeekedElements++;
876
877 }
878
879 if ($this->siteConfig['fileSeek'] >= $this->siteConfig['total_files']) {
880 unlink($pathFilesListFile);
881 unset($this->siteConfig['fileSeek']);
882 $this->log(sprintf(
883 __('File duplication milestone: %s/%s (%s)', 'backup-backup'),
884 $this->siteConfig['total_files'],
885 $this->siteConfig['total_files'],
886 '100%'
887 ));
888 $this->progress(90);
889 $this->setContinuation(5);
890 } else {
891 $this->setContinuation(4, 3);
892 }
893
894 }
895
896 }
897
898 // Step: 5 (setup passwordless login script and paste adjusted wp-config)
899 private function setupWpConfigAndLoginScript() {
900
901 $this->copyOverPasswordLessScript();
902
903 $this->log(__('Inserting wp-config.php file for staging site', 'backup-backup'), 'STEP');
904
905 $sourceWPConfig = trailingslashit($this->siteConfig['root_source']) . 'wp-config.php';
906 $destinationWPConfig = trailingslashit($this->siteConfig['root_staging']) . 'wp-config.php';
907
908 $previousPrefix = $this->siteConfig['source_db_prefix'];
909 $destinationPrefix = $this->siteConfig['db_prefix'];
910
911 $previousRoot = untrailingslashit($this->siteConfig['root_source']);
912 $destinationRoot = untrailingslashit($this->siteConfig['root_staging']);
913
914 $sourceURL = $this->parseDomain($this->siteConfig['source_home_url']);
915 $destinationURL = $this->parseDomain($this->siteConfig['url']);
916
917 $wpconfig = file_get_contents($sourceWPConfig);
918
919 // Table Prefix
920 $this->log(__('Replacing table prefix in wp-config.php', 'backup-backup'));
921 if (strpos($wpconfig, '"' . $previousPrefix . '";') !== false) {
922 $wpconfig = str_replace('"' . $previousPrefix . '";', '"' . $destinationPrefix . '";', $wpconfig);
923 } elseif (strpos($wpconfig, "'" . $previousPrefix . "';") !== false) {
924 $wpconfig = str_replace("'" . $previousPrefix . "';", "'" . $destinationPrefix . "';", $wpconfig);
925 }
926
927 // Paths e.g. for wp_debug_log
928 $this->log(__('Adjusting paths in wp-config.php', 'backup-backup'));
929 if (strpos($wpconfig, '"' . $previousRoot . '";') !== false) {
930 $wpconfig = str_replace('"' . $previousRoot . '";', '"' . $destinationRoot . '";', $wpconfig);
931 } elseif (strpos($wpconfig, "'" . $previousRoot . "';") !== false) {
932 $wpconfig = str_replace("'" . $previousRoot . "';", "'" . $destinationRoot . "';", $wpconfig);
933 }
934
935 // Paths e.g. for wp_home & wp_siteurl
936 $this->log(__('Adjusting domains in wp-config.php', 'backup-backup'));
937 $wpconfig = explode("\n", $wpconfig);
938 for ($i = 0; $i < sizeof($wpconfig); ++$i) {
939
940 $line = $wpconfig[$i];
941
942 if (strpos($line, 'WP_SITEURL') !== false || strpos($line, 'WP_HOME') !== false) {
943 $wpconfig[$i] = str_replace($sourceURL, $destinationURL, $line);
944 }
945
946 }
947
948 $wpconfig = implode("\n", $wpconfig);
949
950 file_put_contents($destinationWPConfig, $wpconfig);
951 $this->log(__('File adjustments for wp-config.php finished successfully', 'backup-backup'), 'SUCCESS');
952
953 $this->setContinuation(6);
954
955 }
956
957 // Step: 6 (cleanup and misc)
958 private function performFinish() {
959
960 $this->cleanup();
961 $this->setContinuation(7);
962
963 }
964
965 public function __destruct() {
966
967 parent::__destruct();
968
969 }
970
971 }
972