PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
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 day ago Exceptions 1 day ago Cancel.php 1 day ago CancelUpdate.php 1 day ago Cloning.php 1 day ago CloningProcess.php 1 day ago Data.php 1 day ago Database.php 1 day ago Delete.php 1 day ago Directories.php 1 day ago Files.php 1 day ago Finish.php 1 day ago Job.php 1 day ago JobExecutable.php 1 day ago Logs.php 1 day ago PreserveDataFirstStep.php 1 day ago PreserveDataSecondStep.php 1 day ago ProcessLock.php 1 day ago Scan.php 1 day ago SearchReplace.php 1 day ago TotalStepsAreNumberOfTables.php 1 day ago Updating.php 1 day ago
Database.php
530 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 if (!$tableService->deleteTablesStartWith($this->getStagingPrefix())) {
171 return false;
172 }
173
174 $this->options->databaseResettingStatus = 'finished';
175 $this->saveOptions();
176
177 $this->prepareResponse();
178 return true;
179 }
180
181
182
183
184
185
186 private function isTableExist($name)
187 {
188 $old = $this->stagingDb->get_var($this->stagingDb->prepare("SHOW TABLES LIKE %s", $name));
189
190 return (
191 $old === $name &&
192 (
193 !isset($this->options->job->current, $this->options->job->start) || $this->options->job->start === 0
194 )
195 );
196 }
197
198
199
200
201
202
203 private function shouldAbortIfTableExist($name)
204 {
205 return isset($this->options->mainJob) && $this->options->mainJob !== Job::UPDATE && $this->isTableExist($name);
206 }
207
208
209
210
211 private function finishStep()
212 {
213
214 if ($this->options->job->total > $this->options->job->start) {
215 return false;
216 }
217
218 $this->finishDataCopying();
219
220 return true;
221 }
222
223
224
225
226 private function finishDataCopying()
227 {
228
229 $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
230
231
232 $this->options->job = new stdClass();
233 }
234
235
236
237
238
239 private function abortIfExternalButNotPro()
240 {
241 if (defined('WPSTGPRO_VERSION')) {
242 return false;
243 }
244
245 $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"));
246
247 return true;
248 }
249
250
251
252
253
254 private function setJob($table)
255 {
256 if (isset($this->options->job->current)) {
257 return;
258 }
259
260
261 if (!is_object($this->options->job)) {
262 $this->options->job = new stdClass();
263 }
264
265 $this->options->job->current = $table;
266 $this->options->job->start = 0;
267 }
268
269
270
271
272
273
274 private function copyTable($srcTableName)
275 {
276 $srcTableName = is_object($srcTableName) ? $srcTableName->name : $srcTableName;
277
278 $tableWithoutPrefix = $this->databaseCloningService->removeDBPrefix($srcTableName);
279 $destTableName = $this->getStagingPrefix() . $tableWithoutPrefix;
280
281 if ($this->isMultisiteAndPro()) {
282
283 if ($tableWithoutPrefix === 'users') {
284 $srcTableName = $this->productionDb->base_prefix . 'users';
285 }
286
287
288 if ($tableWithoutPrefix === 'usermeta') {
289 $srcTableName = $this->productionDb->base_prefix . 'usermeta';
290 }
291 }
292
293 if (!$this->isCopyProcessStarted() && $this->shouldAbortIfTableExist($destTableName)) {
294 $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));
295 return true;
296 }
297
298 $this->setJob($destTableName);
299
300 if (!$this->startJob($destTableName, $srcTableName)) {
301 return true;
302 }
303
304 $tablesToExcludeData = Hooks::applyFilters(RowsExporter::FILTER_EXCLUDE_TABLES_DATA, RowsExporter::TABLES_EXCLUDED_FROM_DATA_COPYING);
305 if (in_array($tableWithoutPrefix, $tablesToExcludeData)) {
306 $this->log("Skipping data copy for table {$srcTableName}", Logger::TYPE_INFO);
307 $this->finishDataCopying();
308 return true;
309 }
310
311 $this->copyData($destTableName, $srcTableName);
312
313 return $this->finishStep();
314 }
315
316
317
318
319
320
321 private function copyData($destTableName, $srcTableName)
322 {
323 try {
324 $this->databaseCloningService->copyData($srcTableName, $destTableName, $this->options->job->start, $this->settings->queryLimit);
325 } catch (FatalException $e) {
326 $this->returnException($e->getMessage());
327 return;
328 }
329
330
331 $this->options->job->start += $this->settings->queryLimit;
332 }
333
334
335
336
337
338
339 private function isExcludedTable(string $table)
340 {
341
342 if (
343 in_array(
344 $table,
345 array_map(
346 function ($tableName) {
347 return $this->options->prefix . $tableName;
348 },
349 $this->excludedTableService->getExcludedTables($this->isNetworkClone())
350 )
351 )
352 ) {
353 return true;
354 }
355
356 return false;
357 }
358
359
360
361
362
363
364
365
366 private function startJob($destinationTable, $sourceTable)
367 {
368 if ($this->isExcludedTable($destinationTable)) {
369 return false;
370 }
371
372 if ($this->options->job->start !== 0) {
373 return true;
374 }
375
376 if ($this->databaseCloningService->isMissingTable($sourceTable)) {
377 return true;
378 }
379
380 try {
381 $this->options->job->total = 0;
382 $this->options->job->total = $this->databaseCloningService->createTable($sourceTable, $destinationTable);
383 } catch (FatalException $e) {
384 $this->log($e->getMessage(), Logger::TYPE_WARNING);
385 $this->log(__('Skipping cloning table: ' . $sourceTable, 'wp-staging'), Logger::TYPE_WARNING);
386 $this->finishStep();
387 return false;
388 }
389
390 if ($this->options->job->total === 0) {
391 $this->finishStep();
392 return false;
393 }
394
395 $this->options->job->copyProcessStarted = true;
396 $this->saveOptions();
397 return true;
398 }
399
400
401
402
403
404
405
406
407 private function addMissingTables()
408 {
409 $dbPrefix = WPStaging::getTablePrefix();
410
411 if (isset($this->options->mainJob) && $this->options->mainJob === Job::UPDATE) {
412 return;
413 }
414
415 if (!in_array($dbPrefix . 'users', $this->options->tables)) {
416 $this->options->tables[] = $dbPrefix . 'users';
417 $this->saveOptions();
418 }
419
420 if (!in_array($dbPrefix . 'usermeta', $this->options->tables)) {
421 $this->options->tables[] = $dbPrefix . 'usermeta';
422 $this->saveOptions();
423 }
424 }
425
426
427
428
429 private function abortIfStagingPrefixEqualsProdPrefix()
430 {
431 $dbPrefix = WPStaging::getTablePrefix();
432 if ($dbPrefix === $this->getStagingPrefix()) {
433 $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.';
434 $this->returnException($error);
435 return true;
436 }
437
438 return false;
439 }
440
441
442
443
444
445 protected function getStagingPrefix()
446 {
447 if ($this->isExternalDatabase()) {
448 $this->options->prefix = !empty($this->options->databasePrefix) ? $this->options->databasePrefix : $this->productionDb->prefix;
449 }
450
451 if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
452 return strtolower($this->options->prefix);
453 }
454
455 return $this->options->prefix;
456 }
457
458
459
460
461
462
463 private function abortIfDirectoryNotEmpty()
464 {
465 $path = trailingslashit($this->options->cloneDir);
466 if (isset($this->options->mainJob) && $this->options->mainJob !== Job::RESET && $this->options->mainJob !== Job::UPDATE && is_dir($path) && !wpstg_is_empty_dir($path)) {
467 $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. ");
468 return true;
469 }
470
471 return false;
472 }
473
474
475
476
477
478 private function abortIfDirectoryNotCreated()
479 {
480
481 if (isset($this->options->mainJob) && ($this->isUpdateOrResetJob())) {
482 return false;
483 }
484
485
486 $path = trailingslashit($this->options->cloneDir);
487 if (is_dir($path)) {
488 return false;
489 }
490
491 $fs = new Filesystem();
492 if ($fs->mkdir($path)) {
493 return false;
494 }
495
496 $this->returnException(" Unable to create the staging site directory $path " . $fs->getLogs()[0]);
497 return true;
498 }
499
500
501
502
503
504 private function abortIfPrefixContainsInvalidCharacter()
505 {
506
507
508 if (preg_match('|[^a-z0-9_]|i', $this->options->databasePrefix)) {
509 $this->returnException(__("Table prefix contains invalid character(s). Use different prefix with valid characters.", 'wp-staging'));
510 return true;
511 }
512
513 if ($this->isWpStagingReservedPrefix($this->options->databasePrefix)) {
514 $this->returnException($this->getReservedPrefixErrorMessage($this->options->databasePrefix));
515 return true;
516 }
517
518 return false;
519 }
520
521
522
523
524
525 private function isCopyProcessStarted()
526 {
527 return isset($this->options->job->copyProcessStarted) && $this->options->job->copyProcessStarted === true;
528 }
529 }
530