FileUtil.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\io;
12 
16 
17 /**
18  * FileUtil provides basic support for file functionality like HTTP file upload.
19  *
20  * @author ingo herwig <ingo@wemove.com>
21  */
22 class FileUtil {
23 
24  /**
25  * Copy an uploaded file to a given destination (only if the mime type matches the given one).
26  * @param $mediaFile An associative array with the following keys: 'name', 'type', 'tmp_name' (typically a $_FILES entry)
27  * @param $destName The destination file name
28  * @param $mimeTypes An array holding the allowed mime types, null if arbitrary (default: _null_)
29  * @param $override Boolean whether an existing file should be overridden, if false an unique id will be placed in the filename to prevent overriding (default: _true_)
30  * @return The filename of the uploaded file
31  */
32  public static function uploadFile($mediaFile, $destName, $mimeTypes=null, $override=true) {
33  $message = ObjectFactory::getInstance('message');
34 
35  // check if the file was uploaded
36  if (!is_uploaded_file($mediaFile['tmp_name'])) {
37  $msg = $message->getText("Possible file upload attack: filename %0%.", [$mediaFile['name']]);
38  throw new IOException($msg);
39  }
40 
41  // check mime type
42  if ($mimeTypes != null && !in_array($mediaFile['type'], $mimeTypes)) {
43  throw new IOException($message->getText("File '%0%' has wrong mime type: %1%. Allowed types: %2%.",
44  [$mediaFile['name'], $mediaFile['type'], join(", ", $mimeTypes)]));
45  }
46 
47  // check if we need a new name
48  if ($override == false && file_exists($destName)) {
49  $pieces = preg_split('/\./', self::basename($destName));
50  $extension = array_pop($pieces);
51  $name = join('.', $pieces);
52  $destName = dirname($destName)."/".$name.uniqid(mt_rand(0, 99)).".".$extension;
53  }
54  $result = move_uploaded_file($mediaFile['tmp_name'], $destName);
55  if ($result === false) {
56  throw new IOException($message->getText("Failed to move %0% to %1%.", [$mediaFile['tmp_name'], $destName]));
57  }
58  return self::basename($destName);
59  }
60 
61  /**
62  * Get the mime type of the given file
63  * @param $file The file
64  * @return String
65  */
66  public static function getMimeType($file) {
67  $defaultType = 'application/octet-stream';
68  if (class_exists('\FileInfo')) {
69  // use extension
70  $fileInfo = new finfo(FILEINFO_MIME);
71  $fileType = $fileInfo->file(file_get_contents($file));
72  }
73  else {
74  // try detect image mime type
75  $imageInfo = @getimagesize($file);
76  $fileType = isset($imageInfo['mime']) ? $imageInfo['mime'] : '';
77  }
78  return (is_string($fileType) && !empty($fileType)) ? $fileType : $defaultType;
79  }
80 
81  /**
82  * Write unicode to file.
83  * @param $fp File Handle
84  * @param $str String to write
85  */
86  public static function fputsUnicode($fp, $str) {
87  fputs($fp, utf8_encode($str));
88  }
89 
90  /*
91  * Get the files in a directory that match a pattern
92  * @param $directory The directory to search in
93  * @param $pattern The pattern (regexp) to match (default: _/./_)
94  * @param $prependDirectoryName Boolean whether to prepend the directory name to each file (default: _false_)
95  * @param $recursive Boolean whether to recurse into subdirectories (default: _false_)
96  * @return An array containing the filenames sorted by modification date
97  */
98  public static function getFiles($directory, $pattern='/./', $prependDirectoryName=false, $recursive=false) {
99  if (strrpos($directory, '/') != strlen($directory)-1) {
100  $directory .= '/';
101  }
102  if (!is_dir($directory)) {
103  $message = ObjectFactory::getInstance('message');
104  throw new IllegalArgumentException($message->getText("The directory '%0%' does not exist.", [$directory]));
105  }
106  $result = [];
107  $d = dir($directory);
108  $d->rewind();
109  while(false !== ($file = $d->read())) {
110  if($file != '.' && $file != '..') {
111  if ($recursive && is_dir($directory.$file)) {
112  $files = self::getFiles($directory.$file, $pattern, $prependDirectoryName, $recursive);
113  $result = array_merge($result, $files);
114  }
115  else if(is_file($directory.$file) && preg_match($pattern, $file)) {
116  $sortkey = filectime($directory.$file).',';
117  if ($prependDirectoryName) {
118  $file = $directory.$file;
119  }
120  $sortkey .= $file;
121  $result[$sortkey] = $file;
122  }
123  }
124  }
125  $d->close();
126  krsort($result);
127  return array_values($result);
128  }
129 
130  /*
131  * Get the directories in a directory that match a pattern
132  * @param $directory The directory to search in
133  * @param $pattern The pattern (regexp) to match (default: _/./_)
134  * @param $prependDirectoryName Boolean whether to prepend the directory name to each directory (default: _false_)
135  * @param $recursive Boolean whether to recurse into subdirectories (default: _false_)
136  * @return An array containing the directory names
137  */
138  public static function getDirectories($directory, $pattern='/./', $prependDirectoryName=false, $recursive=false) {
139  if (strrpos($directory, '/') != strlen($directory)-1) {
140  $directory .= '/';
141  }
142  if (!is_dir($directory)) {
143  $message = ObjectFactory::getInstance('message');
144  throw new IllegalArgumentException($message->getText("The directory '%0%' does not exist.", [$directory]));
145  }
146 
147  $result = [];
148  $d = dir($directory);
149  $d->rewind();
150  // iterate over all files
151  while(false !== ($file = $d->read())) {
152  // exclude this and parent directory
153  if($file != '.' && $file != '..') {
154  // include directories only
155  if (is_dir($directory.$file)) {
156  // recurse
157  if ($recursive) {
158  $dirs = self::getDirectories($directory.$file, $pattern, $prependDirectoryName, $recursive);
159  $result = array_merge($result, $dirs);
160  }
161  if(preg_match($pattern, $file)) {
162  if ($prependDirectoryName) {
163  $file = $directory.$file;
164  }
165  $result[] = $file;
166  }
167  }
168  }
169  }
170  $d->close();
171  return $result;
172  }
173 
174  /**
175  * Recursive copy for files/directories.
176  * @param $source The name of the source directory/file
177  * @param $dest The name of the destination directory/file
178  */
179  public static function copyRec($source, $dest) {
180  if (is_file($source)) {
181  $perms = fileperms($source);
182  return copy($source, $dest) && chmod($dest, $perms);
183  }
184  if (!is_dir($source)) {
185  $message = ObjectFactory::getInstance('message');
186  throw new IllegalArgumentException($message->getText("Cannot copy %0% (it's neither a file nor a directory).", [$source]));
187  }
188  self::copyRecDir($source, $dest);
189  }
190 
191  /**
192  * Recursive copy for directories.
193  * @param $source The name of the source directory
194  * @param $dest The name of the destination directory
195  */
196  public static function copyRecDir($source, $dest) {
197  if (!is_dir($dest)) {
198  self::mkdirRec($dest);
199  }
200  $dir = opendir($source);
201  while ($file = readdir($dir)) {
202  if ($file == "." || $file == "..") {
203  continue;
204  }
205  self::copyRec("$source/$file", "$dest/$file");
206  }
207  closedir($dir);
208  }
209 
210  /**
211  * Recursive directory creation.
212  * @param $dirname The name of the directory
213  * @param $perm The permission for the new directories (default: 0775)
214  */
215  public static function mkdirRec($dirname, $perm=0775) {
216  if (!is_dir($dirname)) {
217  @mkdir($dirname, $perm, true);
218  }
219  }
220 
221  /**
222  * Empty a directory.
223  * @param $dirname The name of the directory
224  */
225  public static function emptyDir($dirname) {
226  if (is_dir($dirname)) {
227  $files = self::getFiles($dirname, '/./', true, true);
228  foreach ($files as $file) {
229  @unlink($file);
230  }
231  $dirs = self::getDirectories($dirname, '/./', true, true);
232  foreach ($dirs as $dir) {
233  @rmdir($dir);
234  }
235  }
236  }
237 
238  /**
239  * Realpath function that also works for non existing paths
240  * code from http://www.php.net/manual/en/function.realpath.php
241  * @param $path
242  * @return String
243  */
244  public static function realpath($path) {
245  if (file_exists($path)) {
246  return str_replace("\\", "/", realpath($path));
247  }
248  $path = str_replace("\\", "/", $path);
249  $parts = array_filter(explode("/", $path), 'strlen');
250  $absolutes = [];
251  foreach ($parts as $part) {
252  if ('.' == $part) {
253  continue;
254  }
255  if ('..' == $part) {
256  array_pop($absolutes);
257  }
258  else {
259  $absolutes[] = $part;
260  }
261  }
262  $result = implode("/", $absolutes);
263  if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') {
264  $result = '/'.$result;
265  }
266  return $result;
267  }
268 
269  /**
270  * Get a sanitized filename
271  * code from: http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename#2021729
272  * @param $file
273  * @return String
274  */
275  public static function sanitizeFilename($file) {
276  $file = preg_replace("([^\w\s\d\-_~,;:\[\]\(\).])", '', $file);
277  $file = preg_replace("([\.]{2,})", '', $file);
278  return $file;
279  }
280 
281  /**
282  * Fix the name of an existing file to be used with php file functions
283  * @param $file
284  * @return String or null, if the file does not exist
285  */
286  public static function fixFilename($file) {
287  if (file_exists($file)) {
288  return $file;
289  }
290  else {
291  $file = iconv('utf-8', 'cp1252', $file);
292  if (file_exists($file)) {
293  return $file;
294  }
295  }
296  return null;
297  }
298 
299  /**
300  * Url encode a file path
301  * @param $file
302  * @return String
303  */
304  public static function urlencodeFilename($file) {
305  $parts = explode('/', $file);
306  $result = [];
307  foreach ($parts as $part) {
308  $result[] = rawurlencode($part);
309  }
310  return join('/', $result);
311  }
312 
313  /**
314  * Check if the given file exists
315  * @param $file
316  * @return Boolean
317  */
318  public static function fileExists($file) {
319  return self::fixFilename($file) !== null;
320  }
321 
322  /**
323  * Get the trailing name component of a path (locale independent)
324  * @param $file
325  * @return String
326  */
327  public static function basename($file) {
328  $parts = explode('/', $file);
329  return end($parts);
330  }
331 }
332 ?>
static mkdirRec($dirname, $perm=0775)
Recursive directory creation.
Definition: FileUtil.php:215
static getFiles($directory, $pattern='/./', $prependDirectoryName=false, $recursive=false)
Definition: FileUtil.php:98
Input/Output related interfaces and classes.
Definition: namespaces.php:21
static copyRecDir($source, $dest)
Recursive copy for directories.
Definition: FileUtil.php:196
static basename($file)
Get the trailing name component of a path (locale independent)
Definition: FileUtil.php:327
IllegalArgumentException signals an exception in method arguments.
static fileExists($file)
Check if the given file exists.
Definition: FileUtil.php:318
static fputsUnicode($fp, $str)
Write unicode to file.
Definition: FileUtil.php:86
static getDirectories($directory, $pattern='/./', $prependDirectoryName=false, $recursive=false)
Definition: FileUtil.php:138
static emptyDir($dirname)
Empty a directory.
Definition: FileUtil.php:225
FileUtil provides basic support for file functionality like HTTP file upload.
Definition: FileUtil.php:22
static getInstance($name, $dynamicConfiguration=[])
static copyRec($source, $dest)
Recursive copy for files/directories.
Definition: FileUtil.php:179
IOException signals an exception in i/o operations.
Definition: IOException.php:18
static uploadFile($mediaFile, $destName, $mimeTypes=null, $override=true)
Copy an uploaded file to a given destination (only if the mime type matches the given one).
Definition: FileUtil.php:32
static sanitizeFilename($file)
Get a sanitized filename code from: http://stackoverflow.com/questions/2021624/string-sanitizer-for-f...
Definition: FileUtil.php:275
static realpath($path)
Realpath function that also works for non existing paths code from http://www.php....
Definition: FileUtil.php:244
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...
static getMimeType($file)
Get the mime type of the given file.
Definition: FileUtil.php:66
static fixFilename($file)
Fix the name of an existing file to be used with php file functions.
Definition: FileUtil.php:286
static urlencodeFilename($file)
Url encode a file path.
Definition: FileUtil.php:304