[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / PathUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Core\Environment;
18
19 /**
20 * Class with helper functions for file paths.
21 */
22 class PathUtility
23 {
24 /**
25 * Gets the relative path from the current used script to a given directory.
26 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
27 *
28 * @param string $targetPath Absolute target path
29 * @return string|null
30 */
31 public static function getRelativePathTo($targetPath)
32 {
33 return self::getRelativePath(self::dirname(Environment::getCurrentScript()), $targetPath);
34 }
35
36 /**
37 * Creates an absolute URL out of really any input path, removes '../' parts for the targetPath
38 *
39 * @param string $targetPath can be "../typo3conf/ext/myext/myfile.js" or "/myfile.js"
40 * @return string something like "/mysite/typo3conf/ext/myext/myfile.js"
41 */
42 public static function getAbsoluteWebPath($targetPath)
43 {
44 if (self::isAbsolutePath($targetPath)) {
45 if (strpos($targetPath, Environment::getPublicPath()) === 0) {
46 $targetPath = self::stripPathSitePrefix($targetPath);
47 if (!Environment::isCli()) {
48 $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
49 }
50 }
51 } elseif (strpos($targetPath, '://') !== false) {
52 return $targetPath;
53 } else {
54 // Make an absolute path out of it
55 $targetPath = GeneralUtility::resolveBackPath(self::dirname(Environment::getCurrentScript()) . '/' . $targetPath);
56 $targetPath = self::stripPathSitePrefix($targetPath);
57 if (!Environment::isCli()) {
58 $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath;
59 }
60 }
61 return $targetPath;
62 }
63
64 /**
65 * Gets the relative path from a source directory to a target directory.
66 * The allowed TYPO3 path is checked as well, thus it's not possible to go to upper levels.
67 *
68 * @param string $sourcePath Absolute source path
69 * @param string $targetPath Absolute target path
70 * @return string|null
71 */
72 public static function getRelativePath($sourcePath, $targetPath)
73 {
74 $relativePath = null;
75 $sourcePath = rtrim(GeneralUtility::fixWindowsFilePath($sourcePath), '/');
76 $targetPath = rtrim(GeneralUtility::fixWindowsFilePath($targetPath), '/');
77 if ($sourcePath !== $targetPath) {
78 $commonPrefix = self::getCommonPrefix([$sourcePath, $targetPath]);
79 if ($commonPrefix !== null && GeneralUtility::isAllowedAbsPath($commonPrefix)) {
80 $commonPrefixLength = strlen($commonPrefix);
81 $resolvedSourcePath = '';
82 $resolvedTargetPath = '';
83 $sourcePathSteps = 0;
84 if (strlen($sourcePath) > $commonPrefixLength) {
85 $resolvedSourcePath = (string)substr($sourcePath, $commonPrefixLength);
86 }
87 if (strlen($targetPath) > $commonPrefixLength) {
88 $resolvedTargetPath = (string)substr($targetPath, $commonPrefixLength);
89 }
90 if ($resolvedSourcePath !== '') {
91 $sourcePathSteps = count(explode('/', $resolvedSourcePath));
92 }
93 $relativePath = self::sanitizeTrailingSeparator(str_repeat('../', $sourcePathSteps) . $resolvedTargetPath);
94 }
95 }
96 return $relativePath;
97 }
98
99 /**
100 * Gets the common path prefix out of many paths.
101 * + /var/www/domain.com/typo3/sysext/frontend/
102 * + /var/www/domain.com/typo3/sysext/em/
103 * + /var/www/domain.com/typo3/sysext/file/
104 * = /var/www/domain.com/typo3/sysext/
105 *
106 * @param array $paths Paths to be processed
107 * @return string|null
108 */
109 public static function getCommonPrefix(array $paths)
110 {
111 $paths = array_map([\TYPO3\CMS\Core\Utility\GeneralUtility::class, 'fixWindowsFilePath'], $paths);
112 $commonPath = null;
113 if (count($paths) === 1) {
114 $commonPath = array_shift($paths);
115 } elseif (count($paths) > 1) {
116 $parts = explode('/', array_shift($paths));
117 $comparePath = '';
118 $break = false;
119 foreach ($parts as $part) {
120 $comparePath .= $part . '/';
121 foreach ($paths as $path) {
122 if (strpos($path . '/', $comparePath) !== 0) {
123 $break = true;
124 break;
125 }
126 }
127 if ($break) {
128 break;
129 }
130 $commonPath = $comparePath;
131 }
132 }
133 if ($commonPath !== null) {
134 $commonPath = self::sanitizeTrailingSeparator($commonPath, '/');
135 }
136 return $commonPath;
137 }
138
139 /**
140 * Sanitizes a trailing separator.
141 * (e.g. 'some/path' -> 'some/path/')
142 *
143 * @param string $path The path to be sanitized
144 * @param string $separator The separator to be used
145 * @return string
146 */
147 public static function sanitizeTrailingSeparator($path, $separator = '/')
148 {
149 return rtrim($path, $separator) . $separator;
150 }
151
152 /**
153 * Returns trailing name component of path
154 * Since basename() is locale dependent we need to access
155 * the filesystem with the same locale of the system, not
156 * the rendering context.
157 * @see http://www.php.net/manual/en/function.basename.php
158 *
159 *
160 * @param string $path
161 *
162 * @return string
163 */
164 public static function basename($path)
165 {
166 $currentLocale = setlocale(LC_CTYPE, 0);
167 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
168 $basename = basename($path);
169 setlocale(LC_CTYPE, $currentLocale);
170 return $basename;
171 }
172
173 /**
174 * Returns parent directory's path
175 * Since dirname() is locale dependent we need to access
176 * the filesystem with the same locale of the system, not
177 * the rendering context.
178 * @see http://www.php.net/manual/en/function.dirname.php
179 *
180 *
181 * @param string $path
182 *
183 * @return string
184 */
185 public static function dirname($path)
186 {
187 $currentLocale = setlocale(LC_CTYPE, 0);
188 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
189 $dirname = dirname($path);
190 setlocale(LC_CTYPE, $currentLocale);
191 return $dirname;
192 }
193
194 /**
195 * Returns parent directory's path
196 * Since dirname() is locale dependent we need to access
197 * the filesystem with the same locale of the system, not
198 * the rendering context.
199 * @see http://www.php.net/manual/en/function.dirname.php
200 *
201 *
202 * @param string $path
203 * @param int $options
204 *
205 * @return string|array
206 */
207 public static function pathinfo($path, $options = null)
208 {
209 $currentLocale = setlocale(LC_CTYPE, 0);
210 setlocale(LC_CTYPE, $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale']);
211 $pathinfo = $options == null ? pathinfo($path) : pathinfo($path, $options);
212 setlocale(LC_CTYPE, $currentLocale);
213 return $pathinfo;
214 }
215
216 /**
217 * Checks if the $path is absolute or relative (detecting either '/' or 'x:/' as first part of string) and returns TRUE if so.
218 *
219 * @param string $path File path to evaluate
220 * @return bool
221 */
222 public static function isAbsolutePath($path)
223 {
224 // On Windows also a path starting with a drive letter is absolute: X:/
225 if (Environment::isWindows() && (substr($path, 1, 2) === ':/' || substr($path, 1, 2) === ':\\')) {
226 return true;
227 }
228 // Path starting with a / is always absolute, on every system
229 return $path[0] === '/';
230 }
231
232 /**
233 * Gets the (absolute) path of an include file based on the (absolute) path of a base file
234 *
235 * Does NOT do any sanity checks. This is a task for the calling function, e.g.
236 * call GeneralUtility::getFileAbsFileName() on the result.
237 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName()
238 *
239 * Resolves all dots and slashes between that paths of both files.
240 * Whether the result is absolute or not, depends of the base file name.
241 *
242 * If the include file goes higher than a relative base file, then the result
243 * will contain dots as a relative part.
244 * <pre>
245 * base: abc/one.txt
246 * include: ../../two.txt
247 * result: ../two.txt
248 * </pre>
249 * The exact behavior, refer to getCanonicalPath().
250 *
251 * @param string $baseFilenameOrPath The name of the file or a path that serves as a base; a path will need to have a '/' at the end
252 * @param string $includeFileName The name of the file that is included in the file
253 * @return string The (absolute) path of the include file
254 */
255 public static function getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
256 {
257 $fileName = static::basename($includeFileName);
258 $basePath = substr($baseFilenameOrPath, -1) === '/' ? $baseFilenameOrPath : static::dirname($baseFilenameOrPath);
259 $newDir = static::getCanonicalPath($basePath . '/' . static::dirname($includeFileName));
260 // Avoid double slash on empty path
261 $result = (($newDir !== '/') ? $newDir : '') . '/' . $fileName;
262 return $result;
263 }
264
265 /**
266 * Returns parent directory's path
267 * Early during bootstrap there is no TYPO3_CONF_VARS yet so the setting for the system locale
268 * is also unavailable. The path of the parent directory is determined with a regular expression
269 * to avoid issues with locales.
270 *
271 * @param string $path
272 *
273 * @return string Path without trailing slash
274 */
275 public static function dirnameDuringBootstrap($path): string
276 {
277 return preg_replace('#(.*)(/|\\\\)([^\\\\/]+)$#', '$1', $path);
278 }
279
280 /**
281 * Returns filename part of a path
282 * Early during bootstrap there is no TYPO3_CONF_VARS yet so the setting for the system locale
283 * is also unavailable. The filename part is determined with a regular expression to avoid issues
284 * with locales.
285 *
286 * @param string $path
287 *
288 * @return string
289 */
290 public static function basenameDuringBootstrap($path): string
291 {
292 return preg_replace('#.*[/\\\\]([^\\\\/]+)$#', '$1', $path);
293 }
294
295 /*********************
296 *
297 * Cleaning methods
298 *
299 *********************/
300 /**
301 * Resolves all dots, slashes and removes spaces after or before a path...
302 *
303 * @param string $path Input string
304 * @return string Canonical path, always without trailing slash
305 */
306 public static function getCanonicalPath($path)
307 {
308 // Replace backslashes with slashes to work with Windows paths if given
309 $path = trim(str_replace('\\', '/', $path));
310
311 // @todo do we really need this? Probably only in testing context for vfs?
312 $protocol = '';
313 if (strpos($path, '://') !== false) {
314 list($protocol, $path) = explode('://', $path);
315 $protocol .= '://';
316 }
317
318 $absolutePathPrefix = '';
319 if (static::isAbsolutePath($path)) {
320 if (Environment::isWindows() && substr($path, 1, 2) === ':/') {
321 $absolutePathPrefix = substr($path, 0, 3);
322 $path = substr($path, 3);
323 } else {
324 $path = ltrim($path, '/');
325 $absolutePathPrefix = '/';
326 }
327 }
328
329 $theDirParts = explode('/', $path);
330 $theDirPartsCount = count($theDirParts);
331 for ($partCount = 0; $partCount < $theDirPartsCount; $partCount++) {
332 // double-slashes in path: remove element
333 if ($theDirParts[$partCount] === '') {
334 array_splice($theDirParts, $partCount, 1);
335 $partCount--;
336 $theDirPartsCount--;
337 }
338 // "." in path: remove element
339 if (($theDirParts[$partCount] ?? '') === '.') {
340 array_splice($theDirParts, $partCount, 1);
341 $partCount--;
342 $theDirPartsCount--;
343 }
344 // ".." in path:
345 if (($theDirParts[$partCount] ?? '') === '..') {
346 if ($partCount >= 1) {
347 // Remove this and previous element
348 array_splice($theDirParts, $partCount - 1, 2);
349 $partCount -= 2;
350 $theDirPartsCount -= 2;
351 } elseif ($absolutePathPrefix) {
352 // can't go higher than root dir
353 // simply remove this part and continue
354 array_splice($theDirParts, $partCount, 1);
355 $partCount--;
356 $theDirPartsCount--;
357 }
358 }
359 }
360
361 return $protocol . $absolutePathPrefix . implode('/', $theDirParts);
362 }
363
364 /**
365 * Strip first part of a path, equal to the length of public web path including trailing slash
366 *
367 * @param string $path
368 * @return string
369 * @internal
370 */
371 public static function stripPathSitePrefix($path)
372 {
373 return substr($path, strlen(Environment::getPublicPath() . '/'));
374 }
375 }