PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.8.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.8.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 5 years ago Exceptions 5 years ago Cancel.php 2 years ago CancelUpdate.php 2 years ago Cloning.php 2 years ago CloningProcess.php 2 years ago Data.php 2 years ago Database.php 2 years ago Delete.php 2 years ago Directories.php 2 years ago Files.php 2 years ago Finish.php 1 year ago Job.php 2 years ago JobExecutable.php 2 years ago Logs.php 3 years ago PreserveDataFirstStep.php 3 years ago PreserveDataSecondStep.php 2 years ago ProcessLock.php 2 years ago Scan.php 2 years ago SearchReplace.php 2 years ago TotalStepsAreNumberOfTables.php 5 years ago Updating.php 2 years ago
Scan.php
788 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules\Jobs;
4
5 use DirectoryIterator;
6 use Exception;
7 use RuntimeException;
8 use UnexpectedValueException;
9 use WPStaging\Backend\Optimizer\Optimizer;
10 use WPStaging\Core\Utils\Directories as DirectoriesUtil;
11 use WPStaging\Core\WPStaging;
12 use WPStaging\Framework\Adapter\Directory;
13 use WPStaging\Framework\Filesystem\DiskWriteCheck;
14 use WPStaging\Framework\Filesystem\Scanning\ScanConst;
15 use WPStaging\Framework\Staging\Sites;
16 use WPStaging\Framework\Utils\Sanitize;
17 use WPStaging\Framework\Utils\Strings;
18 use WPStaging\Framework\Utils\WpDefaultDirectories;
19 use WPStaging\Backup\Exceptions\DiskNotWritableException;
20 use WPStaging\Framework\Filesystem\PathChecker;
21 use WPStaging\Framework\SiteInfo;
22 use WPStaging\Framework\TemplateEngine\TemplateEngine;
23 use WPStaging\Framework\Filesystem\PathIdentifier;
24
25 /**
26 * Class Scan
27 * @package WPStaging\Backend\Modules\Jobs
28 */
29 class Scan extends Job
30 {
31 /**
32 * CSS class name to use for WordPress core directories like wp-content, wp-admin, wp-includes
33 * This doesn't contain class selector prefix
34 *
35 * @var string
36 */
37 const WP_CORE_DIR = "wpstg-wp-core-dir";
38
39 /**
40 * CSS class name to use for WordPress non core directories
41 * This doesn't contain class selector prefix
42 *
43 * @var string
44 */
45 const WP_NON_CORE_DIR = "wpstg-wp-non-core-dir";
46
47 /** @var array */
48 private $directories = [];
49
50 /** @var DirectoriesUtil */
51 private $objDirectories;
52
53 /** @var string|null */
54 private $directoryToScanOnly;
55
56 /**
57 * @var string Path to gif loader for directory loading
58 */
59 private $gifLoaderPath;
60
61 /**
62 * @var Strings
63 */
64 private $strUtils;
65
66 /**
67 * @var Directory
68 */
69 protected $dirAdapter;
70
71 /**
72 * @var DiskWriteCheck
73 */
74 private $diskWriteCheck;
75
76 /**
77 * @var Sanitize
78 */
79 private $sanitize;
80
81 /**
82 * @var string Path to the info icon
83 */
84 private $infoIconPath;
85
86 /**
87 * @var string
88 */
89 private $basePath;
90
91 /**
92 * @var string
93 */
94 private $pathIdentifier;
95
96 /**
97 * @var TemplateEngine
98 */
99 private $templateEngine;
100
101 /** @var PathIdentifier */
102 private $pathAdapter;
103
104 /** @var PathChecker */
105 private $pathChecker;
106
107 /** @var string */
108 protected $absPath = ABSPATH;
109
110 /** @var string */
111 protected $wpContentPath = WP_CONTENT_DIR;
112
113 /** @var bool */
114 private $isUploadsSymlinked;
115
116 /**
117 * Job constructor.
118 * @throws Exception
119 */
120 public function __construct($directoryToScanOnly = null)
121 {
122 // Accept both the absolute path or relative path with respect to wp root
123 // Santized the path to make comparing works for windows platform too.
124 $this->directoryToScanOnly = null;
125 if ($directoryToScanOnly !== null) {
126 $this->directoryToScanOnly = $directoryToScanOnly;
127 }
128
129 // TODO: inject using DI when available
130 $this->strUtils = new Strings();
131 $this->pathAdapter = WPStaging::make(PathIdentifier::class);
132 $this->pathChecker = WPStaging::make(PathChecker::class);
133 $this->dirAdapter = WPStaging::make(Directory::class);
134 $this->diskWriteCheck = WPStaging::make(DiskWriteCheck::class);
135 $this->sanitize = WPStaging::make(Sanitize::class);
136 $this->templateEngine = WPStaging::make(TemplateEngine::class);
137 parent::__construct();
138 }
139
140 /**
141 * @param string $gifLoaderPath
142 */
143 public function setGifLoaderPath(string $gifLoaderPath)
144 {
145 $this->gifLoaderPath = $gifLoaderPath;
146 }
147
148 /**
149 * @param string $infoIconPath
150 */
151 public function setInfoIcon(string $infoIconPath)
152 {
153 $this->infoIconPath = $infoIconPath;
154 }
155
156 /**
157 * Return the path of info icon
158 *
159 * @return string
160 */
161 public function getInfoIcon(): string
162 {
163 return $this->infoIconPath;
164 }
165
166 /**
167 * @param string $directoryToScanOnly
168 */
169 public function setDirectoryToScanOnly(string $directoryToScanOnly)
170 {
171 $this->directoryToScanOnly = $directoryToScanOnly;
172 }
173
174 /**
175 * @param string $basePath
176 * @todo add typed property `string` and ensure this value is never null
177 */
178 public function setBasePath($basePath)
179 {
180 $this->basePath = rtrim(wp_normalize_path($basePath), '/');
181 }
182
183 /** @return string */
184 public function getBasePath(): string
185 {
186 return $this->basePath;
187 }
188
189 /** @param string $pathIdentifier */
190 public function setPathIdentifier(string $pathIdentifier)
191 {
192 $this->pathIdentifier = $pathIdentifier;
193 }
194
195 /** @return string */
196 public function getPathIdentifier(): string
197 {
198 return $this->pathIdentifier;
199 }
200
201 /**
202 * Upon class initialization
203 */
204 public function initialize()
205 {
206 $this->objDirectories = new DirectoriesUtil();
207
208 $this->options->existingClones = get_option(Sites::STAGING_SITES_OPTION, []);
209 $this->options->existingClones = is_array($this->options->existingClones) ? $this->options->existingClones : [];
210
211 $this->directories = [];
212 if (!empty($this->directoryToScanOnly)) {
213 return;
214 }
215
216 $this->getTables();
217
218 $this->setBasePath($this->absPath);
219 $this->setPathIdentifier(PathIdentifier::IDENTIFIER_ABSPATH);
220 $this->getDirectories($this->absPath);
221
222 // If wp-content is outside ABSPATH, then scan it too
223 if ($this->isWpContentOutsideAbspath()) {
224 $this->setBasePath($this->wpContentPath);
225 $this->setPathIdentifier(PathIdentifier::IDENTIFIER_WP_CONTENT);
226 $this->getDirectories(dirname($this->wpContentPath));
227 }
228
229 $this->installOptimizer();
230 }
231
232 /**
233 * Start Module
234 * @return $this|object
235 * @throws Exception
236 */
237 public function start()
238 {
239 // Basic Options
240 $this->options->root = str_replace(["\\", '/'], DIRECTORY_SEPARATOR, ABSPATH);
241 $this->options->current = null;
242 $this->options->currentClone = $this->getCurrentClone();
243
244 if ($this->options->currentClone !== null) {
245 // Make sure no warning is shown when updating/resetting an old clone having no exclude rules options
246 $this->options->currentClone['excludeSizeRules'] = $this->options->currentClone['excludeSizeRules'] ?? [];
247 $this->options->currentClone['excludeGlobRules'] = $this->options->currentClone['excludeGlobRules'] ?? [];
248 // Make sure no warning is shown when updating/resetting an old clone having no admin account data
249 $this->options->currentClone['useNewAdminAccount'] = $this->options->currentClone['useNewAdminAccount'] ?? false;
250 $this->options->currentClone['adminEmail'] = $this->options->currentClone['adminEmail'] ?? '';
251 $this->options->currentClone['adminPassword'] = $this->options->currentClone['adminPassword'] ?? '';
252 // Make sure no warning is shown when updating/resetting an old clone without databaseSsl, uploadsSymlinked, emailsAllowed and networkClone options
253 $this->options->currentClone['emailsAllowed'] = $this->options->currentClone['emailsAllowed'] ?? true;
254 $this->options->currentClone['databaseSsl'] = $this->options->currentClone['databaseSsl'] ?? false;
255 $this->options->currentClone['uploadsSymlinked'] = $this->options->currentClone['uploadsSymlinked'] ?? false;
256 $this->options->currentClone['networkClone'] = $this->options->currentClone['networkClone'] ?? false;
257 }
258
259 // Tables
260 $this->options->clonedTables = [];
261
262 // Files
263 $this->options->totalFiles = 0;
264 $this->options->totalFileSize = 0;
265 $this->options->copiedFiles = 0;
266
267
268 // Directories
269 $this->options->includedDirectories = [];
270 $this->options->includedExtraDirectories = [];
271 $this->options->excludedDirectories = [];
272 $this->options->extraDirectories = [];
273 $this->options->scannedDirectories = [];
274
275 // Job
276 $this->options->currentJob = "PreserveDataFirstStep";
277 $this->options->currentStep = 0;
278 $this->options->totalSteps = 0;
279
280 // Define mainJob to differentiate between cloning, updating and pushing
281 $this->options->mainJob = Job::STAGING;
282 $job = '';
283 if (isset($_POST["job"])) {
284 $job = $this->sanitize->sanitizeString($_POST['job']);
285 }
286
287 if ($this->options->current !== null && $job === 'resetting') {
288 $this->options->mainJob = Job::RESET;
289 } elseif ($this->options->current !== null) {
290 $this->options->mainJob = Job::UPDATE;
291 }
292
293 // Delete previous cached files
294 $this->cloneOptionCache->delete();
295 $this->filesIndexCache->delete();
296
297 $this->saveOptions();
298
299 return $this;
300 }
301
302 /**
303 * Make sure the Optimizer mu plugin is installed before cloning or pushing
304 */
305 private function installOptimizer()
306 {
307 $optimizer = new Optimizer();
308 $optimizer->installOptimizer();
309 }
310
311 /**
312 * @param bool|null $parentChecked Is parent folder selected
313 * @param bool $forceDefault Default false. Set it to true,
314 * when default button on ui is clicked,
315 * to ignore previous selected option for UPDATE and RESET process.
316 * @param null|array $directories to list
317 *
318 * @return string
319 */
320 public function directoryListing($parentChecked = null, $forceDefault = false, $directories = null): string
321 {
322 if ($directories === null) {
323 $directories = $this->directories;
324 }
325
326 uksort($directories, 'strcasecmp');
327
328 $excludedDirectories = [];
329 $extraDirectories = [];
330
331 if ($this->isUpdateOrResetJob()) {
332 $currentClone = json_decode(json_encode($this->options->currentClone));
333 $extraDirectories = isset($currentClone->extraDirectories) ? $currentClone->extraDirectories : [];
334 $excludedDirectories = isset($currentClone->excludedDirectories) ? array_map(function ($directory) {
335 // Exception is thrown when directory doesn't have identifier, so we will return directory as it is
336 try {
337 return $this->pathAdapter->transformIdentifiableToPath($directory);
338 } catch (UnexpectedValueException $ex) {
339 return $directory;
340 }
341 }, $currentClone->excludedDirectories) : [];
342 }
343
344 $output = '';
345 foreach ($directories as $dirName => $directory) {
346 // Not a directory, possibly a symlink, therefore we will skip it
347 if (!is_array($directory)) {
348 continue;
349 }
350
351 // Need to preserve keys so no array_shift()
352 $data = reset($directory);
353 unset($directory[key($directory)]);
354
355 $output .= $this->getDirectoryHtml($data['dirName'], $data, $excludedDirectories, $extraDirectories, $parentChecked, $forceDefault);
356 }
357
358 return $output;
359 }
360
361 /**
362 * Checks if there is enough free disk space to create staging site according to selected directories
363 * Returns null when can't run disk_free_space function one way or another
364 * @param string $excludedDirectories
365 * @param string $extraDirectories
366 *
367 * @return bool|null
368 */
369 public function hasFreeDiskSpace(string $excludedDirectories, string $extraDirectories)
370 {
371 $dirUtils = new WpDefaultDirectories();
372 $selectedDirectories = $dirUtils->getWpCoreDirectories();
373 $excludedDirectories = $dirUtils->getExcludedDirectories($excludedDirectories);
374
375 if ($this->isUploadsSymlinked) {
376 $uploadDirectory = rtrim(str_replace($this->absPath, PathIdentifier::IDENTIFIER_ABSPATH, $this->dirAdapter->getMainSiteUploadsDirectory()), '/');
377 $excludedDirectories[] = $uploadDirectory;
378 }
379
380 $size = 0;
381 // Scan WP Root path for size (only files)
382 $size += $this->getDirectorySizeExcludingSubdirs($this->absPath);
383 // Scan selected directories for size (wp-core)
384 foreach ($selectedDirectories as $directory) {
385 if ($this->isPathInDirectories($directory, $excludedDirectories)) {
386 continue;
387 }
388
389 $size += $this->getDirectorySizeInclSubdirs($directory, $excludedDirectories);
390 }
391
392 if (!empty($extraDirectories) && $extraDirectories !== '') {
393 $extraDirectories = wpstg_urldecode(explode(ScanConst::DIRECTORIES_SEPARATOR, $extraDirectories));
394 foreach ($extraDirectories as $directory) {
395 $size += $this->getDirectorySizeInclSubdirs($this->absPath . $directory, $excludedDirectories);
396 }
397 }
398
399 $errorMessage = null;
400 try {
401 $this->diskWriteCheck->checkPathCanStoreEnoughBytes($this->absPath, $size);
402 } catch (RuntimeException $ex) {
403 $errorMessage = $ex->getMessage();
404 } catch (DiskNotWritableException $ex) {
405 $errorMessage = $ex->getMessage();
406 }
407
408 $data = [
409 'requiredSpace' => $this->utilsMath->formatSize($size),
410 'errorMessage' => $errorMessage
411 ];
412
413 echo json_encode($data);
414 die();
415 }
416
417 /**
418 * Get Database Tables
419 */
420 protected function getTables()
421 {
422 $db = WPStaging::getInstance()->get("wpdb");
423 $dbPrefix = WPStaging::getTablePrefix();
424
425 $sql = "SHOW TABLE STATUS";
426
427 $tables = $db->get_results($sql);
428
429 $currentTables = [];
430
431 $currentClone = $this->getCurrentClone();
432 $networkClone = is_multisite() && is_main_site() && is_array($currentClone) && (array_key_exists('networkClone', $currentClone) ? $this->sanitize->sanitizeBool($currentClone['networkClone']) : false);
433
434 // Reset excluded Tables than loop through all tables
435 $this->options->excludedTables = [];
436 foreach ($tables as $table) {
437 // Create array of unchecked tables
438 // On the main website of a multisite installation, do not select network site tables beginning with wp_1_, wp_2_ etc.
439 // (On network sites, the correct tables are selected anyway)
440 if (
441 ( ! empty($dbPrefix) && strpos($table->Name, $dbPrefix) !== 0)
442 || (is_multisite() && is_main_site() && !$networkClone && preg_match('/^' . $dbPrefix . '\d+_/', $table->Name))
443 ) {
444 $this->options->excludedTables[] = $table->Name;
445 }
446
447 if ($table->Comment !== "VIEW") {
448 $currentTables[] = [
449 "name" => $table->Name,
450 "size" => ($table->Data_length + $table->Index_length)
451 ];
452 }
453 }
454
455 $this->options->tables = json_decode(json_encode($currentTables));
456 }
457
458 /**
459 * Get directories and main meta data about given directory path
460 * @param string $dirPath - Optional - Default ABSPATH
461 * @param bool $shouldReturn - Optional - Default false
462 *
463 * @return void|array Depend upon value of $shouldReturn
464 */
465 public function getDirectories(string $dirPath = ABSPATH, bool $shouldReturn = false)
466 {
467 if (!is_dir($dirPath)) {
468 return;
469 }
470
471 try {
472 $directories = new DirectoryIterator($dirPath);
473 } catch (UnexpectedValueException $ex) {
474 $errorMessage = $ex->getMessage();
475 if ($ex->getCode() === 5) {
476 $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');
477 }
478
479 echo json_encode([
480 'success' => false,
481 'type' => '',
482 // TODO: Create a Swal Response Class and Js library to handle that response or, Implement own Swal alternative
483 'swalOptions' => [
484 'title' => esc_html__('Error!', 'wp-staging'),
485 'html' => $errorMessage,
486 'cancelButtonText' => esc_html__('Ok', 'wp-staging'),
487 'showCancelButton' => true,
488 'showConfirmButton' => false,
489 ],
490 ]);
491
492 exit();
493 }
494
495 $result = [];
496
497 foreach ($directories as $directory) {
498 if ($directory->isDot() || $directory->isFile()) {
499 continue;
500 }
501
502 // Not a valid directory
503 if (($path = $this->getPath($directory)) === false) {
504 continue;
505 }
506
507 $fullPath = trailingslashit($this->getBasePath()) . ltrim($path, '/');
508 $size = $this->getDirectorySize($fullPath);
509
510 // If filename is int, then it is treated as a numeric index in key and start with 0
511 $result[$directory->getFilename()]['metaData'] = [
512 'dirName' => $directory->getFilename(),
513 "size" => $size,
514 "path" => $fullPath,
515 "basePath" => $this->getBasePath(),
516 "prefix" => $this->getPathIdentifier()
517 ];
518 }
519
520 if ($shouldReturn) {
521 return $result;
522 }
523
524 $this->directories = array_merge($this->directories, $result);
525 }
526
527 /**
528 * Get Path from $directory
529 * @param DirectoryIterator $directory
530 * @return bool|string
531 */
532 protected function getPath($directory)
533 {
534 $basePath = $this->getBasePath();
535 $realPath = WPStaging::make('WPSTG_ALLOW_VFS') === true && strpos($directory->getPathname(), 'vfs://') === 0 ? $directory->getPathname() : $directory->getRealPath();
536 $realPath = wp_normalize_path($realPath);
537
538 /*
539 * Do not follow root path like src/web/..
540 * This must be done before \SplFileInfo->isDir() is used!
541 * Prevents open base dir restriction fatal errors
542 */
543 if (strpos($realPath, $basePath) !== 0) {
544 return false;
545 }
546
547 $path = str_replace($basePath, '', $realPath);
548 // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
549 if (!$directory->isDir() || (strlen($path) < 1 && $this->pathIdentifier !== PathIdentifier::IDENTIFIER_WP_CONTENT)) {
550 return false;
551 }
552
553 return $path;
554 }
555
556 /**
557 * @param string $dirName
558 * @param array $dirInfo contains information about the directory
559 * @param array $excludedDirectories
560 * @param array $extraDirectories
561 * @param bool $parentChecked
562 * @param bool $forceDefault
563 * @return string
564 */
565 protected function getDirectoryHtml($dirName, $dirInfo, $excludedDirectories, $extraDirectories, $parentChecked = false, $forceDefault = false)
566 {
567 $data = $dirInfo;
568 $dataPath = isset($data["path"]) ? $data["path"] : '';
569 $dataSize = isset($data["size"]) ? $data["size"] : '';
570 $path = wp_normalize_path($dataPath);
571 $basePath = isset($data["basePath"]) ? $data["basePath"] : wp_normalize_path($this->absPath);
572 $prefix = isset($data["prefix"]) ? $data["prefix"] : PathIdentifier::IDENTIFIER_ABSPATH;
573 $relPath = str_replace($basePath, '', $path);
574 $relPath = ltrim($relPath, '/');
575
576 // Check if directory name or directory path is not WP core folder
577 $isNotWPCoreDir = $this->isNonWpCoreDirectory($dirName, $path);
578
579 $class = $isNotWPCoreDir ? self::WP_NON_CORE_DIR : self::WP_CORE_DIR;
580 $dirType = 'other';
581
582 if ($this->strUtils->startsWith($path, $this->dirAdapter->getPluginsDirectory()) !== false) {
583 $pluginPath = $this->strUtils->strReplaceFirst($this->dirAdapter->getPluginsDirectory(), '', $path);
584 $dirType = strpos($pluginPath, '/') === false ? 'plugin' : 'other';
585 } elseif ($this->strUtils->startsWith($path, $this->dirAdapter->getActiveThemeParentDirectory()) !== false) {
586 $themePath = $this->strUtils->strReplaceFirst($this->dirAdapter->getActiveThemeParentDirectory(), '', $path);
587 $dirType = strpos($themePath, '/') === false ? 'theme' : 'other';
588 }
589
590 $isScanned = 'false';
591 if (
592 trailingslashit($path) === $this->dirAdapter->getWpContentDirectory()
593 || trailingslashit($path) === $this->dirAdapter->getPluginsDirectory()
594 || trailingslashit($path) === $this->dirAdapter->getActiveThemeParentDirectory()
595 ) {
596 $isScanned = 'true';
597 }
598
599 // Make wp-includes and wp-admin directory items not expandable
600 $isNavigatable = 'true';
601 if ($this->strUtils->startsWith($path, $basePath . "/wp-admin") !== false || $this->strUtils->startsWith($path, $basePath . "/wp-includes") !== false) {
602 $isNavigatable = 'false';
603 }
604
605 // Decide if item checkbox is active or not
606 $shouldBeChecked = $parentChecked !== null ? $parentChecked : !$isNotWPCoreDir;
607 if (!$forceDefault && $this->isUpdateOrResetJob() && (!$this->isPathInDirectories($path, $excludedDirectories, $basePath))) {
608 $shouldBeChecked = true;
609 } elseif (!$forceDefault && $this->isUpdateOrResetJob()) {
610 $shouldBeChecked = false;
611 }
612
613 if (!$forceDefault && $this->isUpdateOrResetJob() && $class === self::WP_NON_CORE_DIR && !$this->isPathInDirectories($path, $extraDirectories)) {
614 $shouldBeChecked = false;
615 }
616
617 $isDisabledDir = $dirName === 'wp-admin' || $dirName === 'wp-includes';
618
619 $isDisabled = false;
620 if (strpos($dataPath, 'wp-content/' . Directory::STAGING_SITE_DIRECTORY) !== false) {
621 $isDisabled = true;
622 $shouldBeChecked = false;
623 }
624
625 return $this->templateEngine->render('Backend/views/clone/ajax/directory-navigation.php', [
626 'scan' => $this,
627 'prefix' => $prefix,
628 'relPath' => $relPath,
629 'class' => $class,
630 'dirType' => $dirType,
631 'isScanned' => $isScanned,
632 'isNavigatable' => $isNavigatable,
633 'shouldBeChecked' => $shouldBeChecked,
634 'parentChecked' => $parentChecked,
635 'directoryDisabled' => $isNotWPCoreDir || $isDisabledDir,
636 'isDisabled' => $isDisabled,
637 'dirName' => $dirName,
638 'gifLoaderPath' => $this->gifLoaderPath,
639 'formattedSize' => $this->utilsMath->formatSize($dataSize),
640 'isDebugMode' => $this->utilsMath->formatSize($dataSize),
641 'dataPath' => $dataPath,
642 'basePath' => $basePath,
643 'forceDefault' => $forceDefault,
644 'dirPath' => $path,
645 ]);
646 }
647
648 /**
649 * Gets size of given directory
650 * @param string $path
651 * @return int|null
652 */
653 protected function getDirectorySize($path)
654 {
655 if (!isset($this->settings->checkDirectorySize) || $this->settings->checkDirectorySize !== '1') {
656 return null;
657 }
658
659 return $this->objDirectories->size($path);
660 }
661
662 /**
663 * Get total size of a directory including all its subdirectories
664 * @param string $dir
665 * @param array $excludedDirectories
666 * @return int
667 */
668 protected function getDirectorySizeInclSubdirs($dir, $excludedDirectories)
669 {
670 $size = 0;
671 foreach (glob(rtrim($dir, '/') . '/*', GLOB_NOSORT) as $each) {
672 if (is_file($each)) {
673 $size += filesize($each);
674 continue;
675 }
676
677 if ($this->isPathInDirectories($each, $excludedDirectories)) {
678 continue;
679 }
680
681 $size += $this->getDirectorySizeInclSubdirs($each, $excludedDirectories);
682 }
683
684 return $size;
685 }
686
687 /**
688 * Get total size of a directory excluding all its subdirectories
689 * @param string $dir
690 * @return int
691 */
692 protected function getDirectorySizeExcludingSubdirs($dir)
693 {
694 $size = 0;
695 foreach (glob(rtrim($dir, '/') . '/*', GLOB_NOSORT) as $each) {
696 $size += is_file($each) ? filesize($each) : 0;
697 }
698
699 return $size;
700 }
701
702 /**
703 * Is the path present is given list of directories
704 * @param string $path
705 * @param array $directories List of directories relative to ABSPATH with leading slash
706 * @param ?string $basePath
707 *
708 * @return bool
709 */
710 protected function isPathInDirectories(string $path, array $directories, $basePath = null): bool
711 {
712 return $this->pathChecker->isPathInPathsList($path, $directories, true, $basePath);
713 }
714
715 /**
716 * Get clone from $_POST['clone'] and set it as current clone
717 * If clone is not found, then set current clone to null
718 *
719 * @return array|null
720 */
721 protected function getCurrentClone()
722 {
723 $cloneID = isset($_POST["clone"]) ? $this->sanitize->sanitizeString($_POST['clone']) : '';
724
725 if (array_key_exists($cloneID, $this->options->existingClones)) {
726 $this->options->current = $cloneID;
727 return $this->options->existingClones[$this->options->current];
728 }
729
730 return null;
731 }
732
733 /**
734 * @return bool
735 */
736 protected function isWpContentOutsideAbspath()
737 {
738 /** @var SiteInfo $siteInfo */
739 $siteInfo = WPStaging::make(SiteInfo::class);
740 return $siteInfo->isWpContentOutsideAbspath();
741 }
742
743 /**
744 * Check if directory name or directory path is not WP core folder
745 *
746 * @param string $dirname
747 * @param string $path
748 * @return bool
749 */
750 protected function isNonWpCoreDirectory($dirname, $path)
751 {
752 $coreDirectories = [
753 'wp-admin',
754 'wp-content',
755 'wp-includes'
756 ];
757
758 if (in_array($dirname, $coreDirectories)) {
759 return false;
760 }
761
762 $wpDirectories = [
763 $this->dirAdapter->getWpContentDirectory(),
764 $this->dirAdapter->getPluginsDirectory(),
765 $this->dirAdapter->getActiveThemeParentDirectory(),
766 $this->dirAdapter->getUploadsDirectory(),
767 $this->dirAdapter->getMuPluginsDirectory()
768 ];
769
770 foreach ($wpDirectories as $wpDirectory) {
771 if (strpos(trailingslashit($path), $wpDirectory) !== false) {
772 return false;
773 }
774 }
775
776 return true;
777 }
778
779 /**
780 * @param bool $isUploadsSymlinked
781 * @return void
782 */
783 public function setIsUploadsSymlinked(bool $isUploadsSymlinked)
784 {
785 $this->isUploadsSymlinked = $isUploadsSymlinked;
786 }
787 }
788