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