PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.9.4
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.9.4
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 / AbstractFilesystemScanner.php
wp-staging / Framework / Filesystem Last commit date
Filters 2 days ago Scanning 5 years ago AbstractFileObject.php 1 year ago AbstractFilesystemScanner.php 2 days ago DebugLogReader.php 2 years ago DirectoryListing.php 6 months ago DirectorySize.php 2 weeks ago DiskWriteCheck.php 6 months ago FileObject.php 1 year ago Filesystem.php 7 months ago FilesystemExceptions.php 5 years ago FilesystemScanner.php 2 weeks ago FilesystemScannerDto.php 1 month ago FilterableDirectoryIterator.php 1 year ago LegacyFileRulesTrait.php 2 weeks ago LogCleanup.php 6 months ago LogFiles.php 1 year ago MissingFileException.php 3 years ago OPcache.php 6 months ago PartIdentifier.php 9 months ago PathChecker.php 2 years ago PathIdentifier.php 3 weeks ago Permissions.php 1 month ago WpUploadsFolderSymlinker.php 1 month ago
AbstractFilesystemScanner.php
372 lines
1 <?php
2
3 namespace WPStaging\Framework\Filesystem;
4
5 use Exception;
6 use SplFileInfo;
7 use WPStaging\Framework\Adapter\Directory;
8 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
9 use WPStaging\Framework\Traits\SafeFileInfoTrait;
10 use WPStaging\Framework\Utils\PluginInfo;
11
12 abstract class AbstractFilesystemScanner
13 {
14 use EndOfLinePlaceholderTrait;
15 use SafeFileInfoTrait;
16
17 /**
18 * @var string
19 */
20 const PATH_SEPARATOR = '::';
21
22 /** @var Directory */
23 protected $directory;
24
25 /** @var Filesystem */
26 protected $filesystem;
27
28 /** @var PathIdentifier */
29 protected $pathIdentifier;
30
31 /** @var PluginInfo */
32 protected $pluginInfo;
33
34 /**
35 * The parent path which is currently being scanned
36 * Can be either plugins, mu_plugins, themes, uploads or other
37 * Where other means base wp-content directory but skipping plugins, mu_plugins, themes and uploads as they are handle separately
38 * @var string
39 */
40 protected $currentPathScanning = '';
41
42 /**
43 * The root path of the site
44 * @var string
45 */
46 protected $rootPath = '';
47
48 /**
49 * The content path of the site
50 * @var string
51 */
52 protected $contentPath = '';
53
54 /** @var bool */
55 protected $skipFiles = false;
56
57 /** @var bool */
58 protected $skipDirectories = false;
59
60 /** @var array */
61 protected $excludeRules = [];
62
63 /**
64 * @param Directory $directory
65 * @param PathIdentifier $pathIdentifier
66 * @param Filesystem $filesystem
67 * @param PluginInfo $pluginInfo
68 */
69 public function __construct(
70 Directory $directory,
71 PathIdentifier $pathIdentifier,
72 Filesystem $filesystem,
73 PluginInfo $pluginInfo
74 ) {
75 $this->directory = $directory;
76 $this->filesystem = $filesystem;
77 $this->pathIdentifier = $pathIdentifier;
78 $this->pluginInfo = $pluginInfo;
79 $this->rootPath = ABSPATH;
80 $this->contentPath = WP_CONTENT_DIR;
81 }
82
83 /**
84 * @param string $currentPathScanning
85 * @return void
86 */
87 public function setCurrentPathScanning(string $currentPathScanning)
88 {
89 $this->currentPathScanning = $currentPathScanning;
90 }
91
92 /**
93 * @param string $rootPath
94 * @return void
95 */
96 public function setRootPath(string $rootPath)
97 {
98 $this->rootPath = $rootPath;
99 }
100
101 /**
102 * @param string $contentPath
103 * @return void
104 */
105 public function setContentPath(string $contentPath)
106 {
107 $this->contentPath = $contentPath;
108 }
109
110 /**
111 * @return void
112 */
113 public function setOnlyFiles()
114 {
115 $this->skipFiles = false;
116 $this->skipDirectories = true;
117 }
118
119 /**
120 * @return void
121 */
122 public function setOnlyDirectories()
123 {
124 $this->skipFiles = true;
125 $this->skipDirectories = false;
126 }
127
128 public function resetFilesDirectoriesSkipping()
129 {
130 $this->skipFiles = false;
131 $this->skipDirectories = false;
132 }
133
134 /**
135 * @param array $excludeRules
136 * @return void
137 */
138 public function setExcludeRules(array $excludeRules)
139 {
140 $this->excludeRules = $excludeRules;
141 }
142
143 /**
144 * @param string $excludeRule
145 * @return void
146 */
147 public function addExcludeRule(string $excludeRule)
148 {
149 $this->excludeRules[] = $excludeRule;
150 }
151
152 /**
153 * @param string $directory
154 * @param bool $processLinks
155 * @param bool $scanLinkDirectory
156 * @return void
157 */
158 public function preScanPath(string $directory, bool $processLinks = false, bool $scanLinkDirectory = true)
159 {
160 $iterator = (new FilterableDirectoryIterator())
161 ->setDirectory(trailingslashit($directory))
162 ->setRecursive(false)
163 ->setSkipDirectoriesWithIncludeRules()
164 ->setDotSkip()
165 ->setWpRootPath($this->rootPath)
166 ->setExcludePaths($this->excludeRules)
167 ->get();
168
169 /** @var SplFileInfo $item */
170 foreach ($iterator as $item) {
171 $isLink = $this->isLinkSafely($item);
172 if ($isLink === null) {
173 continue;
174 }
175
176 if ($isLink && $processLinks) {
177 $this->processLink($item, $scanLinkDirectory);
178 }
179
180 if ($isLink) {
181 continue;
182 }
183
184 $isFile = $this->isFileSafely($item);
185 if ($isFile === null) {
186 continue;
187 }
188
189 if ($isFile && !$this->skipFiles) {
190 $this->processFile($item);
191 }
192
193 if ($isFile) {
194 continue;
195 }
196
197 if ($this->skipDirectories) {
198 continue;
199 }
200
201 $isDir = $this->isDirSafely($item);
202 if ($isDir) {
203 $this->processDirectory($item);
204 }
205 }
206 }
207
208 /**
209 * @param string $path
210 * @return void
211 * @throws Exception
212 */
213 protected function processPath(string $path)
214 {
215 $path = $this->replacePlaceholdersWithEOLs($path);
216 if (empty($path)) {
217 return;
218 }
219
220 list($path, $linkPath) = $this->resolvePath($path);
221
222 $path = untrailingslashit($this->filesystem->normalizePath($path, true));
223
224 if (!file_exists($path)) {
225 throw new Exception("$path is not a directory. Skipping...");
226 }
227
228 $this->preRecursivePathScanningStep();
229 $this->recursivePathScanning($path, $linkPath);
230 }
231
232 /**
233 * @return void
234 */
235 abstract protected function preRecursivePathScanningStep();
236
237 /**
238 * @param SplFileInfo $fileInfo
239 * @param string $linkPath
240 * @return void
241 */
242 abstract protected function processFile(SplFileInfo $fileInfo, string $linkPath = '');
243
244 /**
245 * @param SplFileInfo $fileInfo
246 * @param ?SplFileInfo $linkInfo
247 * @return void
248 */
249 abstract protected function processDirectory(SplFileInfo $fileInfo, $linkInfo = null);
250
251 /**
252 * @param SplFileInfo $linkInfo
253 * @param bool $scanDirectory
254 * @return void
255 */
256 protected function processLink(SplFileInfo $linkInfo, bool $scanDirectory = true)
257 {
258 $isLink = $this->isLinkSafely($linkInfo);
259 if ($isLink === null || !$isLink) {
260 return;
261 }
262
263 $linkTarget = $this->getRealPathSafely($linkInfo);
264 if ($linkTarget === false) {
265 return;
266 }
267
268 $fileInfo = new SplFileInfo($linkTarget);
269
270 $isLink = $this->isLinkSafely($fileInfo);
271 if ($isLink === null || $isLink) {
272 return;
273 }
274
275 $isFile = $this->isFileSafely($fileInfo);
276 if ($isFile === null) {
277 return;
278 }
279
280 if ($isFile) {
281 $this->processFile($fileInfo, $linkInfo->getPathname());
282 return;
283 }
284
285 $isDir = $this->isDirSafely($fileInfo);
286 if ($isDir && $scanDirectory) {
287 $this->processDirectory($fileInfo, $linkInfo);
288 return;
289 }
290 }
291
292 /**
293 * Resolve path on non-wp.com sites (sites with no symlinks structure) to [base_directory, path, '']
294 * Resolve path on wp.com sites (sites with symlinks structure) to [base_directory, path, link]
295 * Where base_directory can be either plugins, mu_plugins, themes, uploads or other etc
296 * Where path is the path to scan
297 * Where link is the link to path (empty in case of non-wp.com sites)
298 * @param string $pathToResolve - Path to resolve in format base_directory::path::link or base_directory::path
299 * @return array [string pathToScan, string linkToPath]
300 */
301 protected function resolvePath(string $pathToResolve): array
302 {
303 $linkPath = '';
304 $pathInfos = explode(self::PATH_SEPARATOR, $pathToResolve);
305 // On non-wp.com sites, we don't have link, we only have base directory and path to scan
306 // On wp.com sites, we have base directory, path to scan and link to path, so path info contains 3 elements
307 if (count($pathInfos) > 2) {
308 // link to path
309 $linkPath = $pathInfos[2];
310 }
311
312 // base directory
313 $this->currentPathScanning = $pathInfos[0];
314
315 // path to scan
316 $path = $pathInfos[1];
317
318 return [$path, $linkPath];
319 }
320
321 /**
322 * @param string $path - Path to scan
323 * @param string $link - If original $path is resolved from link, then this is the link
324 * We need it to keep original path after restore
325 * e.g. $link = /var/www/html/wp-content/themes/twentytwenty is a link to /var/www/libs/themes/twentytwenty (a $path)
326 * @return void
327 * @throws FilesystemExceptions
328 */
329 protected function recursivePathScanning(string $path, string $link = '')
330 {
331 $iterator = (new FilterableDirectoryIterator())
332 ->setDirectory(trailingslashit($path))
333 ->setRecursive(false)
334 ->setDotSkip()
335 ->setWpRootPath($this->rootPath)
336 ->get();
337
338 /** @var SplFileInfo $item */
339 foreach ($iterator as $item) {
340 $isLink = $this->isLinkSafely($item);
341 if ($isLink === null) {
342 continue;
343 }
344
345 // Always check link first otherwise it may be treated as directory
346 if ($isLink) {
347 continue;
348 }
349
350 $linkPath = '';
351 if (!empty($link)) {
352 $linkPath = trailingslashit($link) . $item->getFilename();
353 }
354
355 $isDir = $this->isDirSafely($item);
356 if ($isDir === null) {
357 continue;
358 }
359
360 if ($isDir) {
361 $this->recursivePathScanning($item->getPathname(), $linkPath);
362 continue;
363 }
364
365 $isFile = $this->isFileSafely($item);
366 if ($isFile) {
367 $this->processFile($item, $linkPath);
368 }
369 }
370 }
371 }
372