]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/IO/FileTrait.php
Code quality changes
[galette.git] / galette / lib / Galette / IO / FileTrait.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Files
7 *
8 * PHP version 5
9 *
10 * Copyright © 2013-2014 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category IO
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2013-2014 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.8.1 - 2014-09-18
35 */
36
37 namespace Galette\IO;
38
39 use Analog\Analog;
40
41 /**
42 * Files
43 *
44 * @category IO
45 * @name Csv
46 * @package Galette
47 * @author Johan Cwiklinski <johan@x-tnd.be>
48 * @copyright 2013-2014 The Galette Team
49 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
50 * @link http://galette.tuxfamily.org
51 * @since Available since 0.8.1 - 2014-09-18
52 */
53
54 trait FileTrait
55 {
56 //array keys contain litteral value of each forbidden character
57 //(to be used when showing an error).
58 //Maybe is there a better way to handle this...
59 protected $bad_chars = array(
60 '.' => '\.',
61 '\\' => '\\\\',
62 "'" => "'",
63 ' ' => ' ',
64 '/' => '\/',
65 ':' => ':',
66 '*' => '\*',
67 '?' => '\?',
68 '"' => '"',
69 '<' => '<',
70 '>' => '>',
71 '|' => '|'
72 );
73
74 protected $name;
75 protected $dest_dir;
76 protected $allowed_extensions = array();
77 protected $allowed_mimes = array();
78 protected $maxlenght;
79
80 public static $mime_types = array(
81 'txt' => 'text/plain',
82 'htm' => 'text/html',
83 'html' => 'text/html',
84 'xhtml' => 'application/xhtml+xml',
85 'xht' => 'application/xhtml+xml',
86 'php' => 'text/html',
87 'css' => 'text/css',
88 'js' => 'application/javascript',
89 'json' => 'application/json',
90 'xml' => 'application/xml',
91 'xslt' => 'application/xslt+xml',
92 'xsl' => 'application/xml',
93 'dtd' => 'application/xml-dtd',
94 'atom' => 'application/atom+xml',
95 'mathml' => 'application/mathml+xml',
96 'rdf' => 'application/rdf+xml',
97 'smi' => 'application/smil',
98 'smil' => 'application/smil',
99 'vxml' => 'application/voicexml+xml',
100 'latex' => 'application/x-latex',
101 'tcl' => 'application/x-tcl',
102 'tex' => 'application/x-tex',
103 'texinfo' => 'application/x-texinfo',
104 'wrl' => 'model/vrml',
105 'wrml' => 'model/vrml',
106 'ics' => 'text/calendar',
107 'ifb' => 'text/calendar',
108 'sgml' => 'text/sgml',
109 'htc' => 'text/x-component',
110 'pgp' => 'application/pgp-signature',
111 'rtf' => 'application/rtf',
112 // images
113 'png' => 'image/png',
114 'jpeg' => 'image/jpeg',
115 'jpg' => 'image/jpeg',
116 'gif' => 'image/gif',
117 'bmp' => 'image/bmp',
118 'ico' => 'image/x-icon',
119 'tiff' => 'image/tiff',
120 'tif' => 'image/tiff',
121 'svg' => 'image/svg+xml',
122 'svgz' => 'image/svg+xml',
123 'djvu' => 'image/vnd.djvu',
124 'djv' => 'image/vnd.djvu',
125 // archives
126 'zip' => 'application/zip',
127 'rar' => 'application/x-rar-compressed',
128 'tar' => 'application/x-tar',
129 'gz' => 'application/x-gzip',
130 'tgz' => 'application/x-gzip',
131 'bz2' => 'application/x-bzip2',
132 // audio/video
133 'mp2' => 'audio/mpeg',
134 'mp3' => 'audio/mpeg',
135 'qt' => 'video/quicktime',
136 'mov' => 'video/quicktime',
137 'mpeg' => 'video/mpeg',
138 'mpg' => 'video/mpeg',
139 'mpe' => 'video/mpeg',
140 'wav' => 'audio/wav',
141 'aiff' => 'audio/aiff',
142 'aif' => 'audio/aiff',
143 'avi' => 'video/msvideo',
144 'wmv' => 'video/x-ms-wmv',
145 'ogg' => 'application/ogg',
146 'flv' => 'video/x-flv',
147 'dvi' => 'application/x-dvi',
148 'au' => 'audio/basic',
149 'snd' => 'audio/basic',
150 'mid' => 'audio/midi',
151 'midi' => 'audio/midi',
152 'm3u' => 'audio/x-mpegurl',
153 'm4u' => 'video/vnd.mpegurl',
154 'ram' => 'audio/x-pn-realaudio',
155 'ra' => 'audio/x-pn-realaudio',
156 'rm' => 'application/vnd.rn-realmedia',
157 // adobe
158 'pdf' => 'application/pdf',
159 'psd' => 'image/vnd.adobe.photoshop',
160 'ai' => 'application/postscript',
161 'eps' => 'application/postscript',
162 'ps' => 'application/postscript',
163 'swf' => 'application/x-shockwave-flash',
164 // ms office
165 'doc' => 'application/msword',
166 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
167 'xls' => 'application/vnd.ms-excel',
168 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
169 'ppt' => 'application/vnd.ms-powerpoint',
170 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
171 'pps' => 'application/vnd.ms-powerpoint',
172 // open office
173 'odt' => 'application/vnd.oasis.opendocument.text',
174 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
175 'odc' => 'application/vnd.oasis.opendocument.chart',
176 'odb' => 'application/vnd.oasis.opendocument.database',
177 'odg' => 'application/vnd.oasis.opendocument.graphics',
178 'odp' => 'application/vnd.oasis.opendocument.presentation',
179 );
180
181 /**
182 * Initialization
183 *
184 * @param string $dest File destination directory
185 * @param array $extensions Array of permitted extensions
186 * @param array $mimes Array of permitted mime types
187 * @param int $maxlenght Maximum lenght for each file
188 *
189 * @return void
190 */
191 protected function init(
192 $dest,
193 $extensions = null,
194 $mimes = null,
195 $maxlenght = null
196 ) {
197 if ($dest !== null && substr($dest, -1) !== '/') {
198 //normalize path
199 $dest .= '/';
200 }
201 $this->dest_dir = $dest;
202 if ($extensions !== null) {
203 $this->allowed_extensions = $extensions;
204 }
205 if ($mimes !== null) {
206 $this->allowed_mimes = $mimes;
207 }
208 if ($maxlenght !== null) {
209 $this->maxlenght = $maxlenght;
210 } else {
211 $this->maxlenght = self::MAX_FILE_SIZE;
212 }
213 }
214
215 /**
216 * Copy existing file to new Location
217 *
218 * @param string $dest Destination directory
219 *
220 * @return boolean
221 */
222 public function copyTo($dest)
223 {
224 $res = copy(
225 $this->dest_dir . $this->name,
226 $dest . $this->name
227 );
228 if ($res === true) {
229 $this->dest_dir = $dest;
230 }
231 return $res;
232 }
233
234 /**
235 * Stores an file on the disk
236 *
237 * @param object $file the uploaded file
238 * @param boolean $ajax If the file cames from an ajax call (dnd)
239 *
240 * @return true|false|int result of the storage process
241 */
242 public function store($file, $ajax = false)
243 {
244 $class = get_class($this);
245
246 $this->name = $file['name'];
247 $tmpfile = $file['tmp_name'];
248
249 //First, does the file have a valid name?
250 $reg = "/^([^" . implode('', $this->bad_chars) . "]+)\.";
251 if (count($this->allowed_extensions) > 0) {
252 $reg .= "(" . implode('|', $this->allowed_extensions) . ")";
253 } else {
254 $reg .= "(.*)";
255 }
256 $reg .= "$/i";
257 if (preg_match($reg, $this->name, $matches)) {
258 Analog::log(
259 '[' . $class . '] Filename and extension are OK, proceed.',
260 Analog::DEBUG
261 );
262 } else {
263 $erreg = "/^([^" . implode('', $this->bad_chars) . "]+)\.(.*)/i";
264 $m = preg_match($erreg, $this->name, $errmatches);
265
266 $err_msg = '[' . $class . '] ';
267 if ($m == 1) {
268 //ok, we got a good filename and an extension. Extension is bad :)
269 $err_msg .= 'Invalid extension for file ' . $this->name . '.';
270 $ret = self::INVALID_EXTENSION;
271 } else {
272 $err_msg = 'Invalid filename `' . $this->name . '` (Tip: ';
273 $err_msg .= preg_replace(
274 '|%s|',
275 htmlentities($this->getBadChars()),
276 "file name should not contain any of: %s). "
277 );
278 $ret = self::INVALID_FILENAME;
279 }
280
281 Analog::log(
282 $err_msg,
283 Analog::ERROR
284 );
285 return $ret;
286 }
287
288 //Second, let's check file size
289 if ($file['size'] > ($this->maxlenght * 1024)) {
290 Analog::log(
291 '[' . $class . '] File is too big (' . ($file['size'] * 1024) .
292 'Ko for maximum authorized ' . ($this->maxlenght * 1024) .
293 'Ko',
294 Analog::ERROR
295 );
296 return self::FILE_TOO_BIG;
297 } else {
298 Analog::log('[' . $class . '] Filesize is OK, proceed', Analog::DEBUG);
299 }
300
301 $mime = $this->getMimeType($tmpfile);
302
303 if (
304 count($this->allowed_mimes) > 0
305 && !in_array($mime, $this->allowed_mimes)
306 ) {
307 Analog::log(
308 '[' . $class . '] Mimetype `' . $mime . '` not allowed',
309 Analog::ERROR
310 );
311 return self::MIME_NOT_ALLOWED;
312 } else {
313 Analog::log(
314 '[' . $class . '] Mimetype is allowed, proceed',
315 Analog::DEBUG
316 );
317 }
318
319 $new_file = $this->dest_dir . $this->name;
320
321 if (file_exists($new_file)) {
322 Analog::log(
323 '[' . $class . '] File `' . $new_file . '` already exists',
324 Analog::ERROR
325 );
326 return self::NEW_FILE_EXISTS;
327 }
328
329 if ($ajax === true) {
330 $in_place = rename($tmpfile, $new_file);
331 } else {
332 $in_place = move_uploaded_file($tmpfile, $new_file);
333 }
334
335 if ($in_place === false) {
336 return self::CANT_WRITE;
337 }
338 return true;
339 }
340
341 /**
342 * Get destination dir
343 *
344 * @return string
345 */
346 public function getDestDir()
347 {
348 return $this->dest_dir;
349 }
350
351 /**
352 * Set destination directory
353 *
354 * @param string $dir Directory
355 *
356 * @return void
357 */
358 public function setDestDir($dir)
359 {
360 $this->dest_dir = $dir;
361 }
362
363 /**
364 * Get file name
365 *
366 * @return string
367 */
368 public function getFileName()
369 {
370 return $this->name;
371 }
372
373 /**
374 * Set file name
375 *
376 * @param string $name file name
377 *
378 * @return void
379 */
380 public function setFileName($name)
381 {
382 $this->name = $name;
383 }
384
385 /**
386 * Returns unauthorized characters litteral values quoted, comma separated values
387 *
388 * @return string comma separated disallowed characters
389 */
390 public function getBadChars()
391 {
392 return '`' . implode('`, `', array_keys($this->bad_chars)) . '`';
393 }
394
395 /**
396 * Returns allowed extensions
397 *
398 * @return string comma separated allowed extensiosn
399 */
400 public function getAllowedExts()
401 {
402 return implode(', ', $this->allowed_extensions);
403 }
404
405 /**
406 * Return the array of allowed mime types
407 *
408 * @return array
409 */
410 public function getAllowedMimeTypes()
411 {
412 return $this->allowed_mimes;
413 }
414
415 /**
416 * Get file mime type
417 *
418 * @param string $file File
419 *
420 * @return string
421 */
422 public static function getMimeType($file)
423 {
424 $mime = null;
425 $class = get_called_class();
426
427 if (function_exists('finfo_open')) {
428 Analog::log(
429 '[' . $class . '] Function File Info exist ',
430 Analog::DEBUG
431 );
432 $finfo = finfo_open(FILEINFO_MIME_TYPE);
433 $mime = finfo_file($finfo, $file);
434 finfo_close($finfo);
435 } elseif (function_exists('mime_content_type')) {
436 Analog::log(
437 '[' . $class . '] Function mime_content_type exist ',
438 Analog::DEBUG
439 );
440 $mime = mime_content_type($file);
441 } else {
442 Analog::log(
443 '[' . $class . '] Search from extension ',
444 Analog::DEBUG
445 );
446 $exploded = explode('.', $file);
447 $ext = strtolower(array_pop($exploded));
448 Analog::log(
449 '[' . $class . '] Extension : ' . $ext,
450 Analog::DEBUG
451 );
452 if (array_key_exists($ext, self::$mime_types)) {
453 $mime = self::$mime_types[$ext];
454 } else {
455 $mime = 'application/octet-stream';
456 }
457 }
458
459 Analog::log(
460 '[' . $class . '] Found mimetype : ' . $mime . ' for file ' . $file,
461 Analog::INFO
462 );
463 return $mime;
464 }
465
466 /**
467 * Return textual error message
468 *
469 * @param int $code The error code
470 *
471 * @return string Localized message
472 */
473 protected function getErrorMessageFromCode($code)
474 {
475 $error = _T("An error occurred.");
476
477 switch ($code) {
478 case self::INVALID_FILENAME:
479 $error = _T("File name is invalid, it should not contain any special character or space.");
480 break;
481 case self::INVALID_EXTENSION:
482 $error = preg_replace(
483 '|%s|',
484 $this->getAllowedExts(),
485 _T("File extension is not allowed, only %s files are.")
486 );
487 break;
488 case self::FILE_TOO_BIG:
489 $error = preg_replace(
490 '|%d|',
491 $this->maxlenght,
492 _T("File is too big. Maximum allowed size is %dKo")
493 );
494 break;
495 case self::MIME_NOT_ALLOWED:
496 /** FIXME: should be more descriptive */
497 $error = _T("Mime-Type not allowed");
498 break;
499 case self::NEW_FILE_EXISTS:
500 $error = _T("A file with that name already exists!");
501 break;
502 case self::INVALID_FILE:
503 $error = _T("File does not comply with requirements.");
504 break;
505 case self::CANT_WRITE:
506 $error = _T("Unable to write file or temporary file");
507 break;
508 }
509
510 return $error;
511 }
512
513 /**
514 * Return textual error message
515 *
516 * @param int $code The error code
517 *
518 * @return string Localized message
519 */
520 public function getErrorMessage($code)
521 {
522 return $this->getErrorMessageFromCode($code);
523 }
524
525 /**
526 * Return textual error message send by PHP after upload attempt
527 *
528 * @param int $error_code The error code
529 *
530 * @return string Localized message
531 */
532 public function getPhpErrorMessage($error_code)
533 {
534 switch ($error_code) {
535 case UPLOAD_ERR_INI_SIZE:
536 return _T("The uploaded file exceeds the upload_max_filesize directive in php.ini");
537 case UPLOAD_ERR_FORM_SIZE:
538 return _T("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form");
539 case UPLOAD_ERR_PARTIAL:
540 return _T("The uploaded file was only partially uploaded");
541 case UPLOAD_ERR_NO_FILE:
542 return _T("No file was uploaded");
543 case UPLOAD_ERR_NO_TMP_DIR:
544 return _T("Missing a temporary folder");
545 case UPLOAD_ERR_CANT_WRITE:
546 return _T("Failed to write file to disk");
547 case UPLOAD_ERR_EXTENSION:
548 return _T("File upload stopped by extension");
549 default:
550 return _T("Unknown upload error");
551 }
552 }
553 }