PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.89
WindPress – Tailwind CSS integration for WordPress v3.2.89
3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 All 143 releases
windpress / vendor / symfony / filesystem / Path.php

Path.php in WindPress – Tailwind CSS integration for WordPress 3.2.89, at vendor/symfony/filesystem/Path.php

709 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace WindPressDeps\Symfony\Component\Filesystem;
12
13 use WindPressDeps\Symfony\Component\Filesystem\Exception\InvalidArgumentException;
14 use WindPressDeps\Symfony\Component\Filesystem\Exception\RuntimeException;
15 /**
16 * Contains utility methods for handling path strings.
17 *
18 * The methods in this class are able to deal with both UNIX and Windows paths
19 * with both forward and backward slashes. All methods return normalized parts
20 * containing only forward slashes and no excess "." and ".." segments.
21 *
22 * @author Bernhard Schussek <bschussek@gmail.com>
23 * @author Thomas Schulz <mail@king2500.net>
24 * @author Théo Fidry <theo.fidry@gmail.com>
25 */
26 final class Path
27 {
28 /**
29 * The number of buffer entries that triggers a cleanup operation.
30 */
31 private const CLEANUP_THRESHOLD = 1250;
32 /**
33 * The buffer size after the cleanup operation.
34 */
35 private const CLEANUP_SIZE = 1000;
36 /**
37 * Buffers input/output of {@link canonicalize()}.
38 *
39 * @var array<string, string>
40 */
41 private static $buffer = [];
42 /**
43 * @var int
44 */
45 private static $bufferSize = 0;
46 /**
47 * Canonicalizes the given path.
48 *
49 * During normalization, all slashes are replaced by forward slashes ("/").
50 * Furthermore, all "." and ".." segments are removed as far as possible.
51 * ".." segments at the beginning of relative paths are not removed.
52 *
53 * ```php
54 * echo Path::canonicalize("\symfony\puli\..\css\style.css");
55 * // => /symfony/css/style.css
56 *
57 * echo Path::canonicalize("../css/./style.css");
58 * // => ../css/style.css
59 * ```
60 *
61 * This method is able to deal with both UNIX and Windows paths.
62 */
63 public static function canonicalize(string $path): string
64 {
65 if ('' === $path) {
66 return '';
67 }
68 // This method is called by many other methods in this class. Buffer
69 // the canonicalized paths to make up for the severe performance
70 // decrease.
71 if (isset(self::$buffer[$path])) {
72 return self::$buffer[$path];
73 }
74 // Replace "~" with user's home directory.
75 if ('~' === $path[0]) {
76 $path = self::getHomeDirectory() . substr($path, 1);
77 }
78 $path = self::normalize($path);
79 [$root, $pathWithoutRoot] = self::split($path);
80 $canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot);
81 // Add the root directory again
82 self::$buffer[$path] = $canonicalPath = $root . implode('/', $canonicalParts);
83 ++self::$bufferSize;
84 // Clean up regularly to prevent memory leaks
85 if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
86 self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, \true);
87 self::$bufferSize = self::CLEANUP_SIZE;
88 }
89 return $canonicalPath;
90 }
91 /**
92 * Normalizes the given path.
93 *
94 * During normalization, all slashes are replaced by forward slashes ("/").
95 * Contrary to {@link canonicalize()}, this method does not remove invalid
96 * or dot path segments. Consequently, it is much more efficient and should
97 * be used whenever the given path is known to be a valid, absolute system
98 * path.
99 *
100 * This method is able to deal with both UNIX and Windows paths.
101 */
102 public static function normalize(string $path): string
103 {
104 return str_replace('\\', '/', $path);
105 }
106 /**
107 * Returns the directory part of the path.
108 *
109 * This method is similar to PHP's dirname(), but handles various cases
110 * where dirname() returns a weird result:
111 *
112 * - dirname() does not accept backslashes on UNIX
113 * - dirname("C:/symfony") returns "C:", not "C:/"
114 * - dirname("C:/") returns ".", not "C:/"
115 * - dirname("C:") returns ".", not "C:/"
116 * - dirname("symfony") returns ".", not ""
117 * - dirname() does not canonicalize the result
118 *
119 * This method fixes these shortcomings and behaves like dirname()
120 * otherwise.
121 *
122 * The result is a canonical path.
123 *
124 * @return string The canonical directory part. Returns the root directory
125 * if the root directory is passed. Returns an empty string
126 * if a relative path is passed that contains no slashes.
127 * Returns an empty string if an empty string is passed.
128 */
129 public static function getDirectory(string $path): string
130 {
131 if ('' === $path) {
132 return '';
133 }
134 $path = self::canonicalize($path);
135 // Maintain scheme
136 if (\false !== $schemeSeparatorPosition = strpos($path, '://')) {
137 $scheme = substr($path, 0, $schemeSeparatorPosition + 3);
138 $path = substr($path, $schemeSeparatorPosition + 3);
139 } else {
140 $scheme = '';
141 }
142 if (\false === $dirSeparatorPosition = strrpos($path, '/')) {
143 return '';
144 }
145 // Directory equals root directory "/"
146 if (0 === $dirSeparatorPosition) {
147 return $scheme . '/';
148 }
149 // Directory equals Windows root "C:/"
150 if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) {
151 return $scheme . substr($path, 0, 3);
152 }
153 return $scheme . substr($path, 0, $dirSeparatorPosition);
154 }
155 /**
156 * Returns canonical path of the user's home directory.
157 *
158 * Supported operating systems:
159 *
160 * - UNIX
161 * - Windows8 and upper
162 *
163 * If your operating system or environment isn't supported, an exception is thrown.
164 *
165 * The result is a canonical path.
166 *
167 * @throws RuntimeException If your operating system or environment isn't supported
168 */
169 public static function getHomeDirectory(): string
170 {
171 // For UNIX support
172 if (getenv('HOME')) {
173 return self::canonicalize(getenv('HOME'));
174 }
175 // For >= Windows8 support
176 if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
177 return self::canonicalize(getenv('HOMEDRIVE') . getenv('HOMEPATH'));
178 }
179 throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
180 }
181 /**
182 * Returns the root directory of a path.
183 *
184 * The result is a canonical path.
185 *
186 * @return string The canonical root directory. Returns an empty string if
187 * the given path is relative or empty.
188 */
189 public static function getRoot(string $path): string
190 {
191 if ('' === $path) {
192 return '';
193 }
194 // Maintain scheme
195 if (\false !== $schemeSeparatorPosition = strpos($path, '://')) {
196 $scheme = substr($path, 0, $schemeSeparatorPosition + 3);
197 $path = substr($path, $schemeSeparatorPosition + 3);
198 } else {
199 $scheme = '';
200 }
201 $firstCharacter = $path[0];
202 // UNIX root "/" or "\" (Windows style)
203 if ('/' === $firstCharacter || '\\' === $firstCharacter) {
204 return $scheme . '/';
205 }
206 $length = \strlen($path);
207 // Windows root
208 if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) {
209 // Special case: "C:"
210 if (2 === $length) {
211 return $scheme . $path . '/';
212 }
213 // Normal case: "C:/ or "C:\"
214 if ('/' === $path[2] || '\\' === $path[2]) {
215 return $scheme . $firstCharacter . $path[1] . '/';
216 }
217 }
218 return '';
219 }
220 /**
221 * Returns the file name without the extension from a file path.
222 *
223 * @param string|null $extension if specified, only that extension is cut
224 * off (may contain leading dot)
225 */
226 public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string
227 {
228 if ('' === $path) {
229 return '';
230 }
231 if (null !== $extension) {
232 // remove extension and trailing dot
233 return rtrim(basename($path, $extension), '.');
234 }
235 return pathinfo($path, \PATHINFO_FILENAME);
236 }
237 /**
238 * Returns the extension from a file path (without leading dot).
239 *
240 * @param bool $forceLowerCase forces the extension to be lower-case
241 */
242 public static function getExtension(string $path, bool $forceLowerCase = \false): string
243 {
244 if ('' === $path) {
245 return '';
246 }
247 $extension = pathinfo($path, \PATHINFO_EXTENSION);
248 if ($forceLowerCase) {
249 $extension = self::toLower($extension);
250 }
251 return $extension;
252 }
253 /**
254 * Returns whether the path has an (or the specified) extension.
255 *
256 * @param string $path the path string
257 * @param string|string[]|null $extensions if null or not provided, checks if
258 * an extension exists, otherwise
259 * checks for the specified extension
260 * or array of extensions (with or
261 * without leading dot)
262 * @param bool $ignoreCase whether to ignore case-sensitivity
263 */
264 public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = \false): bool
265 {
266 if ('' === $path) {
267 return \false;
268 }
269 $actualExtension = self::getExtension($path, $ignoreCase);
270 // Only check if path has any extension
271 if ([] === $extensions || null === $extensions) {
272 return '' !== $actualExtension;
273 }
274 if (\is_string($extensions)) {
275 $extensions = [$extensions];
276 }
277 foreach ($extensions as $key => $extension) {
278 if ($ignoreCase) {
279 $extension = self::toLower($extension);
280 }
281 // remove leading '.' in extensions array
282 $extensions[$key] = ltrim($extension, '.');
283 }
284 return \in_array($actualExtension, $extensions, \true);
285 }
286 /**
287 * Changes the extension of a path string.
288 *
289 * @param string $path The path string with filename.ext to change.
290 * @param string $extension new extension (with or without leading dot)
291 *
292 * @return string the path string with new file extension
293 */
294 public static function changeExtension(string $path, string $extension): string
295 {
296 if ('' === $path) {
297 return '';
298 }
299 $actualExtension = self::getExtension($path);
300 $extension = ltrim($extension, '.');
301 // No extension for paths
302 if ('/' === substr($path, -1)) {
303 return $path;
304 }
305 // No actual extension in path
306 if (empty($actualExtension)) {
307 return $path . ('.' === substr($path, -1) ? '' : '.') . $extension;
308 }
309 return substr($path, 0, -\strlen($actualExtension)) . $extension;
310 }
311 public static function isAbsolute(string $path): bool
312 {
313 if ('' === $path) {
314 return \false;
315 }
316 // Strip scheme
317 if (\false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
318 $path = substr($path, $schemeSeparatorPosition + 3);
319 }
320 $firstCharacter = $path[0];
321 // UNIX root "/" or "\" (Windows style)
322 if ('/' === $firstCharacter || '\\' === $firstCharacter) {
323 return \true;
324 }
325 // Windows root
326 if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
327 // Special case: "C:"
328 if (2 === \strlen($path)) {
329 return \true;
330 }
331 // Normal case: "C:/ or "C:\"
332 if ('/' === $path[2] || '\\' === $path[2]) {
333 return \true;
334 }
335 }
336 return \false;
337 }
338 public static function isRelative(string $path): bool
339 {
340 return !self::isAbsolute($path);
341 }
342 /**
343 * Turns a relative path into an absolute path in canonical form.
344 *
345 * Usually, the relative path is appended to the given base path. Dot
346 * segments ("." and "..") are removed/collapsed and all slashes turned
347 * into forward slashes.
348 *
349 * ```php
350 * echo Path::makeAbsolute("../style.css", "/symfony/puli/css");
351 * // => /symfony/puli/style.css
352 * ```
353 *
354 * If an absolute path is passed, that path is returned unless its root
355 * directory is different than the one of the base path. In that case, an
356 * exception is thrown.
357 *
358 * ```php
359 * Path::makeAbsolute("/style.css", "/symfony/puli/css");
360 * // => /style.css
361 *
362 * Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css");
363 * // => C:/style.css
364 *
365 * Path::makeAbsolute("C:/style.css", "/symfony/puli/css");
366 * // InvalidArgumentException
367 * ```
368 *
369 * If the base path is not an absolute path, an exception is thrown.
370 *
371 * The result is a canonical path.
372 *
373 * @param string $basePath an absolute base path
374 *
375 * @throws InvalidArgumentException if the base path is not absolute or if
376 * the given path is an absolute path with
377 * a different root than the base path
378 */
379 public static function makeAbsolute(string $path, string $basePath): string
380 {
381 if ('' === $basePath) {
382 throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
383 }
384 if (!self::isAbsolute($basePath)) {
385 throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath));
386 }
387 if (self::isAbsolute($path)) {
388 return self::canonicalize($path);
389 }
390 if (\false !== $schemeSeparatorPosition = strpos($basePath, '://')) {
391 $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
392 $basePath = substr($basePath, $schemeSeparatorPosition + 3);
393 } else {
394 $scheme = '';
395 }
396 return $scheme . self::canonicalize(rtrim($basePath, '/\\') . '/' . $path);
397 }
398 /**
399 * Turns a path into a relative path.
400 *
401 * The relative path is created relative to the given base path:
402 *
403 * ```php
404 * echo Path::makeRelative("/symfony/style.css", "/symfony/puli");
405 * // => ../style.css
406 * ```
407 *
408 * If a relative path is passed and the base path is absolute, the relative
409 * path is returned unchanged:
410 *
411 * ```php
412 * Path::makeRelative("style.css", "/symfony/puli/css");
413 * // => style.css
414 * ```
415 *
416 * If both paths are relative, the relative path is created with the
417 * assumption that both paths are relative to the same directory:
418 *
419 * ```php
420 * Path::makeRelative("style.css", "symfony/puli/css");
421 * // => ../../../style.css
422 * ```
423 *
424 * If both paths are absolute, their root directory must be the same,
425 * otherwise an exception is thrown:
426 *
427 * ```php
428 * Path::makeRelative("C:/symfony/style.css", "/symfony/puli");
429 * // InvalidArgumentException
430 * ```
431 *
432 * If the passed path is absolute, but the base path is not, an exception
433 * is thrown as well:
434 *
435 * ```php
436 * Path::makeRelative("/symfony/style.css", "symfony/puli");
437 * // InvalidArgumentException
438 * ```
439 *
440 * If the base path is not an absolute path, an exception is thrown.
441 *
442 * The result is a canonical path.
443 *
444 * @throws InvalidArgumentException if the base path is not absolute or if
445 * the given path has a different root
446 * than the base path
447 */
448 public static function makeRelative(string $path, string $basePath): string
449 {
450 $path = self::canonicalize($path);
451 $basePath = self::canonicalize($basePath);
452 [$root, $relativePath] = self::split($path);
453 [$baseRoot, $relativeBasePath] = self::split($basePath);
454 // If the base path is given as absolute path and the path is already
455 // relative, consider it to be relative to the given absolute path
456 // already
457 if ('' === $root && '' !== $baseRoot) {
458 // If base path is already in its root
459 if ('' === $relativeBasePath) {
460 $relativePath = ltrim($relativePath, './\\');
461 }
462 return $relativePath;
463 }
464 // If the passed path is absolute, but the base path is not, we
465 // cannot generate a relative path
466 if ('' !== $root && '' === $baseRoot) {
467 throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
468 }
469 // Fail if the roots of the two paths are different
470 if ($baseRoot && $root !== $baseRoot) {
471 throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
472 }
473 if ('' === $relativeBasePath) {
474 return $relativePath;
475 }
476 // Build a "../../" prefix with as many "../" parts as necessary
477 $parts = explode('/', $relativePath);
478 $baseParts = explode('/', $relativeBasePath);
479 $dotDotPrefix = '';
480 // Once we found a non-matching part in the prefix, we need to add
481 // "../" parts for all remaining parts
482 $match = \true;
483 foreach ($baseParts as $index => $basePart) {
484 if ($match && isset($parts[$index]) && $basePart === $parts[$index]) {
485 unset($parts[$index]);
486 continue;
487 }
488 $match = \false;
489 $dotDotPrefix .= '../';
490 }
491 return rtrim($dotDotPrefix . implode('/', $parts), '/');
492 }
493 /**
494 * Returns whether the given path is on the local filesystem.
495 */
496 public static function isLocal(string $path): bool
497 {
498 return '' !== $path && \false === strpos($path, '://');
499 }
500 /**
501 * Returns the longest common base path in canonical form of a set of paths or
502 * `null` if the paths are on different Windows partitions.
503 *
504 * Dot segments ("." and "..") are removed/collapsed and all slashes turned
505 * into forward slashes.
506 *
507 * ```php
508 * $basePath = Path::getLongestCommonBasePath(
509 * '/symfony/css/style.css',
510 * '/symfony/css/..'
511 * );
512 * // => /symfony
513 * ```
514 *
515 * The root is returned if no common base path can be found:
516 *
517 * ```php
518 * $basePath = Path::getLongestCommonBasePath(
519 * '/symfony/css/style.css',
520 * '/puli/css/..'
521 * );
522 * // => /
523 * ```
524 *
525 * If the paths are located on different Windows partitions, `null` is
526 * returned.
527 *
528 * ```php
529 * $basePath = Path::getLongestCommonBasePath(
530 * 'C:/symfony/css/style.css',
531 * 'D:/symfony/css/..'
532 * );
533 * // => null
534 * ```
535 */
536 public static function getLongestCommonBasePath(string ...$paths): ?string
537 {
538 [$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths)));
539 for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
540 [$root, $path] = self::split(self::canonicalize(current($paths)));
541 // If we deal with different roots (e.g. C:/ vs. D:/), it's time
542 // to quit
543 if ($root !== $bpRoot) {
544 return null;
545 }
546 // Make the base path shorter until it fits into path
547 while (\true) {
548 if ('.' === $basePath) {
549 // No more base paths
550 $basePath = '';
551 // next path
552 continue 2;
553 }
554 // Prevent false positives for common prefixes
555 // see isBasePath()
556 if (0 === strpos($path . '/', $basePath . '/')) {
557 // next path
558 continue 2;
559 }
560 $basePath = \dirname($basePath);
561 }
562 }
563 return $bpRoot . $basePath;
564 }
565 /**
566 * Joins two or more path strings into a canonical path.
567 */
568 public static function join(string ...$paths): string
569 {
570 $finalPath = null;
571 $wasScheme = \false;
572 foreach ($paths as $path) {
573 if ('' === $path) {
574 continue;
575 }
576 if (null === $finalPath) {
577 // For first part we keep slashes, like '/top', 'C:\' or 'phar://'
578 $finalPath = $path;
579 $wasScheme = \false !== strpos($path, '://');
580 continue;
581 }
582 // Only add slash if previous part didn't end with '/' or '\'
583 if (!\in_array(substr($finalPath, -1), ['/', '\\'])) {
584 $finalPath .= '/';
585 }
586 // If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim
587 $finalPath .= $wasScheme ? $path : ltrim($path, '/');
588 $wasScheme = \false;
589 }
590 if (null === $finalPath) {
591 return '';
592 }
593 return self::canonicalize($finalPath);
594 }
595 /**
596 * Returns whether a path is a base path of another path.
597 *
598 * Dot segments ("." and "..") are removed/collapsed and all slashes turned
599 * into forward slashes.
600 *
601 * ```php
602 * Path::isBasePath('/symfony', '/symfony/css');
603 * // => true
604 *
605 * Path::isBasePath('/symfony', '/symfony');
606 * // => true
607 *
608 * Path::isBasePath('/symfony', '/symfony/..');
609 * // => false
610 *
611 * Path::isBasePath('/symfony', '/puli');
612 * // => false
613 * ```
614 */
615 public static function isBasePath(string $basePath, string $ofPath): bool
616 {
617 $basePath = self::canonicalize($basePath);
618 $ofPath = self::canonicalize($ofPath);
619 // Append slashes to prevent false positives when two paths have
620 // a common prefix, for example /base/foo and /base/foobar.
621 // Don't append a slash for the root "/", because then that root
622 // won't be discovered as common prefix ("//" is not a prefix of
623 // "/foobar/").
624 return 0 === strpos($ofPath . '/', rtrim($basePath, '/') . '/');
625 }
626 /**
627 * @return string[]
628 */
629 private static function findCanonicalParts(string $root, string $pathWithoutRoot): array
630 {
631 $parts = explode('/', $pathWithoutRoot);
632 $canonicalParts = [];
633 // Collapse "." and "..", if possible
634 foreach ($parts as $part) {
635 if ('.' === $part || '' === $part) {
636 continue;
637 }
638 // Collapse ".." with the previous part, if one exists
639 // Don't collapse ".." if the previous part is also ".."
640 if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) {
641 array_pop($canonicalParts);
642 continue;
643 }
644 // Only add ".." prefixes for relative paths
645 if ('..' !== $part || '' === $root) {
646 $canonicalParts[] = $part;
647 }
648 }
649 return $canonicalParts;
650 }
651 /**
652 * Splits a canonical path into its root directory and the remainder.
653 *
654 * If the path has no root directory, an empty root directory will be
655 * returned.
656 *
657 * If the root directory is a Windows style partition, the resulting root
658 * will always contain a trailing slash.
659 *
660 * list ($root, $path) = Path::split("C:/symfony")
661 * // => ["C:/", "symfony"]
662 *
663 * list ($root, $path) = Path::split("C:")
664 * // => ["C:/", ""]
665 *
666 * @return array{string, string} an array with the root directory and the remaining relative path
667 */
668 private static function split(string $path): array
669 {
670 if ('' === $path) {
671 return ['', ''];
672 }
673 // Remember scheme as part of the root, if any
674 if (\false !== $schemeSeparatorPosition = strpos($path, '://')) {
675 $root = substr($path, 0, $schemeSeparatorPosition + 3);
676 $path = substr($path, $schemeSeparatorPosition + 3);
677 } else {
678 $root = '';
679 }
680 $length = \strlen($path);
681 // Remove and remember root directory
682 if (0 === strpos($path, '/')) {
683 $root .= '/';
684 $path = $length > 1 ? substr($path, 1) : '';
685 } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
686 if (2 === $length) {
687 // Windows special case: "C:"
688 $root .= $path . '/';
689 $path = '';
690 } elseif ('/' === $path[2]) {
691 // Windows normal case: "C:/"..
692 $root .= substr($path, 0, 3);
693 $path = $length > 3 ? substr($path, 3) : '';
694 }
695 }
696 return [$root, $path];
697 }
698 private static function toLower(string $string): string
699 {
700 if (\false !== $encoding = mb_detect_encoding($string, null, \true)) {
701 return mb_strtolower($string, $encoding);
702 }
703 return strtolower($string);
704 }
705 private function __construct()
706 {
707 }
708 }
709