f34bcea9752fbaeb905a08310c912905d77bf1c6
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Resources / PHP / TYPO3.Flow / Classes / TYPO3 / Flow / Utility / Files.php
1 <?php
2 namespace TYPO3\Flow\Utility;
3
4 /* *
5 * This script belongs to the TYPO3 Flow framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 /**
15 * File and directory functions
16 *
17 */
18 class Files {
19
20 /**
21 * Replacing backslashes and double slashes to slashes.
22 * It's needed to compare paths (especially on windows).
23 *
24 * @param string $path Path which should transformed to the Unix Style.
25 * @return string
26 */
27 static public function getUnixStylePath($path) {
28 if (strpos($path, ':') === FALSE) {
29 return str_replace('//', '/', strtr($path, '\\', '/'));
30 } else {
31 return preg_replace('/^([a-z]{2,}):\//', '$1://', str_replace('//', '/', strtr($path, '\\', '/')));
32 }
33 }
34
35 /**
36 * Makes sure path has a trailing slash
37 *
38 * @param string $path
39 * @return string
40 */
41 static public function getNormalizedPath($path) {
42 return rtrim($path, '/') . '/';
43 }
44
45 /**
46 * Properly glues together filepaths / filenames by replacing
47 * backslashes and double slashes of the specified paths.
48 * Note: trailing slashes will be removed, leading slashes won't.
49 * Usage: concatenatePaths(array('dir1/dir2', 'dir3', 'file'))
50 *
51 * @param array $paths the file paths to be combined. Last array element may include the filename.
52 * @return string concatenated path without trailing slash.
53 * @see getUnixStylePath()
54 */
55 static public function concatenatePaths(array $paths) {
56 return rtrim(self::getUnixStylePath(implode('/', $paths)), '/');
57 }
58
59 /**
60 * Returns all filenames from the specified directory. Filters hidden files and
61 * directories.
62 *
63 * @param string $path Path to the directory which shall be read
64 * @param string $suffix If specified, only filenames with this extension are returned (eg. ".php" or "foo.bar")
65 * @param boolean $returnRealPath If turned on, all paths are resolved by calling realpath()
66 * @param boolean $returnDotFiles If turned on, also files beginning with a dot will be returned
67 * @param array $filenames Internally used for the recursion - don't specify!
68 * @return array Filenames including full path
69 * @throws Exception
70 */
71 static public function readDirectoryRecursively($path, $suffix = NULL, $returnRealPath = FALSE, $returnDotFiles = FALSE, &$filenames = array()) {
72 if (!is_dir($path)) throw new \TYPO3\Flow\Utility\Exception('"' . $path . '" is no directory.', 1207253462);
73
74 $directoryIterator = new \DirectoryIterator($path);
75 $suffixLength = strlen($suffix);
76
77 foreach ($directoryIterator as $fileInfo) {
78 $filename = $fileInfo->getFilename();
79 if ($filename === '.' || $filename === '..' || ($returnDotFiles === FALSE && $filename[0] === '.')) {
80 continue;
81 }
82 if ($fileInfo->isFile() && ($suffix === NULL || substr($filename, -$suffixLength) === $suffix)) {
83 $filenames[] = self::getUnixStylePath(($returnRealPath === TRUE ? realpath($fileInfo->getPathname()) : $fileInfo->getPathname()));
84 }
85 if ($fileInfo->isDir()) {
86 self::readDirectoryRecursively($fileInfo->getPathname(), $suffix, $returnRealPath, $returnDotFiles, $filenames);
87 }
88 }
89 return $filenames;
90 }
91
92 /**
93 * Deletes all files, directories and subdirectories from the specified
94 * directory. The passed directory itself won't be deleted though.
95 *
96 * @param string $path Path to the directory which shall be emptied.
97 * @return void
98 * @throws Exception
99 * @see removeDirectoryRecursively()
100 */
101 static public function emptyDirectoryRecursively($path) {
102 if (!is_dir($path)) {
103 throw new \TYPO3\Flow\Utility\Exception('"' . $path . '" is no directory.', 1169047616);
104 }
105
106 if (self::is_link($path)) {
107 if (self::unlink($path) !== TRUE) {
108 throw new \TYPO3\Flow\Utility\Exception('Could not unlink symbolic link "' . $path . '".', 1323697654);
109 }
110 } else {
111 $directoryIterator = new \RecursiveDirectoryIterator($path);
112 foreach ($directoryIterator as $fileInfo) {
113 if (!$fileInfo->isDir()) {
114 if (self::unlink($fileInfo->getPathname()) !== TRUE) {
115 throw new \TYPO3\Flow\Utility\Exception('Could not unlink file "' . $fileInfo->getPathname() . '".', 1169047619);
116 }
117 } elseif (!$directoryIterator->isDot()) {
118 self::removeDirectoryRecursively($fileInfo->getPathname());
119 }
120 }
121 }
122 }
123
124 /**
125 * Deletes all files, directories and subdirectories from the specified
126 * directory. Contrary to emptyDirectoryRecursively() this function will
127 * also finally remove the emptied directory.
128 *
129 * @param string $path Path to the directory which shall be removed completely.
130 * @return void
131 * @throws Exception
132 * @see emptyDirectoryRecursively()
133 */
134 static public function removeDirectoryRecursively($path) {
135 if (self::is_link($path)) {
136 if (self::unlink($path) !== TRUE) {
137 throw new \TYPO3\Flow\Utility\Exception('Could not unlink symbolic link "' . $path . '".', 1316000297);
138 }
139 } else {
140 self::emptyDirectoryRecursively($path);
141 try {
142 if (rmdir($path) !== TRUE) {
143 throw new \TYPO3\Flow\Utility\Exception('Could not remove directory "' . $path . '".', 1316000298);
144 }
145 } catch (\Exception $exception) {
146 throw new \TYPO3\Flow\Utility\Exception('Could not remove directory "' . $path . '".', 1323961907);
147 }
148 }
149 }
150
151 /**
152 * Creates a directory specified by $path. If the parent directories
153 * don't exist yet, they will be created as well.
154 *
155 * @param string $path Path to the directory which shall be created
156 * @return void
157 * @throws Exception
158 * @todo Make mode configurable / make umask configurable
159 */
160 static public function createDirectoryRecursively($path) {
161 if (substr($path, -2) === '/.') {
162 $path = substr($path, 0, -1);
163 }
164 if (is_file($path)) {
165 throw new \TYPO3\Flow\Utility\Exception('Could not create directory "' . $path . '", because a file with that name exists!', 1349340620);
166 }
167 if (!is_dir($path) && strlen($path) > 0) {
168 $oldMask = umask(000);
169 mkdir($path, 0777, TRUE);
170 umask($oldMask);
171 if (!is_dir($path)) {
172 throw new \TYPO3\Flow\Utility\Exception('Could not create directory "' . $path . '"!', 1170251400);
173 }
174 }
175 }
176
177 /**
178 * Copies the contents of the source directory to the target directory.
179 * $targetDirectory will be created if it does not exist.
180 *
181 * If $keepExistingFiles is TRUE, this will keep files already present
182 * in the target location. It defaults to FALSE.
183 *
184 * If $copyDotFiles is TRUE, this will copy files whose name begin with
185 * a dot. It defaults to FALSE.
186 *
187 * @param string $sourceDirectory
188 * @param string $targetDirectory
189 * @param boolean $keepExistingFiles
190 * @param boolean $copyDotFiles
191 * @return void
192 * @throws Exception
193 */
194 static public function copyDirectoryRecursively($sourceDirectory, $targetDirectory, $keepExistingFiles = FALSE, $copyDotFiles = FALSE) {
195 if (!is_dir($sourceDirectory)) {
196 throw new \TYPO3\Flow\Utility\Exception('"' . $sourceDirectory . '" is no directory.', 1235428779);
197 }
198
199 self::createDirectoryRecursively($targetDirectory);
200 if (!is_dir($targetDirectory)) {
201 throw new \TYPO3\Flow\Utility\Exception('"' . $targetDirectory . '" is no directory.', 1235428780);
202 }
203
204 $sourceFilenames = self::readDirectoryRecursively($sourceDirectory, NULL, FALSE, $copyDotFiles);
205 foreach ($sourceFilenames as $filename) {
206 $relativeFilename = str_replace($sourceDirectory, '', $filename);
207 self::createDirectoryRecursively($targetDirectory . dirname($relativeFilename));
208 $targetPathAndFilename = self::concatenatePaths(array($targetDirectory, $relativeFilename));
209 if ($keepExistingFiles === FALSE || !file_exists($targetPathAndFilename)) {
210 copy($filename, $targetPathAndFilename);
211 }
212 }
213 }
214
215 /**
216 * An enhanced version of file_get_contents which intercepts the warning
217 * issued by the original function if a file could not be loaded.
218 *
219 * @param string $pathAndFilename Path and name of the file to load
220 * @param integer $flags (optional) ORed flags using PHP's FILE_* constants (see manual of file_get_contents).
221 * @param resource $context (optional) A context resource created by stream_context_create()
222 * @param integer $offset (optional) Offset where reading of the file starts.
223 * @param integer $maximumLength (optional) Maximum length to read. Default is -1 (no limit)
224 * @return mixed The file content as a string or FALSE if the file could not be opened.
225 */
226 static public function getFileContents($pathAndFilename, $flags = 0, $context = NULL, $offset = -1, $maximumLength = -1) {
227 if ($flags === TRUE) $flags = FILE_USE_INCLUDE_PATH;
228 try {
229 if ($maximumLength > -1) {
230 $content = file_get_contents($pathAndFilename, $flags, $context, $offset, $maximumLength);
231 } else {
232 $content = file_get_contents($pathAndFilename, $flags, $context, $offset);
233 }
234 } catch (\TYPO3\Flow\Error\Exception $ignoredException) {
235 $content = FALSE;
236 }
237 return $content;
238 }
239
240 /**
241 * Returns a human-readable message for the given PHP file upload error
242 * constant.
243 *
244 * @param integer $errorCode One of the UPLOAD_ERR_ constants
245 * @return string
246 */
247 static public function getUploadErrorMessage($errorCode) {
248 switch ($errorCode) {
249 case \UPLOAD_ERR_INI_SIZE:
250 return 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
251 case \UPLOAD_ERR_FORM_SIZE:
252 return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
253 case \UPLOAD_ERR_PARTIAL:
254 return 'The uploaded file was only partially uploaded';
255 case \UPLOAD_ERR_NO_FILE:
256 return 'No file was uploaded';
257 case \UPLOAD_ERR_NO_TMP_DIR:
258 return 'Missing a temporary folder';
259 case \UPLOAD_ERR_CANT_WRITE:
260 return 'Failed to write file to disk';
261 case \UPLOAD_ERR_EXTENSION:
262 return 'File upload stopped by extension';
263 default:
264 return 'Unknown upload error';
265 }
266 }
267
268 /**
269 * A version of is_link() that works on Windows too
270 * @see http://www.php.net/is_link
271 *
272 * If http://bugs.php.net/bug.php?id=51766 gets fixed we can drop this.
273 *
274 * @param string $pathAndFilename Path and name of the file or directory
275 * @return boolean TRUE if the path exists and is a symbolic link, FALSE otherwise
276 */
277 static public function is_link($pathAndFilename) {
278 // if not on Windows, call PHPs own is_link() function
279 if (DIRECTORY_SEPARATOR === '/') {
280 return \is_link($pathAndFilename);
281 }
282 if (!file_exists($pathAndFilename)) {
283 return FALSE;
284 }
285 $normalizedPathAndFilename = strtolower(rtrim(self::getUnixStylePath($pathAndFilename), '/'));
286 $normalizedTargetPathAndFilename = strtolower(self::getUnixStylePath(realpath($pathAndFilename)));
287 if ($normalizedTargetPathAndFilename === '') {
288 return FALSE;
289 }
290 return $normalizedPathAndFilename !== $normalizedTargetPathAndFilename;
291 }
292
293 /**
294 * A version of unlink() that works on Windows regardless on the symlink type (file/directory)
295 *
296 * @param string $pathAndFilename Path and name of the file or directory
297 * @return boolean TRUE if file/directory was removed successfully
298 */
299 static public function unlink($pathAndFilename) {
300 try {
301 // if not on Windows, call PHPs own unlink() function
302 if (DIRECTORY_SEPARATOR === '/' || is_file($pathAndFilename)) {
303 return @\unlink($pathAndFilename);
304 }
305 return rmdir($pathAndFilename);
306 } catch (\Exception $exception) {
307 return FALSE;
308 }
309 }
310 }
311 ?>