‪TYPO3CMS  ‪main
PathUtility.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
22 
27 {
33  public static function ‪getRelativePathTo(string $absolutePath): ?string
34  {
35  return ‪self::getRelativePath(self::dirname(‪Environment::getCurrentScript()), $absolutePath);
36  }
37 
52  public static function ‪getAbsoluteWebPath(string $targetPath, bool $prefixWithSitePath = true): string
53  {
54  if (static::hasProtocolAndScheme($targetPath)) {
55  return $targetPath;
56  }
57 
58  $prefixWithSitePath = $prefixWithSitePath && !‪Environment::isCli();
59  if (self::isAbsolutePath($targetPath)) {
60  if (str_starts_with($targetPath, ‪Environment::getPublicPath())) {
61  // It is an absolute file system path with file/folder inside document root,
62  // therefore we can strip the full file system path to the document root to obtain the URI
63  $targetPath = ‪self::stripPathSitePrefix($targetPath);
64  } elseif (‪Environment::isComposerMode() && str_contains($targetPath, 'Resources/Public') && str_starts_with($targetPath, ‪Environment::getComposerRootPath())) {
65  // TYPO3 is in managed by Composer and it is an absolute file system path inside composer root path,
66  // and a public resource is referenced, therefore we can calculate the path to the published assets
67  // This is true for all Composer packages that are installed in vendor folder by Composer, but still recognized by TYPO3
68  $relativePath = substr($targetPath, strlen(‪Environment::getComposerRootPath()));
69  [$relativePrefix, $relativeAssetPath] = explode('Resources/Public', $relativePath);
70  $targetPath = '_assets/' . md5($relativePrefix) . $relativeAssetPath;
71  } else {
72  // At this point it can be ANY path, even an invalid or non existent and it is totally unclear,
73  // whether this is a mistake or accidentally working as intended.
74  // The only conclusion here is, that this API has to be deprecated altogether an be replaced with API
75  // that clearly distinguishes between creating a URL from a static resource and ensuring an URL is absolute and not relative to current script.
76  $prefixWithSitePath = false;
77  }
78  } else {
79  // Make an absolute path out of it
80  $targetPath = GeneralUtility::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $targetPath);
81  $targetPath = ‪self::stripPathSitePrefix($targetPath);
82  }
83 
84  if ($prefixWithSitePath) {
85  $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
86  }
87 
88  return $targetPath;
89  }
90 
97  public static function ‪getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath = true): string
98  {
99  if (!self::isExtensionPath($resourcePath)) {
100  throw new ‪InvalidFileException('Resource paths must start with "EXT:"', 1630089406);
101  }
102  $absoluteFilePath = GeneralUtility::getFileAbsFileName($resourcePath);
103  if (!str_contains($resourcePath, 'Resources/Public')) {
104  if (!str_starts_with($absoluteFilePath, ‪Environment::getPublicPath())) {
105  // This will be thrown in Composer mode, when extension are installed in vendor folder
106  throw new ‪InvalidFileException(sprintf('"%s" is expected to be in public directory, but is not', $resourcePath), 1635268969);
107  }
108  trigger_error(sprintf('Public resource "%s" is not in extension\'s Resources/Public folder. This is deprecated and will not be supported any more in future TYPO3 versions.', $resourcePath), E_USER_DEPRECATED);
109  }
110 
111  return ‪self::getAbsoluteWebPath($absoluteFilePath, $prefixWithSitePath);
112  }
113 
117  public static function ‪isExtensionPath(string $path): bool
118  {
119  return str_starts_with($path, 'EXT:');
120  }
121 
129  public static function ‪getRelativePath(string $sourcePath, string $targetPath): ?string
130  {
131  $relativePath = null;
132  $sourcePath = rtrim(GeneralUtility::fixWindowsFilePath($sourcePath), '/');
133  $targetPath = rtrim(GeneralUtility::fixWindowsFilePath($targetPath), '/');
134  if ($sourcePath !== $targetPath) {
135  $commonPrefix = ‪self::getCommonPrefix([$sourcePath, $targetPath]);
136  if ($commonPrefix !== null && GeneralUtility::isAllowedAbsPath($commonPrefix)) {
137  $commonPrefixLength = strlen($commonPrefix);
138  $resolvedSourcePath = '';
139  $resolvedTargetPath = '';
140  $sourcePathSteps = 0;
141  if (strlen($sourcePath) > $commonPrefixLength) {
142  $resolvedSourcePath = (string)substr($sourcePath, $commonPrefixLength);
143  }
144  if (strlen($targetPath) > $commonPrefixLength) {
145  $resolvedTargetPath = (string)substr($targetPath, $commonPrefixLength);
146  }
147  if ($resolvedSourcePath !== '') {
148  $sourcePathSteps = count(explode('/', $resolvedSourcePath));
149  }
150  $relativePath = ‪self::sanitizeTrailingSeparator(str_repeat('../', $sourcePathSteps) . $resolvedTargetPath);
151  }
152  }
153  return $relativePath;
154  }
155 
165  public static function ‪getCommonPrefix(array $paths): ?string
166  {
167  $paths = array_map(GeneralUtility::fixWindowsFilePath(...), $paths);
168  $commonPath = null;
169  if (count($paths) === 1) {
170  $commonPath = array_shift($paths);
171  } elseif (count($paths) > 1) {
172  $parts = explode('/', (string)array_shift($paths));
173  $comparePath = '';
174  $break = false;
175  foreach ($parts as $part) {
176  $comparePath .= $part . '/';
177  foreach ($paths as $path) {
178  if (!str_starts_with($path . '/', $comparePath)) {
179  $break = true;
180  break;
181  }
182  }
183  if ($break) {
184  break;
185  }
186  $commonPath = $comparePath;
187  }
188  }
189  if ($commonPath !== null) {
190  $commonPath = ‪self::sanitizeTrailingSeparator($commonPath, '/');
191  }
192  return $commonPath;
193  }
194 
203  public static function ‪sanitizeTrailingSeparator(string $path, string $separator = '/'): string
204  {
205  return rtrim($path, $separator) . $separator;
206  }
207 
219  public static function ‪basename(string $path): string
220  {
221  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
222  if (empty($targetLocale)) {
223  return ‪basename($path);
224  }
225  $currentLocale = (string)setlocale(LC_CTYPE, '0');
226  setlocale(LC_CTYPE, $targetLocale);
227  $basename = ‪basename($path);
228  setlocale(LC_CTYPE, $currentLocale);
229  return $basename;
230  }
231 
243  public static function ‪dirname(string $path): string
244  {
245  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
246  if (empty($targetLocale)) {
247  return ‪dirname($path);
248  }
249  $currentLocale = (string)setlocale(LC_CTYPE, '0');
250  setlocale(LC_CTYPE, $targetLocale);
251  $dirname = ‪dirname($path);
252  setlocale(LC_CTYPE, $currentLocale);
253  return $dirname;
254  }
255 
270  public static function ‪pathinfo(string $path, int $options = PATHINFO_ALL): string|array
271  {
272  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
273  if (empty($targetLocale)) {
274  return ‪pathinfo($path, $options);
275  }
276  $currentLocale = (string)setlocale(LC_CTYPE, '0');
277  setlocale(LC_CTYPE, $targetLocale);
278  $pathinfo = ‪pathinfo($path, $options);
279  setlocale(LC_CTYPE, $currentLocale);
280  return $pathinfo;
281  }
282 
286  public static function ‪isAbsolutePath(string $path): bool
287  {
288  // On Windows also a path starting with a drive letter is absolute: X:/
289  if (‪Environment::isWindows() && (substr($path, 1, 2) === ':/' || substr($path, 1, 2) === ':\\')) {
290  return true;
291  }
292  // Path starting with a / is always absolute, on every system, VFS is needed for tests
293  return str_starts_with($path, '/') || str_starts_with($path, 'vfs://');
294  }
295 
319  public static function ‪getAbsolutePathOfRelativeReferencedFileOrPath(string $baseFilenameOrPath, string $includeFileName): string
320  {
321  $fileName = static::basename($includeFileName);
322  $basePath = str_ends_with($baseFilenameOrPath, '/') ? $baseFilenameOrPath : static::dirname($baseFilenameOrPath);
323  $newDir = static::getCanonicalPath($basePath . '/' . static::dirname($includeFileName));
324  // Avoid double slash on empty path
325  return (($newDir !== '/') ? $newDir : '') . '/' . $fileName;
326  }
327 
337  public static function ‪dirnameDuringBootstrap(string $path): string
338  {
339  return preg_replace('#(.*)(/|\\\\)([^\\\\/]+)$#', '$1', $path);
340  }
341 
348  public static function ‪basenameDuringBootstrap(string $path): string
349  {
350  return preg_replace('#.*[/\\\\]([^\\\\/]+)$#', '$1', $path);
351  }
352 
353  /*********************
354  *
355  * Cleaning methods
356  *
357  *********************/
364  public static function ‪getCanonicalPath(string $path): string
365  {
366  // Replace backslashes with slashes to work with Windows paths if given
367  $path = trim(str_replace('\\', '/', $path));
368 
369  // @todo do we really need this? Probably only in testing context for vfs?
370  $protocol = '';
371  if (str_contains($path, '://')) {
372  [$protocol, $path] = explode('://', $path);
373  $protocol .= '://';
374  }
375 
376  $absolutePathPrefix = '';
377  if (static::isAbsolutePath($path)) {
378  if (‪Environment::isWindows() && substr($path, 1, 2) === ':/') {
379  $absolutePathPrefix = substr($path, 0, 3);
380  $path = substr($path, 3);
381  } else {
382  $path = ltrim($path, '/');
383  $absolutePathPrefix = '/';
384  }
385  }
386 
387  $theDirParts = explode('/', $path);
388  $theDirPartsCount = count($theDirParts);
389  // This cannot use a foreach() as some steps skip ahead multiple elements.
390  for ($partCount = 0; $partCount < $theDirPartsCount; $partCount++) {
391  // double-slashes in path: remove element
392  if ($theDirParts[$partCount] === '') {
393  array_splice($theDirParts, $partCount, 1);
394  $partCount--;
395  $theDirPartsCount--;
396  }
397  // "." in path: remove element
398  if (($theDirParts[$partCount] ?? '') === '.') {
399  array_splice($theDirParts, $partCount, 1);
400  $partCount--;
401  $theDirPartsCount--;
402  }
403  // ".." in path:
404  if (($theDirParts[$partCount] ?? '') === '..') {
405  if ($partCount >= 1) {
406  // Remove this and previous element
407  array_splice($theDirParts, $partCount - 1, 2);
408  $partCount -= 2;
409  $theDirPartsCount -= 2;
410  } elseif ($absolutePathPrefix) {
411  // can't go higher than root dir
412  // simply remove this part and continue
413  array_splice($theDirParts, $partCount, 1);
414  $partCount--;
415  $theDirPartsCount--;
416  }
417  }
418  }
419 
420  return $protocol . $absolutePathPrefix . implode('/', $theDirParts);
421  }
422 
428  public static function ‪stripPathSitePrefix(string $path): string
429  {
430  return substr($path, strlen(‪Environment::getPublicPath() . '/'));
431  }
432 
445  public static function ‪hasProtocolAndScheme(string $path): bool
446  {
447  return str_starts_with($path, '//') || strpos($path, '://') > 0;
448  }
449 
457  public static function ‪isAllowedAdditionalPath(string $path): bool
458  {
459  // ensure the submitted path ends with a string, even for a file
460  $path = ‪self::sanitizeTrailingSeparator($path);
461  $allowedPaths = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? [];
462  if (is_string($allowedPaths)) {
463  // The setting was a string before and is now an array
464  // For compatibility reasons, we cast a string to an array here for now
465  $allowedPaths = [$allowedPaths];
466  }
467  if (!is_array($allowedPaths)) {
468  throw new \RuntimeException('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'lockRootPath\'] is expected to be an array.', 1707408379);
469  }
470  foreach ($allowedPaths as $allowedPath) {
471  $allowedPath = trim($allowedPath);
472  if ($allowedPath !== '' && str_starts_with($path, self::sanitizeTrailingSeparator($allowedPath))) {
473  return true;
474  }
475  }
476  return false;
477  }
478 }
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath(string $path)
Definition: PathUtility.php:364
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static stripPathSitePrefix(string $path)
Definition: PathUtility.php:428
‪TYPO3\CMS\Core\Utility\PathUtility\isExtensionPath
‪static isExtensionPath(string $path)
Definition: PathUtility.php:117
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static isAbsolutePath(string $path)
Definition: PathUtility.php:286
‪TYPO3\CMS\Core\Utility\PathUtility\sanitizeTrailingSeparator
‪static sanitizeTrailingSeparator(string $path, string $separator='/')
Definition: PathUtility.php:203
‪TYPO3\CMS\Core\Core\Environment\getComposerRootPath
‪static string getComposerRootPath()
Definition: Environment.php:174
‪TYPO3\CMS\Core\Utility\PathUtility\isAllowedAdditionalPath
‪static isAllowedAdditionalPath(string $path)
Definition: PathUtility.php:457
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static isComposerMode()
Definition: Environment.php:137
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Utility\PathUtility\getRelativePathTo
‪static getRelativePathTo(string $absolutePath)
Definition: PathUtility.php:33
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:18
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static getCurrentScript()
Definition: Environment.php:220
‪TYPO3\CMS\Core\Utility\PathUtility\getCommonPrefix
‪static getCommonPrefix(array $paths)
Definition: PathUtility.php:165
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath(string $targetPath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:52
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsolutePathOfRelativeReferencedFileOrPath
‪static string getAbsolutePathOfRelativeReferencedFileOrPath(string $baseFilenameOrPath, string $includeFileName)
Definition: PathUtility.php:319
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static dirname(string $path)
Definition: PathUtility.php:243
‪TYPO3\CMS\Core\Utility\PathUtility\getPublicResourceWebPath
‪static getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:97
‪TYPO3\CMS\Core\Utility\PathUtility\getRelativePath
‪static getRelativePath(string $sourcePath, string $targetPath)
Definition: PathUtility.php:129
‪TYPO3\CMS\Core\Utility\PathUtility\basenameDuringBootstrap
‪static basenameDuringBootstrap(string $path)
Definition: PathUtility.php:348
‪TYPO3\CMS\Core\Utility\PathUtility\dirnameDuringBootstrap
‪static string dirnameDuringBootstrap(string $path)
Definition: PathUtility.php:337
‪TYPO3\CMS\Core\Resource\Exception\InvalidFileException
Definition: InvalidFileException.php:23
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static isCli()
Definition: Environment.php:145
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Utility\PathUtility\hasProtocolAndScheme
‪static hasProtocolAndScheme(string $path)
Definition: PathUtility.php:445
‪TYPO3\CMS\Core\Utility\PathUtility\pathinfo
‪static string string[] pathinfo(string $path, int $options=PATHINFO_ALL)
Definition: PathUtility.php:270
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static isWindows()
Definition: Environment.php:276