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 / Scan.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
Scan.php
684 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules\Jobs;
4
5 use DirectoryIterator;
6 use Exception;
7 use UnexpectedValueException;
8 use WPStaging\Backend\Optimizer\Optimizer;
9 use WPStaging\Core\WPStaging;
10 use WPStaging\Framework\Adapter\Directory;
11 use WPStaging\Staging\Sites;
12 use WPStaging\Framework\Utils\Sanitize;
13 use WPStaging\Framework\Utils\Strings;
14 use WPStaging\Framework\Filesystem\PathChecker;
15 use WPStaging\Framework\SiteInfo;
16 use WPStaging\Framework\TemplateEngine\TemplateEngine;
17 use WPStaging\Framework\Filesystem\PathIdentifier;
18
19
20
21
22 class Scan extends Job
23 {
24
25
26
27
28
29
30 const WP_CORE_DIR = "wpstg-wp-core-dir";
31
32
33
34
35
36
37
38 const WP_NON_CORE_DIR = "wpstg-wp-non-core-dir";
39
40
41 private $directories = [];
42
43
44 private $directoryToScanOnly;
45
46
47
48
49 private $gifLoaderPath;
50
51
52
53
54 private $strUtils;
55
56
57
58
59 protected $dirAdapter;
60
61
62
63
64 private $sanitize;
65
66
67
68
69 private $infoIconPath;
70
71
72
73
74 private $basePath;
75
76
77
78
79 private $pathIdentifier;
80
81
82
83
84 private $templateEngine;
85
86
87 private $pathAdapter;
88
89
90 private $pathChecker;
91
92
93 protected $absPath = ABSPATH;
94
95
96 protected $wpContentPath = WP_CONTENT_DIR;
97
98
99
100
101
102 public function __construct($directoryToScanOnly = null)
103 {
104
105
106 $this->directoryToScanOnly = null;
107 if ($directoryToScanOnly !== null) {
108 $this->directoryToScanOnly = $directoryToScanOnly;
109 }
110
111
112 $this->strUtils = new Strings();
113 $this->pathAdapter = WPStaging::make(PathIdentifier::class);
114 $this->pathChecker = WPStaging::make(PathChecker::class);
115 $this->dirAdapter = WPStaging::make(Directory::class);
116 $this->sanitize = WPStaging::make(Sanitize::class);
117 $this->templateEngine = WPStaging::make(TemplateEngine::class);
118 parent::__construct();
119 }
120
121
122
123
124 public function setGifLoaderPath(string $gifLoaderPath)
125 {
126 $this->gifLoaderPath = $gifLoaderPath;
127 }
128
129
130
131
132 public function setInfoIcon(string $infoIconPath)
133 {
134 $this->infoIconPath = $infoIconPath;
135 }
136
137
138
139
140
141
142 public function getInfoIcon(): string
143 {
144 return $this->infoIconPath;
145 }
146
147
148
149
150 public function setDirectoryToScanOnly(string $directoryToScanOnly)
151 {
152 $this->directoryToScanOnly = $directoryToScanOnly;
153 }
154
155
156
157
158
159 public function setBasePath($basePath)
160 {
161 $this->basePath = rtrim(wp_normalize_path($basePath), '/');
162 }
163
164
165 public function getBasePath(): string
166 {
167 return $this->basePath;
168 }
169
170
171 public function setPathIdentifier(string $pathIdentifier)
172 {
173 $this->pathIdentifier = $pathIdentifier;
174 }
175
176
177 public function getPathIdentifier(): string
178 {
179 return $this->pathIdentifier;
180 }
181
182
183
184
185 public function initialize()
186 {
187 $this->options->existingClones = get_option(Sites::STAGING_SITES_OPTION, []);
188 $this->options->existingClones = is_array($this->options->existingClones) ? $this->options->existingClones : [];
189
190 $this->directories = [];
191 if (!empty($this->directoryToScanOnly)) {
192 return;
193 }
194
195 $this->getTables();
196
197 $this->setBasePath($this->absPath);
198 $this->setPathIdentifier(PathIdentifier::IDENTIFIER_ABSPATH);
199 $this->getDirectories($this->absPath);
200
201
202 if ($this->isWpContentOutsideAbspath()) {
203 $this->setBasePath($this->wpContentPath);
204 $this->setPathIdentifier(PathIdentifier::IDENTIFIER_WP_CONTENT);
205 $this->getDirectories(dirname($this->wpContentPath));
206 }
207
208 $this->installOptimizer();
209 }
210
211
212
213
214
215
216 public function start()
217 {
218
219 $this->options->root = str_replace(["\\", '/'], DIRECTORY_SEPARATOR, ABSPATH);
220 $this->options->current = null;
221 $this->options->currentClone = $this->getCurrentClone();
222
223 if ($this->options->currentClone !== null) {
224
225 $this->options->currentClone['excludeSizeRules'] = $this->options->currentClone['excludeSizeRules'] ?? [];
226 $this->options->currentClone['excludeGlobRules'] = $this->options->currentClone['excludeGlobRules'] ?? [];
227
228 $this->options->currentClone['useNewAdminAccount'] = $this->options->currentClone['useNewAdminAccount'] ?? false;
229 $this->options->currentClone['adminEmail'] = $this->options->currentClone['adminEmail'] ?? '';
230 $this->options->currentClone['adminPassword'] = $this->options->currentClone['adminPassword'] ?? '';
231
232 $this->options->currentClone['isEmailsAllowed'] = $this->options->currentClone['isEmailsAllowed'] ?? true;
233 $this->options->currentClone['databaseSsl'] = $this->options->currentClone['databaseSsl'] ?? false;
234 $this->options->currentClone['uploadsSymlinked'] = $this->options->currentClone['uploadsSymlinked'] ?? false;
235 $this->options->currentClone['networkClone'] = $this->options->currentClone['networkClone'] ?? false;
236 $this->options->currentClone['isWooSchedulerEnabled'] = empty($this->options->currentClone['isWooSchedulerEnabled']) ? false : true;
237 $this->options->currentClone['isEmailsReminderEnabled'] = empty($this->options->currentClone['isEmailsReminderEnabled']) ? false : true;
238 $this->options->currentClone['isAutoUpdatePlugins'] = empty($this->options->currentClone['isAutoUpdatePlugins']) ? false : true;
239 }
240
241
242 $this->options->clonedTables = [];
243
244
245 $this->options->totalFiles = 0;
246 $this->options->totalFileSize = 0;
247 $this->options->copiedFiles = 0;
248
249
250
251 $this->options->includedDirectories = [];
252 $this->options->includedExtraDirectories = [];
253 $this->options->excludedDirectories = [];
254 $this->options->extraDirectories = [];
255 $this->options->scannedDirectories = [];
256
257
258 $this->options->currentJob = "PreserveDataFirstStep";
259 $this->options->currentStep = 0;
260 $this->options->totalSteps = 0;
261
262
263 $this->options->mainJob = Job::STAGING;
264 $job = '';
265 if (isset($_POST["job"])) {
266 $job = $this->sanitize->sanitizeString($_POST['job']);
267 }
268
269 if ($this->options->current !== null && $job === 'resetting') {
270 $this->options->mainJob = Job::RESET;
271 } elseif ($this->options->current !== null) {
272 $this->options->mainJob = Job::UPDATE;
273 }
274
275
276 $this->cloneOptionCache->delete();
277 $this->filesIndexCache->delete();
278
279 $this->saveOptions();
280
281 return $this;
282 }
283
284
285
286
287 private function installOptimizer()
288 {
289 $optimizer = new Optimizer();
290 $optimizer->installOptimizer();
291 }
292
293
294
295
296
297
298
299
300
301
302 public function directoryListing($parentChecked = null, $forceDefault = false, $directories = null): string
303 {
304 if ($directories === null) {
305 $directories = $this->directories;
306 }
307
308 uksort($directories, 'strcasecmp');
309
310 $excludedDirectories = [];
311 $extraDirectories = [];
312
313 if ($this->isUpdateOrResetJob()) {
314 $currentClone = json_decode(json_encode($this->options->currentClone));
315 $extraDirectories = isset($currentClone->extraDirectories) ? $currentClone->extraDirectories : [];
316 $excludedDirectories = isset($currentClone->excludedDirectories) ? array_map(function ($directory) {
317
318 try {
319 return $this->pathAdapter->transformIdentifiableToPath($directory);
320 } catch (UnexpectedValueException $ex) {
321 return $directory;
322 }
323 }, $currentClone->excludedDirectories) : [];
324 }
325
326 $output = '';
327 foreach ($directories as $dirName => $directory) {
328
329 if (!is_array($directory) || basename($dirName) === "\\") {
330 continue;
331 }
332
333
334 $data = reset($directory);
335 unset($directory[key($directory)]);
336
337 $output .= $this->getDirectoryHtml($data['dirName'], $data, $excludedDirectories, $extraDirectories, $parentChecked, $forceDefault);
338 }
339
340 return $output;
341 }
342
343
344
345
346 protected function getTables()
347 {
348 $db = WPStaging::getInstance()->get("wpdb");
349 $dbPrefix = WPStaging::getTablePrefix();
350
351 $sql = "SHOW TABLE STATUS";
352
353 $tables = $db->get_results($sql);
354
355 $currentTables = [];
356
357 $currentClone = $this->getCurrentClone();
358 $networkClone = is_multisite() && is_main_site() && is_array($currentClone) && (array_key_exists('networkClone', $currentClone) ? $this->sanitize->sanitizeBool($currentClone['networkClone']) : false);
359
360
361 $this->options->excludedTables = [];
362 foreach ($tables as $table) {
363
364
365
366 if (
367 ( ! empty($dbPrefix) && strpos($table->Name, $dbPrefix) !== 0)
368 || (is_multisite() && is_main_site() && !$networkClone && preg_match('/^' . $dbPrefix . '\d+_/', $table->Name))
369 ) {
370 $this->options->excludedTables[] = $table->Name;
371 }
372
373 if ($table->Comment !== "VIEW") {
374 $currentTables[] = [
375 "name" => $table->Name,
376 "size" => ($table->Data_length + $table->Index_length),
377 ];
378 }
379 }
380
381 $this->options->tables = json_decode(json_encode($currentTables));
382 }
383
384
385
386
387
388
389
390
391 public function getDirectories(string $dirPath = ABSPATH, bool $shouldReturn = false)
392 {
393 if (!is_dir($dirPath)) {
394 return;
395 }
396
397 try {
398 $directories = new DirectoryIterator($dirPath);
399 } catch (UnexpectedValueException $ex) {
400 $errorMessage = $ex->getMessage();
401 if ($ex->getCode() === 5) {
402 $errorMessage = esc_html__('Access Denied: No read permission to scan the root directory for cloning. Alternatively you can try the WP STAGING backup feature!', 'wp-staging');
403 }
404
405 echo json_encode([
406 'success' => false,
407 'type' => '',
408
409 'swalOptions' => [
410 'title' => esc_html__('Error!', 'wp-staging'),
411 'html' => $errorMessage,
412 'cancelButtonText' => esc_html__('Ok', 'wp-staging'),
413 'showCancelButton' => true,
414 'showConfirmButton' => false,
415 ],
416 ]);
417
418 exit();
419 }
420
421 $result = [];
422
423 foreach ($directories as $directory) {
424 if ($directory->isDot() || $directory->isFile()) {
425 continue;
426 }
427
428 $fullPath = $this->resolveDirectoryPath($directory);
429 if (empty($fullPath) || !is_dir($fullPath)) {
430 continue;
431 }
432
433
434 $result[$directory->getFilename()]['metaData'] = [
435 'dirName' => $directory->getFilename(),
436 "path" => $fullPath,
437 "basePath" => $this->getBasePath(),
438 "prefix" => $this->getPathIdentifier(),
439 "isLink" => is_link($directory->getPathname()),
440 ];
441 }
442
443 if ($shouldReturn) {
444 return $result;
445 }
446
447 $this->directories = array_merge($this->directories, $result);
448 }
449
450
451
452
453
454
455 protected function getPath($directory)
456 {
457 $basePath = $this->getBasePath();
458 $realPath = WPStaging::make('WPSTG_ALLOW_VFS') === true && strpos($directory->getPathname(), 'vfs://') === 0 ? $directory->getPathname() : $directory->getRealPath();
459 $realPath = wp_normalize_path($realPath);
460
461
462
463
464
465
466 if (strpos($realPath, $basePath) !== 0) {
467 return false;
468 }
469
470 $path = str_replace($basePath, '', $realPath);
471
472 if (!$directory->isDir() || (strlen($path) < 1 && $this->pathIdentifier !== PathIdentifier::IDENTIFIER_WP_CONTENT)) {
473 return false;
474 }
475
476 return $path;
477 }
478
479
480
481
482
483
484
485
486
487
488 protected function getDirectoryHtml($dirName, $dirInfo, $excludedDirectories, $extraDirectories, $parentChecked = false, $forceDefault = false)
489 {
490 $data = $dirInfo;
491 $dataPath = isset($data["path"]) ? $data["path"] : '';
492 $path = wp_normalize_path($dataPath);
493 $basePath = isset($data["basePath"]) ? $data["basePath"] : wp_normalize_path($this->absPath);
494 $prefix = isset($data["prefix"]) ? $data["prefix"] : PathIdentifier::IDENTIFIER_ABSPATH;
495 $relPath = str_replace($basePath, '', $path);
496 $relPath = ltrim($relPath, '/');
497
498
499 $isNotWPCoreDir = $this->isNonWpCoreDirectory($dirName, $path);
500
501 $class = $isNotWPCoreDir ? self::WP_NON_CORE_DIR : self::WP_CORE_DIR;
502 $dirType = 'other';
503
504 if ($this->strUtils->startsWith($path, $this->dirAdapter->getPluginsDirectory()) !== false) {
505 $pluginPath = $this->strUtils->strReplaceFirst($this->dirAdapter->getPluginsDirectory(), '', $path);
506 $dirType = strpos($pluginPath, '/') === false ? 'plugin' : 'other';
507 } elseif ($this->strUtils->startsWith($path, $this->dirAdapter->getActiveThemeParentDirectory()) !== false) {
508 $themePath = $this->strUtils->strReplaceFirst($this->dirAdapter->getActiveThemeParentDirectory(), '', $path);
509 $dirType = strpos($themePath, '/') === false ? 'theme' : 'other';
510 }
511
512 $isScanned = 'false';
513 if (
514 trailingslashit($path) === $this->dirAdapter->getWpContentDirectory()
515 || trailingslashit($path) === $this->dirAdapter->getPluginsDirectory()
516 || trailingslashit($path) === $this->dirAdapter->getActiveThemeParentDirectory()
517 ) {
518 $isScanned = 'true';
519 }
520
521
522 $isNavigatable = 'true';
523 if ($this->strUtils->startsWith($path, $basePath . "/wp-admin") !== false || $this->strUtils->startsWith($path, $basePath . "/wp-includes") !== false) {
524 $isNavigatable = 'false';
525 }
526
527
528 $shouldBeChecked = $parentChecked !== null ? $parentChecked : !$isNotWPCoreDir;
529 if (!$forceDefault && $this->isUpdateOrResetJob() && (!$this->isPathInDirectories($path, $excludedDirectories, $basePath))) {
530 $shouldBeChecked = true;
531 } elseif (!$forceDefault && $this->isUpdateOrResetJob()) {
532 $shouldBeChecked = false;
533 }
534
535 if (!$forceDefault && $this->isUpdateOrResetJob() && $class === self::WP_NON_CORE_DIR && !$this->isPathInDirectories($path, $extraDirectories)) {
536 $shouldBeChecked = false;
537 }
538
539 $isDisabledDir = $dirName === 'wp-admin' || $dirName === 'wp-includes';
540
541 $isDisabled = false;
542 if (strpos($dataPath, 'wp-content/' . Directory::STAGING_SITE_DIRECTORY) !== false) {
543 $isDisabled = true;
544 $shouldBeChecked = false;
545 }
546
547 $isLink = false;
548 if (strpos(trailingslashit($basePath) . $dirName, 'wp-content') !== false && is_link(trailingslashit($basePath) . $dirName)) {
549 $isDisabled = true;
550 $isNavigatable = 'false';
551 $shouldBeChecked = true;
552 $isLink = true;
553 $relPath = 'wp-content';
554 }
555
556 return $this->templateEngine->render('clone/ajax/directory-navigation.php', [
557 'scan' => $this,
558 'prefix' => $prefix,
559 'relPath' => $relPath,
560 'class' => $class,
561 'dirType' => $dirType,
562 'isScanned' => $isScanned,
563 'isNavigatable' => $isNavigatable,
564 'shouldBeChecked' => $shouldBeChecked,
565 'parentChecked' => $parentChecked,
566 'directoryDisabled' => $isNotWPCoreDir || $isDisabledDir,
567 'isDisabled' => $isDisabled,
568 'dirName' => $dirName,
569 'gifLoaderPath' => $this->gifLoaderPath,
570 'isDebugMode' => false,
571 'dataPath' => $dataPath,
572 'basePath' => $basePath,
573 'forceDefault' => $forceDefault,
574 'dirPath' => $path,
575 'isLink' => $isLink,
576 ]);
577 }
578
579
580
581
582
583
584
585
586
587 protected function isPathInDirectories(string $path, array $directories, $basePath = null): bool
588 {
589 return $this->pathChecker->isPathInPathsList($path, $directories, true, $basePath);
590 }
591
592
593
594
595
596
597
598 protected function getCurrentClone()
599 {
600 $cloneID = isset($_POST["clone"]) ? $this->sanitize->sanitizeString($_POST['clone']) : '';
601
602 if (array_key_exists($cloneID, $this->options->existingClones)) {
603 $this->options->current = $cloneID;
604 return $this->options->existingClones[$this->options->current];
605 }
606
607 return null;
608 }
609
610
611
612
613 protected function isWpContentOutsideAbspath()
614 {
615
616 $siteInfo = WPStaging::make(SiteInfo::class);
617 return $siteInfo->isWpContentOutsideAbspath();
618 }
619
620
621
622
623
624
625
626
627 protected function isNonWpCoreDirectory($dirname, $path)
628 {
629 $coreDirectories = [
630 'wp-admin',
631 'wp-content',
632 'wp-includes',
633 ];
634
635 if (in_array($dirname, $coreDirectories)) {
636 return false;
637 }
638
639 $wpDirectories = [
640 $this->dirAdapter->getWpContentDirectory(),
641 $this->dirAdapter->getPluginsDirectory(),
642 $this->dirAdapter->getActiveThemeParentDirectory(),
643 $this->dirAdapter->getUploadsDirectory(),
644 $this->dirAdapter->getMuPluginsDirectory(),
645 ];
646
647 foreach ($wpDirectories as $wpDirectory) {
648 if (strpos(trailingslashit($path), $wpDirectory) !== false) {
649 return false;
650 }
651 }
652
653 return true;
654 }
655
656
657
658
659
660
661 private function resolveDirectoryPath(DirectoryIterator $directory): string
662 {
663 if (!is_link($directory->getPathname())) {
664 $path = $this->getPath($directory);
665 if ($path === false) {
666 return '';
667 }
668
669 return trailingslashit($this->getBasePath()) . ltrim($path, '/');
670 }
671
672 $targetPath = readlink($directory->getPathname());
673 if ($targetPath === false) {
674 return '';
675 }
676
677 if (!path_is_absolute($targetPath)) {
678 $targetPath = dirname($directory->getPathname()) . '/' . $targetPath;
679 }
680
681 return wp_normalize_path(realpath($targetPath));
682 }
683 }
684