PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 1.3.3
JetBackup – Backup, Restore & Migrate v1.3.3
3.1.22.4 3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / com / core / backup / SGBackupDatabase.php
backup / com / core / backup Last commit date
SGBackup.php 6 years ago SGBackupDatabase.php 6 years ago SGBackupFiles.php 6 years ago SGBackupLog.php 6 years ago SGBackupSchedule.php 6 years ago SGBackupStorage.php 6 years ago SGIBackupDelegate.php 6 years ago
SGBackupDatabase.php
651 lines
1 <?php
2
3 require_once(SG_BACKUP_PATH.'SGIBackupDelegate.php');
4 require_once(SG_LIB_PATH.'SGDBState.php');
5 require_once(SG_LIB_PATH.'SGMysqldump.php');
6 require_once(SG_LIB_PATH.'SGCharsetHandler.php');
7 backupGuardIncludeFile(SG_LIB_PATH.'SGMigrate.php');
8
9 class SGBackupDatabase implements SGIMysqldumpDelegate
10 {
11 private $sgdb = null;
12 private $backupFilePath = '';
13 private $delegate = null;
14 private $cancelled = false;
15 private $nextProgressUpdate = 0;
16 private $actionStartTs = 0;
17 private $totalRowCount = 0;
18 private $currentRowCount = 0;
19 private $warningsFound = false;
20 private $pendingStorageUploads = array();
21 private $state = null;
22 private $reloadStartTs = null;
23 private $progressUpdateInterval;
24 private $migrationAvailable = null;
25 private $oldDbPrefix = null;
26 private $backedUpTables = null;
27 private $newTableNames = null;
28 private $migrateObj = null;
29 private $charsetHanlder = null;
30
31 public function __construct()
32 {
33 $this->sgdb = SGDatabase::getInstance();
34 $this->progressUpdateInterval = SGConfig::get('SG_ACTION_PROGRESS_UPDATE_INTERVAL');
35 }
36
37 public function setDelegate(SGIBackupDelegate $delegate)
38 {
39 $this->delegate = $delegate;
40 }
41
42 public function setFilePath($filePath)
43 {
44 $this->backupFilePath = $filePath;
45 }
46
47 public function isMigrationAvailable()
48 {
49 if ($this->migrationAvailable === null) {
50 $this->migrationAvailable = SGBoot::isFeatureAvailable('BACKUP_WITH_MIGRATION');
51 }
52
53 return $this->migrationAvailable;
54 }
55
56 public function getOldDbPrefix()
57 {
58 if ($this->oldDbPrefix === null) {
59 $this->oldDbPrefix = SGConfig::get('SG_OLD_DB_PREFIX');
60 }
61
62 return $this->oldDbPrefix;
63 }
64
65 public function getBackedUpTables()
66 {
67 if ($this->backedUpTables === null) {
68 $tableNames = SGConfig::get('SG_BACKUPED_TABLES');
69 if ($tableNames) {
70 $tableNames = json_decode($tableNames, true);
71 }
72 else {
73 $tableNames = array();
74 }
75 $this->backedUpTables = $tableNames;
76 }
77
78 return $this->backedUpTables;
79 }
80
81 public function getNewTableNames()
82 {
83 if ($this->newTableNames === null) {
84 $oldDbPrefix = $this->getOldDbPrefix();
85 $tableNames = $this->getBackedUpTables();
86
87 $newTableNames = array();
88 foreach ($tableNames as $tableName) {
89 $newTableNames[] = str_replace($oldDbPrefix, SG_ENV_DB_PREFIX, $tableName);
90 }
91 $this->newTableNames = $newTableNames;
92 }
93
94 return $this->newTableNames;
95 }
96
97 public function getMigrateObj()
98 {
99 if ($this->migrateObj === null) {
100 $this->migrateObj = new SGMigrate();
101 }
102
103 return $this->migrateObj;
104 }
105
106 public function getCharsetHandler()
107 {
108 if ($this->charsetHanlder === null) {
109 $this->charsetHanlder = new SGCharsetHandler();
110 }
111
112 return $this->charsetHanlder;
113 }
114
115 public function didFindWarnings()
116 {
117 return $this->warningsFound;
118 }
119
120 public function setPendingStorageUploads($pendingStorageUploads)
121 {
122 $this->pendingStorageUploads = $pendingStorageUploads;
123 }
124
125 public function saveStateData($offset, $cursor, $inprogress, $lineSize, $backedUpTables)
126 {
127 $sgDBState = $this->getState();
128 $token = $this->delegate->getToken();
129
130 $sgDBState->setLineSize($lineSize);
131 $sgDBState->setNumberOfEntries($this->totalRowCount);
132 $sgDBState->setAction(SG_STATE_ACTION_EXPORTING_SQL);
133 $sgDBState->setInprogress($inprogress);
134 $sgDBState->setCursor($cursor);
135 $sgDBState->setProgressCursor($this->currentRowCount);
136 $sgDBState->setOffset($offset);
137 $sgDBState->setToken($token);
138 $sgDBState->setProgress($this->nextProgressUpdate);
139 $sgDBState->setWarningsFound($this->warningsFound);
140 $sgDBState->setPendingStorageUploads($this->pendingStorageUploads);
141 $sgDBState->setBackedUpTables($backedUpTables);
142 $sgDBState->save();
143 }
144
145 public function getState()
146 {
147 return $this->delegate->getState();
148 }
149
150 public function shouldReload()
151 {
152 $currentTime = time();
153
154 if (($currentTime - $this->reloadStartTs) >= SG_RELOAD_TIMEOUT) {
155 return true;
156 }
157
158 return false;
159 }
160
161 public function reload()
162 {
163 $this->delegate->reload();
164 }
165
166 public function backup($filePath)
167 {
168 $this->reloadStartTs = time();
169 $this->state = $this->delegate->getState();
170
171 if ($this->state && $this->state->getAction() == SG_STATE_ACTION_PREPARING_STATE_FILE) {
172 $this->actionStartTs = time();
173 SGBackupLog::writeAction('backup database', SG_BACKUP_LOG_POS_START);
174 $this->resetBackupProgress();
175 }
176 else {
177 $this->totalRowCount = $this->state->getNumberOfEntries();
178 $this->currentRowCount = $this->state->getProgressCursor();
179 }
180
181 $this->backupFilePath = $filePath;
182
183 $this->export();
184
185 SGBackupLog::writeAction('backup database', SG_BACKUP_LOG_POS_END);
186 SGBackupLog::write('backup database total duration: '.backupGuardFormattedDuration($this->actionStartTs, time()));
187 }
188
189 public function restore($filePath)
190 {
191 $this->reloadStartTs = time();
192 $this->backupFilePath = $filePath;
193
194 $sgDBState = $this->getState();
195 if ($sgDBState && $sgDBState->getType() == SG_STATE_TYPE_DB) {
196 if ($sgDBState->getAction() != SG_STATE_ACTION_RESTORING_DATABASE) {
197 SGBackupLog::writeAction('restore database', SG_BACKUP_LOG_POS_START);
198 $this->actionStartTs = time();
199 //prepare for restore (reset variables)
200 $this->resetRestoreProgress();
201 }
202 //import all db tables
203 $this->import();
204 }
205
206 //run migration logic
207 if ($this->isMigrationAvailable()) {
208 if ($sgDBState->getAction() != SG_STATE_ACTION_MIGRATING_DATABASE) {
209 $this->delegate->prepareMigrateStateFile();
210 }
211
212 $this->processMigration();
213 }
214
215 //external restore file doesn't have the wordpress functions
216 //so we cannot do anything here
217 //it will finalize the restore for itself
218 if (!SGExternalRestore::isEnabled()) {
219 $this->finalizeRestore();
220 }
221
222 SGBackupLog::writeAction('restore database', SG_BACKUP_LOG_POS_END);
223 SGBackupLog::write('restore database total duration: '.backupGuardFormattedDuration($this->actionStartTs, time()));
224 }
225
226 private function processMigration()
227 {
228 $sgMigrateState = $this->getState();
229 if ($sgMigrateState && $sgMigrateState->getAction() != SG_STATE_ACTION_MIGRATING_DATABASE) {
230 SGBackupLog::writeAction('migration', SG_BACKUP_LOG_POS_START);
231 }
232
233 $sgMigrate = new SGMigrate($this->sgdb);
234 $sgMigrate->setDelegate($this);
235
236 $tables = $this->getTables();
237
238 $oldSiteUrl = SGConfig::get('SG_OLD_SITE_URL');
239
240 // Find and replace old urls with new ones
241 $sgMigrate->migrate($oldSiteUrl, SG_SITE_URL, $tables);
242
243 // Find and replace old db prefixes with new ones
244 $sgMigrate->migrateDBPrefix();
245
246 $isMultisite = backupGuardIsMultisite();
247 if ($isMultisite) {
248 $tables = explode(',', SG_MULTISITE_TABLES_TO_MIGRATE);
249
250 $oldPath = SGConfig::get('SG_MULTISITE_OLD_PATH');
251 $newPath = PATH_CURRENT_SITE;
252 $newDomain = DOMAIN_CURRENT_SITE;
253
254 $sgMigrate->migrateMultisite($newDomain, $newPath, $oldPath, $tables);
255 }
256
257 SGBackupLog::writeAction('migration', SG_BACKUP_LOG_POS_END);
258 }
259
260 public function finalizeRestore()
261 {
262 if (SG_ENV_ADAPTER != SG_ENV_WORDPRESS) {
263 return;
264 }
265
266 //recreate current user (to be able to login with it)
267 $this->restoreCurrentUser();
268
269 //setting the following options will tell WordPress that the db is already updated
270 global $wp_db_version;
271 update_option('db_version', $wp_db_version);
272 update_option('db_upgraded', true);
273
274 //fix invalid upload path inserted in db
275 update_option("upload_path", "");
276 }
277
278 private function export()
279 {
280 if ($this->state && $this->state->getAction() == SG_STATE_ACTION_PREPARING_STATE_FILE) {
281 if (!$this->isWritable($this->backupFilePath)) {
282 throw new SGExceptionForbidden('Permission denied. File is not writable: '.$this->backupFilePath);
283 }
284 }
285
286 $customTablesToExclude = str_replace(' ', '', SGConfig::get('SG_TABLES_TO_EXCLUDE'));
287 $tablesToExclude = explode(',', SGConfig::get('SG_BACKUP_DATABASE_EXCLUDE').','.$customTablesToExclude);
288
289 $tablesToBackup = $this->state->getTablesToBackup() ? explode(',', $this->state->getTablesToBackup()) : array();
290 $dump = new SGMysqldump($this->sgdb, SG_DB_NAME, 'mysql', array(
291 'exclude-tables'=>$tablesToExclude,
292 'include-tables'=>$tablesToBackup,
293 'skip-dump-date'=>true,
294 'skip-comments'=>true,
295 'skip-tz-utz'=>true,
296 'add-drop-table'=>true,
297 'no-autocommit'=>false,
298 'single-transaction'=>false,
299 'lock-tables'=>false,
300 'default-character-set'=>SG_DB_CHARSET,
301 'add-locks'=>false
302 ));
303
304 $dump->setDelegate($this);
305
306 $dump->start($this->backupFilePath);
307 }
308
309 private function prepareQueryToExec($query)
310 {
311 $query = $this->replaceInvalidCharacters($query);
312 $query = $this->replaceInvalidEngineTypeInQuery($query);
313
314 if ($this->isMigrationAvailable()) {
315 $tableNames = $this->getBackedUpTables();
316 $newTableNames = $this->getNewTableNames();
317 $query = $this->getMigrateObj()->replaceValuesInQuery($tableNames, $newTableNames, $query);
318 }
319
320 $query = $this->getCharsetHandler()->replaceInvalidCharsets($query);
321
322 $query = rtrim(trim($query), "/*SGEnd*/");
323
324 return $query;
325 }
326
327 private function replaceInvalidEngineTypeInQuery($query)
328 {
329 if (version_compare(SG_MYSQL_VERSION, '5.1', '>=')) {
330 return str_replace("TYPE=InnoDB", "ENGINE=InnoDB", $query);
331 }
332 else {
333 return str_replace("ENGINE=InnoDB", "TYPE=InnoDB", $query);
334 }
335 }
336
337 private function replaceInvalidCharacters($str)
338 {
339 return $str;//preg_replace('/\x00/', '', $str);;
340 }
341
342 private function getDatabaseHeaders()
343 {
344 return "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;/*SGEnd*/".PHP_EOL.
345 "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;/*SGEnd*/".PHP_EOL.
346 "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;/*SGEnd*/".PHP_EOL.
347 "/*!40101 SET NAMES ".SG_DB_CHARSET." */;/*SGEnd*/".PHP_EOL.
348 "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;/*SGEnd*/".PHP_EOL.
349 "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;/*SGEnd*/".PHP_EOL.
350 "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;/*SGEnd*/".PHP_EOL.
351 "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;/*SGEnd*/".PHP_EOL;
352 }
353
354 private function import()
355 {
356 $fileHandle = @fopen($this->backupFilePath, 'r');
357 if (!is_resource($fileHandle)) {
358 throw new SGExceptionForbidden('Could not open file: '.$this->backupFilePath);
359 }
360
361 $importQuery = '';
362 $sgDBState = $this->getState();
363 if ($sgDBState && $sgDBState->getAction() == SG_STATE_ACTION_RESTORING_DATABASE) {
364 $offset = $sgDBState->getOffset();
365 fseek($fileHandle, $offset);
366
367 $this->totalRowCount = $sgDBState->getNumberOfEntries();
368 $this->currentRowCount = $sgDBState->getProgressCursor();
369 $this->nextProgressUpdate = $sgDBState->getProgress();
370 $this->warningsFound = $sgDBState->getWarningsFound();
371
372 $importQuery = $this->getDatabaseHeaders();
373 }
374
375 while (($row = @fgets($fileHandle)) !== false) {
376 $importQuery .= $row;
377 $trimmedRow = trim($row);
378
379 if (strpos($trimmedRow, 'CREATE TABLE') !== false) {
380 $strLength = strlen($trimmedRow);
381 $strCtLength = strlen('CREATE TABLE ');
382 $length = $strLength - $strCtLength - 2;
383 $tableName = substr($trimmedRow, $strCtLength, $length);
384
385 SGBackupLog::write('Importing table: '.$tableName);
386 }
387
388 if ($trimmedRow && substr($trimmedRow, -9) == "/*SGEnd*/") {
389 $queries = explode("/*SGEnd*/".PHP_EOL, $importQuery);
390 foreach ($queries as $query) {
391 if (!$query) {
392 continue;
393 }
394
395 $importQuery = $this->prepareQueryToExec($query);
396
397 SGPing::update();
398 $res = $this->sgdb->execRaw($importQuery);
399 if ($res === false) {
400 //continue restoring database if any query fails
401 //we will just show a warning inside the log
402
403 if (isset($tableName)) {
404 $this->warn('Could not import table: '.$tableName);
405 }
406
407 $this->warn('Error: '.$this->sgdb->getLastError());
408 }
409
410 $shouldReload = $this->shouldReload();
411 $isReloadEnabled = backupGuardIsReloadEnabled();
412 if ($shouldReload && $isReloadEnabled && SGExternalRestore::isEnabled()) {
413 $offset = ftell($fileHandle);
414 $token = $this->delegate->getToken();
415
416 $sgDBState = $this->getState();
417
418 $sgDBState->setToken($token);
419 $sgDBState->setOffset($offset);
420 $sgDBState->setProgress($this->nextProgressUpdate);
421 $sgDBState->setWarningsFound($this->warningsFound);
422 $sgDBState->setNumberOfEntries($this->totalRowCount);
423 $sgDBState->setProgressCursor($this->currentRowCount);
424 $sgDBState->setActionId($this->delegate->getActionId());
425 $sgDBState->setAction(SG_STATE_ACTION_RESTORING_DATABASE);
426
427 $sgDBState->save();
428
429 SGPing::update();
430 @fclose($fileHandle);
431
432 $this->reload();
433 }
434 }
435
436 $importQuery = '';
437 }
438
439 $this->currentRowCount++;
440 SGPing::update();
441 $this->updateProgress();
442 }
443
444 @fclose($fileHandle);
445 }
446
447 public function saveCurrentUser()
448 {
449 if (SG_ENV_ADAPTER != SG_ENV_WORDPRESS) {
450 return;
451 }
452
453 $user = wp_get_current_user();
454
455 $currentUser = serialize(array(
456 'login' => $user->user_login,
457 'pass' => $user->user_pass,
458 'email' => $user->user_email,
459 ));
460
461 SGConfig::set('SG_CURRENT_USER', $currentUser);
462 }
463
464 private function restoreCurrentUser()
465 {
466 $currentUser = SGConfig::get('SG_CURRENT_USER');
467 $user = unserialize($currentUser);
468
469 //erase user data from the config table
470 SGConfig::set('SG_CURRENT_USER', '');
471
472 //if a user is found, it means it's cache, because we have dropped wp_users already
473 $cachedUser = get_user_by('login', $user['login']);
474 if ($cachedUser) {
475 clean_user_cache($cachedUser); //delete user from cache
476 }
477
478 //create a user (it will be a subscriber)
479 $id = wp_create_user($user['login'], $user['pass'], $user['email']);
480 if (is_wp_error($id)) {
481 SGBackupLog::write('User not recreated: '.$id->get_error_message());
482 return false; //user was not created for some reason
483 }
484
485 //get the newly created user
486 $newUser = get_user_by('id', $id);
487
488 //remove its role of subscriber
489 $newUser->remove_role('subscriber');
490 $isMultisite = backupGuardIsMultisite();
491
492 if ($isMultisite) {
493 // add super adminn role
494 grant_super_admin($id);
495 }
496 else {
497 //add admin role
498 $newUser->add_role('administrator');
499 }
500
501 //update password to set the correct (old) password
502 $this->sgdb->query(
503 'UPDATE '.SG_ENV_DB_PREFIX.'users SET user_pass=%s WHERE ID=%d',
504 array(
505 $user['pass'],
506 $id
507 )
508 );
509
510 //clean cache, so new password can take effect
511 clean_user_cache($newUser);
512
513 SGBackupLog::write('User recreated: '.$user['login']);
514 }
515
516 public function warn($message)
517 {
518 $this->warningsFound = true;
519 SGBackupLog::writeWarning($message);
520 }
521
522 public function didExportRow()
523 {
524 $this->currentRowCount++;
525 SGPing::update();
526
527 if ($this->updateProgress())
528 {
529 if ($this->delegate && $this->delegate->isCancelled())
530 {
531 $this->cancelled = true;
532 return;
533 }
534 }
535
536 if (SGBoot::isFeatureAvailable('BACKGROUND_MODE') && $this->delegate->isBackgroundMode())
537 {
538 SGBackgroundMode::next();
539 }
540 }
541
542 public function cancel()
543 {
544 @unlink($this->filePath);
545 }
546
547 private function resetBackupProgress()
548 {
549 $this->totalRowCount = 0;
550 $this->currentRowCount = 0;
551 $tableNames = $this->getTables();
552 foreach ($tableNames as $table)
553 {
554 $this->totalRowCount += $this->getTableRowsCount($table);
555 }
556 $this->nextProgressUpdate = $this->progressUpdateInterval;
557 SGBackupLog::write('Total tables to backup: '.count($tableNames));
558 SGBackupLog::write('Total rows to backup: '.$this->totalRowCount);
559 }
560
561 private function resetRestoreProgress()
562 {
563 $this->totalRowCount = $this->getFileLinesCount($this->backupFilePath);
564 $this->currentRowCount = 0;
565 $this->progressUpdateInterval = SGConfig::get('SG_ACTION_PROGRESS_UPDATE_INTERVAL');
566 $this->nextProgressUpdate = $this->progressUpdateInterval;
567 }
568
569 private function getTables()
570 {
571 $tableNames = array();
572 $tables = $this->sgdb->query('SHOW TABLES FROM `'.SG_DB_NAME.'`');
573 if (!$tables)
574 {
575 throw new SGExceptionDatabaseError('Could not get tables of database: '.SG_DB_NAME);
576 }
577 foreach ($tables as $table)
578 {
579 $tableName = $table['Tables_in_'.SG_DB_NAME];
580 $tablesToExclude = explode(',', SGConfig::get('SG_BACKUP_DATABASE_EXCLUDE'));
581 if (in_array($tableName, $tablesToExclude))
582 {
583 continue;
584 }
585 $tableNames[] = $tableName;
586 }
587 return $tableNames;
588 }
589
590 private function getTableRowsCount($tableName)
591 {
592 $count = 0;
593 $tableRowsNum = $this->sgdb->query('SELECT COUNT(*) AS total FROM '.$tableName);
594 $count = @$tableRowsNum[0]['total'];
595 return $count;
596 }
597
598 private function getFileLinesCount($filePath)
599 {
600 $fileHandle = @fopen($filePath, 'rb');
601 if (!is_resource($fileHandle))
602 {
603 throw new SGExceptionForbidden('Could not open file: '.$filePath);
604 }
605
606 $linecount = 0;
607 while (!feof($fileHandle))
608 {
609 $linecount += substr_count(fread($fileHandle, 8192), "\n");
610 }
611
612 @fclose($fileHandle);
613 return $linecount;
614 }
615
616 private function updateProgress()
617 {
618 $progress = round($this->currentRowCount*100.0/$this->totalRowCount);
619
620 if ($progress>=$this->nextProgressUpdate)
621 {
622 $this->nextProgressUpdate += $this->progressUpdateInterval;
623
624 if ($this->delegate)
625 {
626 $this->delegate->didUpdateProgress($progress);
627 }
628
629 return true;
630 }
631
632 return false;
633 }
634
635 /* Helper Functions */
636
637 private function isWritable($filePath)
638 {
639 if (!file_exists($filePath))
640 {
641 $fp = @fopen($filePath, 'wb');
642 if (!$fp)
643 {
644 throw new SGExceptionForbidden('Could not open file: '.$filePath);
645 }
646 @fclose($fp);
647 }
648 return is_writable($filePath);
649 }
650 }
651