PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.1
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.1
4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backend / Modules / Jobs / Database.php
wp-staging / Backend / Modules / Jobs Last commit date
Cleaners 1 week ago Exceptions 1 week ago Cancel.php 1 week ago CancelUpdate.php 1 week ago Cloning.php 6 days ago CloningProcess.php 1 week ago Data.php 1 week ago Database.php 6 days ago Delete.php 1 week ago Directories.php 1 week ago Files.php 1 week ago Finish.php 1 week ago Job.php 6 days ago JobExecutable.php 1 week ago Logs.php 1 week ago PreserveDataFirstStep.php 1 week ago PreserveDataSecondStep.php 1 week ago ProcessLock.php 1 week ago Scan.php 1 week ago SearchReplace.php 1 week ago TotalStepsAreNumberOfTables.php 1 week ago Updating.php 6 days ago
Database.php
536 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules\Jobs;
4
5 use stdClass;
6 use WPStaging\Backend\Modules\Jobs\Exceptions\FatalException;
7 use WPStaging\Core\Utils\Logger;
8 use WPStaging\Core\WPStaging;
9 use WPStaging\Framework\CloningProcess\CloningDto;
10 use WPStaging\Framework\CloningProcess\Database\DatabaseCloningService;
11 use WPStaging\Framework\Adapter\Database as DatabaseAdapter;
12 use WPStaging\Framework\Database\TableService;
13 use WPStaging\Framework\Traits\TablePrefixValidator;
14 use WPStaging\Framework\Facades\Hooks;
15 use WPStaging\Framework\Filesystem\Filesystem;
16 use WPStaging\Staging\Service\Database\RowsExporter;
17
18
19
20
21
22 class Database extends CloningProcess
23 {
24 use TotalStepsAreNumberOfTables;
25 use TablePrefixValidator;
26
27
28
29
30 private $databaseCloningService;
31
32
33
34
35 private $total = 0;
36
37
38
39
40
41 public function initialize()
42 {
43 $this->setupMemoryExhaustFile();
44 $this->initializeDbObjects();
45 $this->abortIfDirectoryNotEmpty();
46 $this->abortIfDirectoryNotCreated();
47 $this->abortIfPrefixContainsInvalidCharacter();
48 if (!$this->isExternalDatabase()) {
49 $this->abortIfStagingPrefixEqualsProdPrefix();
50 } else {
51 $this->abortIfExternalButNotPro();
52 }
53
54 $this->generateDto();
55 $this->addMissingTables();
56 $this->total = count($this->options->tables);
57
58 if ($this->options->mainJob === Job::RESET) {
59 $this->total++;
60 }
61 }
62
63
64
65
66 protected function generateDto()
67 {
68 $this->databaseCloningService = new DatabaseCloningService(
69 new CloningDto(
70 $this,
71 $this->stagingDb,
72 $this->productionDb,
73 $this->isExternalDatabase(),
74 $this->isMultisiteAndPro(),
75 $this->isExternalDatabase() ? $this->options->databaseServer : null,
76 $this->isExternalDatabase() ? $this->options->databaseUser : null,
77 $this->isExternalDatabase() ? $this->options->databasePassword : null,
78 $this->isExternalDatabase() ? $this->options->databaseDatabase : null,
79 $this->isExternalDatabase() ? $this->options->databaseSsl : false
80 )
81 );
82 }
83
84
85
86
87
88
89
90 protected function execute()
91 {
92
93 if ($this->isOverThreshold()) {
94
95 $this->prepareResponse(false, false);
96 $this->saveOptions();
97 return false;
98 }
99
100
101 if ($this->options->currentStep > $this->total || !$this->isRunning()) {
102 $this->prepareResponse(true, false);
103 return false;
104 }
105
106 if (!$this->deleteAllTables()) {
107
108 $this->prepareResponse(false, false);
109
110
111 return true;
112 }
113
114
115 $tableIndex = $this->options->currentStep;
116 if ($this->options->mainJob === Job::RESET) {
117 $tableIndex--;
118 }
119
120
121 if (isset($this->options->tables[$tableIndex]) && !$this->copyTable($this->options->tables[$tableIndex])) {
122
123 $this->prepareResponse(false, false);
124
125
126 return true;
127 }
128
129 $this->prepareResponse();
130
131
132 return true;
133 }
134
135
136
137
138
139
140
141 private function deleteAllTables()
142 {
143 if ($this->options->mainJob !== Job::RESET) {
144 return true;
145 }
146
147 if ($this->options->currentStep !== 0) {
148 return true;
149 }
150
151 if (!isset($this->options->databaseResettingStatus)) {
152 $this->options->databaseResettingStatus = 'pending';
153 $this->saveOptions();
154 }
155
156 if ($this->options->databaseResettingStatus === 'finished') {
157 return true;
158 }
159
160 if ($this->options->databaseResettingStatus === 'pending') {
161 $this->log('#################### Start Reset Job ####################');
162 $this->log('DB: Removing all staging database tables.');
163 $this->options->databaseResettingStatus = 'processing';
164 $this->saveOptions();
165 }
166
167
168 $tableService = new TableService(new DatabaseAdapter($this->stagingDb));
169 $tableService->setShouldStop([$this, 'isOverThreshold']);
170 $tableService->deleteTablesStartWith($this->getStagingPrefix());
171
172 foreach ($tableService->getErrors() as $error) {
173 $this->log($error, Logger::TYPE_ERROR);
174 }
175
176 if ($tableService->hasRefusedProductionTable()) {
177 return false;
178 }
179
180 $this->options->databaseResettingStatus = 'finished';
181 $this->saveOptions();
182
183 $this->prepareResponse();
184 return true;
185 }
186
187
188
189
190
191
192 private function isTableExist($name)
193 {
194 $old = $this->stagingDb->get_var($this->stagingDb->prepare("SHOW TABLES LIKE %s", $name));
195
196 return (
197 $old === $name &&
198 (
199 !isset($this->options->job->current, $this->options->job->start) || $this->options->job->start === 0
200 )
201 );
202 }
203
204
205
206
207
208
209 private function shouldAbortIfTableExist($name)
210 {
211 return isset($this->options->mainJob) && $this->options->mainJob !== Job::UPDATE && $this->isTableExist($name);
212 }
213
214
215
216
217 private function finishStep()
218 {
219
220 if ($this->options->job->total > $this->options->job->start) {
221 return false;
222 }
223
224 $this->finishDataCopying();
225
226 return true;
227 }
228
229
230
231
232 private function finishDataCopying()
233 {
234
235 $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
236
237
238 $this->options->job = new stdClass();
239 }
240
241
242
243
244
245 private function abortIfExternalButNotPro()
246 {
247 if (defined('WPSTGPRO_VERSION')) {
248 return false;
249 }
250
251 $this->returnException(__("This staging site is located in another database and needs to be edited with <a href='https://wp-staging.com' target='_blank'>WP STAGING Pro</a>", "wp-staging"));
252
253 return true;
254 }
255
256
257
258
259
260 private function setJob($table)
261 {
262 if (isset($this->options->job->current)) {
263 return;
264 }
265
266
267 if (!is_object($this->options->job)) {
268 $this->options->job = new stdClass();
269 }
270
271 $this->options->job->current = $table;
272 $this->options->job->start = 0;
273 }
274
275
276
277
278
279
280 private function copyTable($srcTableName)
281 {
282 $srcTableName = is_object($srcTableName) ? $srcTableName->name : $srcTableName;
283
284 $tableWithoutPrefix = $this->databaseCloningService->removeDBPrefix($srcTableName);
285 $destTableName = $this->getStagingPrefix() . $tableWithoutPrefix;
286
287 if ($this->isMultisiteAndPro()) {
288
289 if ($tableWithoutPrefix === 'users') {
290 $srcTableName = $this->productionDb->base_prefix . 'users';
291 }
292
293
294 if ($tableWithoutPrefix === 'usermeta') {
295 $srcTableName = $this->productionDb->base_prefix . 'usermeta';
296 }
297 }
298
299 if (!$this->isCopyProcessStarted() && $this->shouldAbortIfTableExist($destTableName)) {
300 $this->returnException(sprintf(__("Can not proceed. Tables beginning with the prefix '%s' already exist in the database i.e. %s. Choose another table prefix and try again.", "wp-staging"), $this->getStagingPrefix(), $destTableName));
301 return true;
302 }
303
304 $this->setJob($destTableName);
305
306 if (!$this->startJob($destTableName, $srcTableName)) {
307 return true;
308 }
309
310 $tablesToExcludeData = Hooks::applyFilters(RowsExporter::FILTER_EXCLUDE_TABLES_DATA, RowsExporter::TABLES_EXCLUDED_FROM_DATA_COPYING);
311 if (in_array($tableWithoutPrefix, $tablesToExcludeData)) {
312 $this->log("Skipping data copy for table {$srcTableName}", Logger::TYPE_INFO);
313 $this->finishDataCopying();
314 return true;
315 }
316
317 $this->copyData($destTableName, $srcTableName);
318
319 return $this->finishStep();
320 }
321
322
323
324
325
326
327 private function copyData($destTableName, $srcTableName)
328 {
329 try {
330 $this->databaseCloningService->copyData($srcTableName, $destTableName, $this->options->job->start, $this->settings->queryLimit);
331 } catch (FatalException $e) {
332 $this->returnException($e->getMessage());
333 return;
334 }
335
336
337 $this->options->job->start += $this->settings->queryLimit;
338 }
339
340
341
342
343
344
345 private function isExcludedTable(string $table)
346 {
347
348 if (
349 in_array(
350 $table,
351 array_map(
352 function ($tableName) {
353 return $this->options->prefix . $tableName;
354 },
355 $this->excludedTableService->getExcludedTables($this->isNetworkClone())
356 )
357 )
358 ) {
359 return true;
360 }
361
362 return false;
363 }
364
365
366
367
368
369
370
371
372 private function startJob($destinationTable, $sourceTable)
373 {
374 if ($this->isExcludedTable($destinationTable)) {
375 return false;
376 }
377
378 if ($this->options->job->start !== 0) {
379 return true;
380 }
381
382 if ($this->databaseCloningService->isMissingTable($sourceTable)) {
383 return true;
384 }
385
386 try {
387 $this->options->job->total = 0;
388 $this->options->job->total = $this->databaseCloningService->createTable($sourceTable, $destinationTable);
389 } catch (FatalException $e) {
390 $this->log($e->getMessage(), Logger::TYPE_WARNING);
391 $this->log(__('Skipping cloning table: ' . $sourceTable, 'wp-staging'), Logger::TYPE_WARNING);
392 $this->finishStep();
393 return false;
394 }
395
396 if ($this->options->job->total === 0) {
397 $this->finishStep();
398 return false;
399 }
400
401 $this->options->job->copyProcessStarted = true;
402 $this->saveOptions();
403 return true;
404 }
405
406
407
408
409
410
411
412
413 private function addMissingTables()
414 {
415 $dbPrefix = WPStaging::getTablePrefix();
416
417 if (isset($this->options->mainJob) && $this->options->mainJob === Job::UPDATE) {
418 return;
419 }
420
421 if (!in_array($dbPrefix . 'users', $this->options->tables)) {
422 $this->options->tables[] = $dbPrefix . 'users';
423 $this->saveOptions();
424 }
425
426 if (!in_array($dbPrefix . 'usermeta', $this->options->tables)) {
427 $this->options->tables[] = $dbPrefix . 'usermeta';
428 $this->saveOptions();
429 }
430 }
431
432
433
434
435 private function abortIfStagingPrefixEqualsProdPrefix()
436 {
437 $dbPrefix = WPStaging::getTablePrefix();
438 if ($dbPrefix === $this->getStagingPrefix()) {
439 $error = 'Fatal error 7: The destination database table prefix ' . $this->getStagingPrefix() . ' is identical to the table prefix of the production site. Go to Sites > Actions > Edit Data and correct the table prefix or contact us.';
440 $this->returnException($error);
441 return true;
442 }
443
444 return false;
445 }
446
447
448
449
450
451 protected function getStagingPrefix()
452 {
453 if ($this->isExternalDatabase()) {
454 $this->options->prefix = !empty($this->options->databasePrefix) ? $this->options->databasePrefix : $this->productionDb->prefix;
455 }
456
457 if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
458 return strtolower($this->options->prefix);
459 }
460
461 return $this->options->prefix;
462 }
463
464
465
466
467
468
469 private function abortIfDirectoryNotEmpty()
470 {
471 $path = trailingslashit($this->options->cloneDir);
472 if (isset($this->options->mainJob) && $this->options->mainJob !== Job::RESET && $this->options->mainJob !== Job::UPDATE && is_dir($path) && !wpstg_is_empty_dir($path)) {
473 $this->returnException(" Can not continue for security purposes. Directory {$path} is not empty! Use FTP or a file manager plugin and make sure it does not contain any files. ");
474 return true;
475 }
476
477 return false;
478 }
479
480
481
482
483
484 private function abortIfDirectoryNotCreated()
485 {
486
487 if (isset($this->options->mainJob) && ($this->isUpdateOrResetJob())) {
488 return false;
489 }
490
491
492 $path = trailingslashit($this->options->cloneDir);
493 if (is_dir($path)) {
494 return false;
495 }
496
497 $fs = new Filesystem();
498 if ($fs->mkdir($path)) {
499 return false;
500 }
501
502 $this->returnException(" Unable to create the staging site directory $path " . $fs->getLogs()[0]);
503 return true;
504 }
505
506
507
508
509
510 private function abortIfPrefixContainsInvalidCharacter()
511 {
512
513
514 if (preg_match('|[^a-z0-9_]|i', $this->options->databasePrefix)) {
515 $this->returnException(__("Table prefix contains invalid character(s). Use different prefix with valid characters.", 'wp-staging'));
516 return true;
517 }
518
519 if ($this->isWpStagingReservedPrefix($this->options->databasePrefix)) {
520 $this->returnException($this->getReservedPrefixErrorMessage($this->options->databasePrefix));
521 return true;
522 }
523
524 return false;
525 }
526
527
528
529
530
531 private function isCopyProcessStarted()
532 {
533 return isset($this->options->job->copyProcessStarted) && $this->options->job->copyProcessStarted === true;
534 }
535 }
536