‪TYPO3CMS  11.5
PathUtility.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
20 
25 {
33  public static function ‪getRelativePathTo($targetPath)
34  {
35  return ‪self::getRelativePath(self::dirname(‪Environment::getCurrentScript()), $targetPath);
36  }
37 
51  public static function ‪getAbsoluteWebPath($targetPath, bool $prefixWithSitePath = true)
52  {
53  if (static::hasProtocolAndScheme($targetPath)) {
54  return $targetPath;
55  }
56 
57  $prefixWithSitePath = $prefixWithSitePath && !‪Environment::isCli();
58  if (self::isAbsolutePath($targetPath)) {
59  if (str_starts_with($targetPath, ‪Environment::getPublicPath())) {
60  // It is an absolute file system path with file/folder inside document root,
61  // therefore we can strip the full file system path to the document root to obtain the URI
62  $targetPath = ‪self::stripPathSitePrefix($targetPath);
63  } elseif (‪Environment::isComposerMode() && str_contains($targetPath, 'Resources/Public') && str_starts_with($targetPath, ‪Environment::getComposerRootPath())) {
64  // TYPO3 is in managed by Composer and it is an absolute file system path inside composer root path,
65  // and a public resource is referenced, therefore we can calculate the path to the published assets
66  // This is true for all Composer packages that are installed in vendor folder by Composer, but still recognized by TYPO3
67  $relativePath = substr($targetPath, strlen(‪Environment::getComposerRootPath()));
68  [$relativePrefix, $relativeAssetPath] = explode('Resources/Public', $relativePath);
69  $targetPath = '_assets/' . md5($relativePrefix) . $relativeAssetPath;
70  } else {
71  // At this point it can be ANY path, even an invalid or non existent and it is totally unclear,
72  // whether this is a mistake or accidentally working as intended.
73  // The only conclusion here is, that this API has to be deprecated altogether an be replaced with API
74  // that clearly distinguishes between creating a URL from a static resource and ensuring an URL is absolute and not relative to current script.
75  $prefixWithSitePath = false;
76  }
77  } else {
78  // Make an absolute path out of it
79  $targetPath = GeneralUtility::resolveBackPath(self::dirname(‪Environment::getCurrentScript()) . '/' . $targetPath);
80  $targetPath = ‪self::stripPathSitePrefix($targetPath);
81  }
82 
83  if ($prefixWithSitePath) {
84  $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
85  }
86 
87  return $targetPath;
88  }
89 
98  public static function ‪getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath = true): string
99  {
100  if (!self::isExtensionPath($resourcePath)) {
101  throw new ‪InvalidFileException('Resource paths must start with "EXT:"', 1630089406);
102  }
103  $absoluteFilePath = GeneralUtility::getFileAbsFileName($resourcePath);
104  if (!str_contains($resourcePath, 'Resources/Public')) {
105  if (!str_starts_with($absoluteFilePath, ‪Environment::getPublicPath())) {
106  // This will be thrown in Composer mode, when extension are installed in vendor folder
107  throw new ‪InvalidFileException(sprintf('"%s" is expected to be in public directory, but is not', $resourcePath), 1635268969);
108  }
109  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);
110  }
111 
112  return ‪self::getAbsoluteWebPath($absoluteFilePath, $prefixWithSitePath);
113  }
114 
121  public static function ‪isExtensionPath(string $path): bool
122  {
123  return str_starts_with($path, 'EXT:');
124  }
125 
134  public static function ‪getRelativePath($sourcePath, $targetPath)
135  {
136  $relativePath = null;
137  $sourcePath = rtrim(GeneralUtility::fixWindowsFilePath($sourcePath), '/');
138  $targetPath = rtrim(GeneralUtility::fixWindowsFilePath($targetPath), '/');
139  if ($sourcePath !== $targetPath) {
140  $commonPrefix = ‪self::getCommonPrefix([$sourcePath, $targetPath]);
141  if ($commonPrefix !== null && GeneralUtility::isAllowedAbsPath($commonPrefix)) {
142  $commonPrefixLength = strlen($commonPrefix);
143  $resolvedSourcePath = '';
144  $resolvedTargetPath = '';
145  $sourcePathSteps = 0;
146  if (strlen($sourcePath) > $commonPrefixLength) {
147  $resolvedSourcePath = (string)substr($sourcePath, $commonPrefixLength);
148  }
149  if (strlen($targetPath) > $commonPrefixLength) {
150  $resolvedTargetPath = (string)substr($targetPath, $commonPrefixLength);
151  }
152  if ($resolvedSourcePath !== '') {
153  $sourcePathSteps = count(explode('/', $resolvedSourcePath));
154  }
155  $relativePath = ‪self::sanitizeTrailingSeparator(str_repeat('../', $sourcePathSteps) . $resolvedTargetPath);
156  }
157  }
158  return $relativePath;
159  }
160 
171  public static function ‪getCommonPrefix(array $paths)
172  {
173  $paths = array_map([GeneralUtility::class, 'fixWindowsFilePath'], $paths);
174  $commonPath = null;
175  if (count($paths) === 1) {
176  $commonPath = array_shift($paths);
177  } elseif (count($paths) > 1) {
178  $parts = explode('/', (string)array_shift($paths));
179  $comparePath = '';
180  $break = false;
181  foreach ($parts as $part) {
182  $comparePath .= $part . '/';
183  foreach ($paths as $path) {
184  if (strpos($path . '/', $comparePath) !== 0) {
185  $break = true;
186  break;
187  }
188  }
189  if ($break) {
190  break;
191  }
192  $commonPath = $comparePath;
193  }
194  }
195  if ($commonPath !== null) {
196  $commonPath = ‪self::sanitizeTrailingSeparator($commonPath, '/');
197  }
198  return $commonPath;
199  }
200 
209  public static function ‪sanitizeTrailingSeparator($path, $separator = '/')
210  {
211  return rtrim($path, $separator) . $separator;
212  }
213 
226  public static function ‪basename($path)
227  {
228  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
229  if (empty($targetLocale)) {
230  return ‪basename($path);
231  }
232  $currentLocale = (string)setlocale(LC_CTYPE, '0');
233  setlocale(LC_CTYPE, $targetLocale);
234  $basename = ‪basename($path);
235  setlocale(LC_CTYPE, $currentLocale);
236  return $basename;
237  }
238 
251  public static function ‪dirname($path)
252  {
253  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
254  if (empty($targetLocale)) {
255  return ‪dirname($path);
256  }
257  $currentLocale = (string)setlocale(LC_CTYPE, '0');
258  setlocale(LC_CTYPE, $targetLocale);
259  $dirname = ‪dirname($path);
260  setlocale(LC_CTYPE, $currentLocale);
261  return $dirname;
262  }
263 
277  public static function ‪pathinfo($path, $options = null)
278  {
279  $targetLocale = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] ?? '';
280  if (empty($targetLocale)) {
281  return $options === null ? ‪pathinfo($path) : ‪pathinfo($path, $options);
282  }
283  $currentLocale = (string)setlocale(LC_CTYPE, '0');
284  setlocale(LC_CTYPE, $targetLocale);
285  $pathinfo = $options == null ? ‪pathinfo($path) : ‪pathinfo($path, $options);
286  setlocale(LC_CTYPE, $currentLocale);
287  return $pathinfo;
288  }
289 
296  public static function ‪isAbsolutePath($path)
297  {
298  // On Windows also a path starting with a drive letter is absolute: X:/
299  if (‪Environment::isWindows() && (substr($path, 1, 2) === ':/' || substr($path, 1, 2) === ':\\')) {
300  return true;
301  }
302  // Path starting with a / is always absolute, on every system, VFS is needed for tests
303  return substr($path, 0, 1) === '/' || substr($path, 0, 6) === 'vfs://';
304  }
305 
329  public static function ‪getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
330  {
331  $fileName = static::basename($includeFileName);
332  $basePath = substr($baseFilenameOrPath, -1) === '/' ? $baseFilenameOrPath : static::dirname($baseFilenameOrPath);
333  $newDir = static::getCanonicalPath($basePath . '/' . static::dirname($includeFileName));
334  // Avoid double slash on empty path
335  $result = (($newDir !== '/') ? $newDir : '') . '/' . $fileName;
336  return $result;
337  }
338 
349  public static function ‪dirnameDuringBootstrap($path): string
350  {
351  return preg_replace('#(.*)(/|\\\\)([^\\\\/]+)$#', '$1', $path);
352  }
353 
364  public static function ‪basenameDuringBootstrap($path): string
365  {
366  return preg_replace('#.*[/\\\\]([^\\\\/]+)$#', '$1', $path);
367  }
368 
369  /*********************
370  *
371  * Cleaning methods
372  *
373  *********************/
380  public static function ‪getCanonicalPath($path)
381  {
382  // Replace backslashes with slashes to work with Windows paths if given
383  $path = trim(str_replace('\\', '/', $path));
384 
385  // @todo do we really need this? Probably only in testing context for vfs?
386  $protocol = '';
387  if (str_contains($path, '://')) {
388  [$protocol, $path] = explode('://', $path);
389  $protocol .= '://';
390  }
391 
392  $absolutePathPrefix = '';
393  if (static::isAbsolutePath($path)) {
394  if (‪Environment::isWindows() && substr($path, 1, 2) === ':/') {
395  $absolutePathPrefix = substr($path, 0, 3);
396  $path = substr($path, 3);
397  } else {
398  $path = ltrim($path, '/');
399  $absolutePathPrefix = '/';
400  }
401  }
402 
403  $theDirParts = explode('/', $path);
404  $theDirPartsCount = count($theDirParts);
405  for ($partCount = 0; $partCount < $theDirPartsCount; $partCount++) {
406  // double-slashes in path: remove element
407  if ($theDirParts[$partCount] === '') {
408  array_splice($theDirParts, $partCount, 1);
409  $partCount--;
410  $theDirPartsCount--;
411  }
412  // "." in path: remove element
413  if (($theDirParts[$partCount] ?? '') === '.') {
414  array_splice($theDirParts, $partCount, 1);
415  $partCount--;
416  $theDirPartsCount--;
417  }
418  // ".." in path:
419  if (($theDirParts[$partCount] ?? '') === '..') {
420  if ($partCount >= 1) {
421  // Remove this and previous element
422  array_splice($theDirParts, $partCount - 1, 2);
423  $partCount -= 2;
424  $theDirPartsCount -= 2;
425  } elseif ($absolutePathPrefix) {
426  // can't go higher than root dir
427  // simply remove this part and continue
428  array_splice($theDirParts, $partCount, 1);
429  $partCount--;
430  $theDirPartsCount--;
431  }
432  }
433  }
434 
435  return $protocol . $absolutePathPrefix . implode('/', $theDirParts);
436  }
437 
445  public static function ‪stripPathSitePrefix($path)
446  {
447  return substr($path, strlen(‪Environment::getPublicPath() . '/'));
448  }
449 
463  public static function ‪hasProtocolAndScheme(string $path): bool
464  {
465  return strpos($path, '//') === 0 || strpos($path, '://') > 0;
466  }
467 
475  public static function ‪isAllowedAdditionalPath(string $path): bool
476  {
477  // ensure the submitted path ends with a string, even for a file
478  $path = ‪self::sanitizeTrailingSeparator($path);
479  $allowedPaths = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? [];
480  if (is_string($allowedPaths)) {
481  // The setting was a string before and is now an array
482  // For compatibility reasons, we cast a string to an array here for now
483  $allowedPaths = [$allowedPaths];
484  }
485  if (!is_array($allowedPaths)) {
486  throw new \RuntimeException('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'lockRootPath\'] is expected to be an array.', 1707408379);
487  }
488  foreach ($allowedPaths as $allowedPath) {
489  $allowedPath = trim($allowedPath);
490  if ($allowedPath !== '' && str_starts_with($path, self::sanitizeTrailingSeparator($allowedPath))) {
491  return true;
492  }
493  }
494  return false;
495  }
496 }
‪TYPO3\CMS\Core\Utility\PathUtility\basenameDuringBootstrap
‪static string basenameDuringBootstrap($path)
Definition: PathUtility.php:364
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:25
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:206
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static string dirname($path)
Definition: PathUtility.php:251
‪TYPO3\CMS\Core\Core\Environment\getComposerRootPath
‪static string getComposerRootPath()
Definition: Environment.php:191
‪TYPO3\CMS\Core\Utility\PathUtility\isAllowedAdditionalPath
‪static isAllowedAdditionalPath(string $path)
Definition: PathUtility.php:475
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static string stripPathSitePrefix($path)
Definition: PathUtility.php:445
‪TYPO3\CMS\Core\Utility\PathUtility\isExtensionPath
‪static bool isExtensionPath(string $path)
Definition: PathUtility.php:121
‪TYPO3\CMS\Core\Utility\PathUtility\getRelativePath
‪static string null getRelativePath($sourcePath, $targetPath)
Definition: PathUtility.php:134
‪TYPO3\CMS\Core\Utility\PathUtility\dirnameDuringBootstrap
‪static string dirnameDuringBootstrap($path)
Definition: PathUtility.php:349
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static bool isWindows()
Definition: Environment.php:318
‪TYPO3\CMS\Core\Utility
Definition: ArrayUtility.php:16
‪TYPO3\CMS\Core\Utility\PathUtility\getPublicResourceWebPath
‪static string getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:98
‪TYPO3\CMS\Core\Core\Environment\getCurrentScript
‪static string getCurrentScript()
Definition: Environment.php:246
‪TYPO3\CMS\Core\Utility\PathUtility\getCanonicalPath
‪static string getCanonicalPath($path)
Definition: PathUtility.php:380
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:226
‪TYPO3\CMS\Core\Utility\PathUtility\getRelativePathTo
‪static string null getRelativePathTo($targetPath)
Definition: PathUtility.php:33
‪TYPO3\CMS\Core\Utility\PathUtility\getCommonPrefix
‪static string null getCommonPrefix(array $paths)
Definition: PathUtility.php:171
‪TYPO3\CMS\Core\Resource\Exception\InvalidFileException
Definition: InvalidFileException.php:23
‪TYPO3\CMS\Core\Utility\PathUtility\pathinfo
‪static string string[] pathinfo($path, $options=null)
Definition: PathUtility.php:277
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static bool isAbsolutePath($path)
Definition: PathUtility.php:296
‪TYPO3\CMS\Core\Utility\PathUtility\sanitizeTrailingSeparator
‪static string sanitizeTrailingSeparator($path, $separator='/')
Definition: PathUtility.php:209
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static bool isComposerMode()
Definition: Environment.php:152
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Utility\PathUtility\hasProtocolAndScheme
‪static bool hasProtocolAndScheme(string $path)
Definition: PathUtility.php:463
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:43
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath($targetPath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:51
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static bool isCli()
Definition: Environment.php:162
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsolutePathOfRelativeReferencedFileOrPath
‪static string getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
Definition: PathUtility.php:329