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 / Framework / Filesystem / FilesystemScanner.php
wp-staging / Framework / Filesystem Last commit date
Filters 1 day ago Scanning 1 day ago AbstractFileObject.php 1 day ago AbstractFilesystemScanner.php 1 day ago DebugLogReader.php 1 day ago DirectoryListing.php 1 day ago DirectorySize.php 1 day ago DiskWriteCheck.php 1 day ago FileObject.php 1 day ago Filesystem.php 1 day ago FilesystemExceptions.php 5 years ago FilesystemScanner.php 1 day ago FilesystemScannerDto.php 1 day ago FilterableDirectoryIterator.php 1 day ago LegacyFileRulesTrait.php 1 day ago LogCleanup.php 1 day ago LogFiles.php 1 day ago MissingFileException.php 3 years ago OPcache.php 1 day ago PartIdentifier.php 1 day ago PathChecker.php 1 day ago PathIdentifier.php 1 day ago Permissions.php 1 day ago WpUploadsFolderSymlinker.php 1 day ago
FilesystemScanner.php
654 lines
1 <?php
2
3 namespace WPStaging\Framework\Filesystem;
4
5 use OutOfBoundsException;
6 use RuntimeException;
7 use SplFileInfo;
8 use Throwable;
9 use WPStaging\Core\WPStaging;
10 use WPStaging\Framework\Adapter\Directory;
11 use WPStaging\Framework\Filesystem\Filters\PathFilterHelper;
12 use WPStaging\Framework\Job\Exception\DiskNotWritableException;
13 use WPStaging\Framework\Queue\FinishedQueueException;
14 use WPStaging\Framework\Queue\SeekableQueueInterface;
15 use WPStaging\Framework\SiteInfo;
16 use WPStaging\Framework\Utils\PluginInfo;
17 use WPStaging\Vendor\Psr\Log\LoggerInterface;
18
19 class FilesystemScanner extends AbstractFilesystemScanner
20 {
21 use LegacyFileRulesTrait;
22
23
24 protected $filesystemQueue;
25
26
27 protected $taskQueue;
28
29
30 protected $logger;
31
32
33 protected $scannerDto;
34
35
36 protected $logTitle = '';
37
38
39 protected $queueCacheName = '';
40
41
42 protected $ignoreFileBiggerThan = 0;
43
44
45 protected $ignoreFileExtensions = [];
46
47
48 protected $ignoreFileExtensionFilesBiggerThan = [];
49
50
51 protected $isSiteHostedOnWordPressCom = false;
52
53
54 protected $folderNameRules = [];
55
56
57 protected $fileNameRules = [];
58
59
60
61
62
63
64
65
66 protected $recursiveExcludeFilter = null;
67
68
69
70
71
72
73
74
75 protected $enqueueEmptyDirectories = false;
76
77
78
79
80
81
82
83
84
85 public function __construct(
86 Directory $directory,
87 PathIdentifier $pathIdentifier,
88 Filesystem $filesystem,
89 PluginInfo $pluginInfo,
90 SiteInfo $siteInfo,
91 SeekableQueueInterface $filesystemQueue
92 ) {
93 parent::__construct($directory, $pathIdentifier, $filesystem, $pluginInfo);
94 $this->isSiteHostedOnWordPressCom = $siteInfo->isHostedOnWordPressCom();
95 $this->filesystemQueue = $filesystemQueue;
96 }
97
98
99
100
101
102
103
104 public function setFilters(int $ignoreFileBiggerThan, array $ignoreFileExtensions, array $ignoreFileExtensionFilesBiggerThan)
105 {
106 $this->ignoreFileBiggerThan = $ignoreFileBiggerThan;
107 $this->ignoreFileExtensions = $ignoreFileExtensions;
108 $this->ignoreFileExtensionFilesBiggerThan = $ignoreFileExtensionFilesBiggerThan;
109 }
110
111
112
113
114
115
116
117 public function setNameExcludeRules(array $folderNameRules, array $fileNameRules)
118 {
119 $this->folderNameRules = $folderNameRules;
120 $this->fileNameRules = $fileNameRules;
121 }
122
123
124
125
126
127
128
129
130
131 public function setRecursiveExcludeRules(array $rules)
132 {
133 if (empty($rules)) {
134 $this->recursiveExcludeFilter = null;
135 return;
136 }
137
138 $filter = new PathFilterHelper();
139 $filter->setWpRootPath($this->rootPath);
140 $filter->categorizeRules($rules);
141 $this->recursiveExcludeFilter = $filter;
142 }
143
144
145
146
147
148 public function setRootPath(string $rootPath)
149 {
150 parent::setRootPath($rootPath);
151 if ($this->recursiveExcludeFilter !== null) {
152 $this->recursiveExcludeFilter->setWpRootPath($rootPath);
153 }
154 }
155
156
157
158
159
160 public function setEnqueueEmptyDirectories(bool $enqueueEmptyDirectories)
161 {
162 $this->enqueueEmptyDirectories = $enqueueEmptyDirectories;
163 }
164
165
166
167
168 public function setupFilesystemQueue()
169 {
170 $fileBackupQueueCacheName = $this->queueCacheName . '_' . $this->currentPathScanning;
171 $this->filesystemQueue->setup($fileBackupQueueCacheName, SeekableQueueInterface::MODE_WRITE);
172 }
173
174
175
176
177
178 public function setLogTitle(string $logTitle)
179 {
180 $this->logTitle = $logTitle;
181 }
182
183
184
185
186
187 public function setQueueCacheName(string $queueCacheName)
188 {
189 $this->queueCacheName = $queueCacheName;
190 }
191
192
193
194
195
196
197
198 public function inject(LoggerInterface $logger, SeekableQueueInterface $taskQueue, FilesystemScannerDto $scannerDto)
199 {
200 $this->logger = $logger;
201 $this->taskQueue = $taskQueue;
202 $this->scannerDto = $scannerDto;
203 }
204
205 public function getFilesystemScannerDto(): FilesystemScannerDto
206 {
207 return $this->scannerDto;
208 }
209
210
211
212
213 public function unlockQueue()
214 {
215 $this->filesystemQueue->shutdown();
216 }
217
218
219
220
221
222
223 public function processQueue()
224 {
225 try {
226 $path = $this->taskQueue->dequeue();
227 if ($path === null) {
228 throw new FinishedQueueException('Directory Scanner Queue is Finished');
229 }
230
231 $this->processPath($path);
232 } catch (FinishedQueueException $ex) {
233 try {
234 WPStaging::make(DiskWriteCheck::class)->checkPathCanStoreEnoughBytes($this->directory->getPluginUploadsDirectory(), $this->scannerDto->getFilesystemSize());
235 } catch (DiskNotWritableException $e) {
236 throw $e;
237 } catch (RuntimeException $e) {
238
239 $this->logger->debug($e->getMessage());
240 }
241
242 throw $ex;
243 } catch (OutOfBoundsException $e) {
244 $this->logger->debug($e->getMessage());
245 } catch (Throwable $e) {
246 $this->logger->warning($e->getMessage());
247 }
248 }
249
250
251
252
253 protected function preRecursivePathScanningStep()
254 {
255 $this->setupFilesystemQueue();
256 }
257
258
259
260
261
262
263
264 protected function processFile(SplFileInfo $file, string $linkPath = '')
265 {
266 $normalizedPath = $this->filesystem->normalizePath($file->getPathname(), !$file->isFile());
267 $fileSize = $file->getSize();
268 $fileExtension = $file->getExtension();
269
270 $relativePath = $this->computeRelativePathFromRoot($normalizedPath);
271
272 if ($this->isExcludeByFileNameRule($file->getFilename())) {
273 $this->scannerDto->addFileExcludedInRequest($relativePath);
274 return;
275 }
276
277
278 $normalizedDebugPath = $this->filesystem->normalizePath($this->contentPath . '/debug.log');
279 if ($normalizedPath === $normalizedDebugPath) {
280 $this->scannerDto->addFileExcludedInRequest($relativePath);
281 $this->logger->notice(sprintf(
282 '%s: Skipped file "%s". Excluded by rule.',
283 esc_html($this->logTitle),
284 esc_html($relativePath)
285 ));
286 return;
287 }
288
289 if ($this->canExcludeLogFile($fileExtension) || $this->canExcludeCacheFile($fileExtension) || isset($this->ignoreFileExtensions[$fileExtension])) {
290 $this->scannerDto->addFileExcludedInRequest($relativePath);
291 $this->logger->notice(sprintf(
292 '%s: Skipped file: "%s". Extension: "%s" is excluded by rule.',
293 esc_html($this->logTitle),
294 esc_html($relativePath),
295 esc_html($fileExtension)
296 ));
297
298 return;
299 }
300
301 if (isset($this->ignoreFileExtensionFilesBiggerThan[$fileExtension])) {
302 if ($fileSize > $this->ignoreFileExtensionFilesBiggerThan[$fileExtension]) {
303 $this->scannerDto->addFileExcludedInRequest($relativePath);
304 $this->logger->notice(sprintf(
305 '%s: Skipped file "%s" (%s). It exceeds the maximum allowed file size for files with the extension "%s" (%s).',
306 esc_html($this->logTitle),
307 esc_html($relativePath),
308 size_format($fileSize),
309 esc_html($fileExtension),
310 size_format($this->ignoreFileExtensionFilesBiggerThan[$fileExtension])
311 ));
312
313 return;
314 }
315 } elseif ($fileSize > $this->ignoreFileBiggerThan) {
316 $this->scannerDto->addFileExcludedInRequest($relativePath);
317 $this->logger->notice(sprintf(
318 '%s: Skipped file "%s" (%s). It exceeds the maximum file size (%s).',
319 esc_html($this->logTitle),
320 esc_html($relativePath),
321 size_format($fileSize),
322 size_format($this->ignoreFileBiggerThan)
323 ));
324
325 return;
326 }
327
328 $this->scannerDto->incrementDiscoveredFiles();
329 $this->scannerDto->incrementDiscoveredFilesByCategory($this->currentPathScanning);
330 $this->scannerDto->addFilesystemSize($fileSize);
331
332 if (!empty($linkPath)) {
333 $linkPath = $this->filesystem->normalizePath($linkPath, true);
334 $relativePath = $this->replaceEOLsWithPlaceholders($relativePath);
335 $path = rtrim($relativePath, '/') . self::PATH_SEPARATOR . rtrim($linkPath, '/');
336 $this->filesystemQueue->enqueue($path);
337 return;
338 }
339
340 $relativePath = $this->replaceEOLsWithPlaceholders($relativePath);
341 $this->filesystemQueue->enqueue(rtrim($relativePath, '/'));
342 }
343
344
345
346
347
348 private function computeRelativePathFromRoot(string $normalizedPath): string
349 {
350 return str_replace($this->filesystem->normalizePath($this->rootPath, true), '', $normalizedPath);
351 }
352
353
354
355
356
357
358 protected function processDirectory(SplFileInfo $dir, $link = null)
359 {
360 if ($this->isUploadsYearMonthDirectory($dir)) {
361 $this->preScanPath($dir->getPathname());
362 return;
363 }
364
365 $normalizedPath = $this->filesystem->normalizePath($dir->getPathname(), true);
366
367 if ($this->isExcludedDirectory($dir->getPathname()) || $this->canExcludeCacheDir($dir)) {
368 return;
369 }
370
371 if ($link !== null && $this->isExcludedDirectory($link->getPathname())) {
372 return;
373 }
374
375 $folderName = $link !== null ? $link->getFilename() : $dir->getFilename();
376 if ($this->isExcludeByFolderNameRule($folderName)) {
377 return;
378 }
379
380 if ($link !== null) {
381 $linkPath = $this->filesystem->normalizePath($link->getPathname(), true);
382 $this->taskQueue->enqueue($this->currentPathScanning . self::PATH_SEPARATOR . $normalizedPath . self::PATH_SEPARATOR . $linkPath);
383 return;
384 }
385
386
387 $this->taskQueue->enqueue($this->currentPathScanning . self::PATH_SEPARATOR . $normalizedPath);
388 }
389
390
391
392
393
394
395
396 protected function isExcludedByRecursiveRule(string $path): bool
397 {
398 if ($this->recursiveExcludeFilter === null) {
399 return false;
400 }
401
402 return $this->recursiveExcludeFilter->isMatched(new SplFileInfo($path));
403 }
404
405
406
407
408
409 protected function isExcludedDirectory(string $path): bool
410 {
411 $normalizedPath = $this->filesystem->normalizePath($path, true);
412
413 if (in_array($normalizedPath, $this->scannerDto->getExcludedDirectories())) {
414 $relativePathForLogging = str_replace($this->filesystem->normalizePath($this->contentPath, true), '', $normalizedPath);
415
416 $this->logger->notice(sprintf(
417 '%s: Skipped directory "%s". Excluded by rule',
418 esc_html($this->logTitle),
419 esc_html($relativePathForLogging)
420 ));
421
422 return true;
423 }
424
425 return false;
426 }
427
428
429
430
431
432 protected function recursivePathScanning(string $path, string $link = '')
433 {
434 if ($this->isExcludedDirectory($path) || $this->isExcludedByRecursiveRule($path)) {
435 return;
436 }
437
438 $this->scannerDto->incrementTotalDirectories();
439
440 if (!$this->enqueueEmptyDirectories) {
441 parent::recursivePathScanning($path, $link);
442 return;
443 }
444
445
446
447 $discoveredFilesBefore = $this->scannerDto->getDiscoveredFiles();
448
449 parent::recursivePathScanning($path, $link);
450
451 if ($this->scannerDto->getDiscoveredFiles() === $discoveredFilesBefore) {
452 $this->enqueueEmptyDirectory($path, $link);
453 }
454 }
455
456
457
458
459
460
461
462
463 public function maybeEnqueueAsEmptyDirectory(string $path): bool
464 {
465 if (!$this->enqueueEmptyDirectories || !is_dir($path)) {
466 return false;
467 }
468
469 $iterator = new \FilesystemIterator($path, \FilesystemIterator::SKIP_DOTS);
470 if ($iterator->valid()) {
471 return false;
472 }
473
474 $this->scannerDto->incrementTotalDirectories();
475 $this->enqueueEmptyDirectory($path);
476
477 return true;
478 }
479
480
481
482
483
484
485
486
487
488 protected function enqueueEmptyDirectory(string $path, string $link = '')
489 {
490 $normalizedPath = $this->filesystem->normalizePath($path, true);
491 $rootPath = $this->filesystem->normalizePath($this->rootPath, true);
492 $relativePath = str_replace($rootPath, '', $normalizedPath);
493 $relativePath = $this->replaceEOLsWithPlaceholders($relativePath);
494
495 $this->scannerDto->incrementDiscoveredFiles();
496 $this->scannerDto->incrementDiscoveredFilesByCategory($this->currentPathScanning);
497
498 if (!empty($link)) {
499 $linkPath = $this->filesystem->normalizePath($link, true);
500 $queueItem = rtrim($relativePath, '/') . self::PATH_SEPARATOR . rtrim($linkPath, '/');
501 $this->filesystemQueue->enqueue($queueItem);
502 return;
503 }
504
505 $this->filesystemQueue->enqueue(rtrim($relativePath, '/'));
506 }
507
508
509
510
511
512 protected function isUploadsYearMonthDirectory(SplFileInfo $dir): bool
513 {
514 if ($this->currentPathScanning !== PartIdentifier::UPLOAD_PART_IDENTIFIER) {
515 return false;
516 }
517
518 $parentDir = $dir->getPathInfo();
519 if ($parentDir === false) {
520 return false;
521 }
522
523 if ($this->filesystem->normalizePath($parentDir->getPathname(), true) !== $this->directory->getUploadsDirectory()) {
524 return false;
525 }
526
527
528
529
530
531
532
533 return is_numeric($dir->getBasename()) && $dir->getBasename() > 1970 && $dir->getBasename() < 2100;
534 }
535
536 protected function isExcludeByFileNameRule(string $fileName): bool
537 {
538 if (empty($this->fileNameRules)) {
539 return false;
540 }
541
542 foreach ($this->fileNameRules as $rule) {
543 if ($this->ruleMatch($rule, $fileName)) {
544 $this->logger->info(sprintf(
545 '%s: Skipped file "%s". Excluded by file name rule: "%s".',
546 esc_html($this->logTitle),
547 esc_html($fileName),
548 esc_html($rule)
549 ));
550
551 return true;
552 }
553 }
554
555 return false;
556 }
557
558 protected function isExcludeByFolderNameRule(string $folderName): bool
559 {
560 if (empty($this->folderNameRules)) {
561 return false;
562 }
563
564 foreach ($this->folderNameRules as $rule) {
565 if ($this->ruleMatch($rule, $folderName)) {
566 $this->logger->info(sprintf(
567 '%s: Skipped directory "%s". Excluded by folder name rule: "%s".',
568 esc_html($this->logTitle),
569 esc_html($folderName),
570 esc_html($rule)
571 ));
572
573 return true;
574 }
575 }
576
577 return false;
578 }
579
580
581
582
583
584 private function canExcludeLogFile(string $fileExtension): bool
585 {
586 if ($fileExtension !== 'log') {
587 return false;
588 }
589
590 if (!$this->scannerDto->getIsExcludingLogs()) {
591 return false;
592 }
593
594 return true;
595 }
596
597
598
599
600
601 private function canExcludeCacheFile(string $fileExtension): bool
602 {
603 if ($fileExtension !== 'cache') {
604 return false;
605 }
606
607 if (!$this->scannerDto->getIsExcludingCaches()) {
608 return false;
609 }
610
611 return true;
612 }
613
614
615
616
617
618 private function canExcludeCacheDir(SplFileInfo $dir): bool
619 {
620 if (!$dir->isDir()) {
621 return false;
622 }
623
624 if (!$this->scannerDto->getIsExcludingCaches()) {
625 return false;
626 }
627
628 if (!$this->isPathContainsCache($dir->getRealPath())) {
629 return false;
630 }
631
632 $this->logger->notice(sprintf(
633 '%s: Skipped directory "%s". Excluded by smart exclusion rule: Excluding cache folder.',
634 esc_html($this->logTitle),
635 esc_html($dir->getRealPath())
636 ));
637
638 return true;
639 }
640
641
642
643
644
645
646
647 private function isPathContainsCache(string $path): bool
648 {
649 $pathParts = explode(DIRECTORY_SEPARATOR, $path);
650
651 return in_array('cache', $pathParts);
652 }
653 }
654