Translated using Weblate (Albanian)
[phpmyadmin.git] / libraries / File.php
blob79fe0ed90e7695465212b673cc5b0b213b859ee5
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * file upload functions
6 * @package PMA\libraries
7 */
8 namespace PMA\libraries;
10 use PMA\libraries\config\ConfigFile;
12 /**
13 * File wrapper class
15 * @todo when uploading a file into a blob field, should we also consider using
16 * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
18 * @package PMA\libraries
20 class File
22 /**
23 * @var string the temporary file name
24 * @access protected
26 var $_name = null;
28 /**
29 * @var string the content
30 * @access protected
32 var $_content = null;
34 /**
35 * @var Message|null the error message
36 * @access protected
38 var $_error_message = null;
40 /**
41 * @var bool whether the file is temporary or not
42 * @access protected
44 var $_is_temp = false;
46 /**
47 * @var string type of compression
48 * @access protected
50 var $_compression = null;
52 /**
53 * @var integer
55 var $_offset = 0;
57 /**
58 * @var integer size of chunk to read with every step
60 var $_chunk_size = 32768;
62 /**
63 * @var resource file handle
65 var $_handle = null;
67 /**
68 * @var boolean whether to decompress content before returning
70 var $_decompress = false;
72 /**
73 * @var string charset of file
75 var $_charset = null;
77 /**
78 * constructor
80 * @param boolean|string $name file name or false
82 * @access public
84 public function __construct($name = false)
86 if ($name && is_string($name)) {
87 $this->setName($name);
91 /**
92 * destructor
94 * @see File::cleanUp()
95 * @access public
97 public function __destruct()
99 $this->cleanUp();
103 * deletes file if it is temporary, usually from a moved upload file
105 * @access public
106 * @return boolean success
108 public function cleanUp()
110 if ($this->isTemp()) {
111 return $this->delete();
114 return true;
118 * deletes the file
120 * @access public
121 * @return boolean success
123 public function delete()
125 return unlink($this->getName());
129 * checks or sets the temp flag for this file
130 * file objects with temp flags are deleted with object destruction
132 * @param boolean $is_temp sets the temp flag
134 * @return boolean File::$_is_temp
135 * @access public
137 public function isTemp($is_temp = null)
139 if (null !== $is_temp) {
140 $this->_is_temp = (bool) $is_temp;
143 return $this->_is_temp;
147 * accessor
149 * @param string $name file name
151 * @return void
152 * @access public
154 public function setName($name)
156 $this->_name = trim($name);
160 * Gets file content
162 * @return string|false the binary file content,
163 * or false if no content
165 * @access public
167 public function getRawContent()
169 if (null === $this->_content) {
170 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
171 return false;
174 if (! $this->isReadable()) {
175 return false;
178 if (function_exists('file_get_contents')) {
179 $this->_content = file_get_contents($this->getName());
180 } elseif ($size = filesize($this->getName())) {
181 $handle = fopen($this->getName(), 'rb');
182 $this->_content = fread($handle, $size);
183 fclose($handle);
187 return $this->_content;
191 * Gets file content
193 * @return string|false the binary file content as a string,
194 * or false if no content
196 * @access public
198 public function getContent()
200 $result = $this->getRawContent();
201 if ($result === false) {
202 return false;
204 return '0x' . bin2hex($result);
208 * Whether file is uploaded.
210 * @access public
212 * @return bool
214 public function isUploaded()
216 return is_uploaded_file($this->getName());
220 * accessor
222 * @access public
223 * @return string File::$_name
225 public function getName()
227 return $this->_name;
231 * Initializes object from uploaded file.
233 * @param string $name name of file uploaded
235 * @return boolean success
236 * @access public
238 public function setUploadedFile($name)
240 $this->setName($name);
242 if (! $this->isUploaded()) {
243 $this->setName(null);
244 $this->_error_message = Message::error(__('File was not an uploaded file.'));
245 return false;
248 return true;
252 * Loads uploaded file from table change request.
254 * @param string $key the md5 hash of the column name
255 * @param string $rownumber number of row to process
257 * @return boolean success
258 * @access public
260 public function setUploadedFromTblChangeRequest($key, $rownumber)
262 if (! isset($_FILES['fields_upload'])
263 || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])
265 return false;
267 $file = File::fetchUploadedFromTblChangeRequestMultiple(
268 $_FILES['fields_upload'],
269 $rownumber,
270 $key
273 // check for file upload errors
274 switch ($file['error']) {
275 // we do not use the PHP constants here cause not all constants
276 // are defined in all versions of PHP - but the correct constants names
277 // are given as comment
278 case 0: //UPLOAD_ERR_OK:
279 return $this->setUploadedFile($file['tmp_name']);
280 case 4: //UPLOAD_ERR_NO_FILE:
281 break;
282 case 1: //UPLOAD_ERR_INI_SIZE:
283 $this->_error_message = Message::error(__(
284 'The uploaded file exceeds the upload_max_filesize directive in '
285 . 'php.ini.'
287 break;
288 case 2: //UPLOAD_ERR_FORM_SIZE:
289 $this->_error_message = Message::error(__(
290 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '
291 . 'specified in the HTML form.'
293 break;
294 case 3: //UPLOAD_ERR_PARTIAL:
295 $this->_error_message = Message::error(__(
296 'The uploaded file was only partially uploaded.'
298 break;
299 case 6: //UPLOAD_ERR_NO_TMP_DIR:
300 $this->_error_message = Message::error(__('Missing a temporary folder.'));
301 break;
302 case 7: //UPLOAD_ERR_CANT_WRITE:
303 $this->_error_message = Message::error(__('Failed to write file to disk.'));
304 break;
305 case 8: //UPLOAD_ERR_EXTENSION:
306 $this->_error_message = Message::error(__('File upload stopped by extension.'));
307 break;
308 default:
309 $this->_error_message = Message::error(__('Unknown error in file upload.'));
310 } // end switch
312 return false;
316 * strips some dimension from the multi-dimensional array from $_FILES
318 * <code>
319 * $file['name']['multi_edit'][$rownumber][$key] = [value]
320 * $file['type']['multi_edit'][$rownumber][$key] = [value]
321 * $file['size']['multi_edit'][$rownumber][$key] = [value]
322 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
323 * $file['error']['multi_edit'][$rownumber][$key] = [value]
325 * // becomes:
327 * $file['name'] = [value]
328 * $file['type'] = [value]
329 * $file['size'] = [value]
330 * $file['tmp_name'] = [value]
331 * $file['error'] = [value]
332 * </code>
334 * @param array $file the array
335 * @param string $rownumber number of row to process
336 * @param string $key key to process
338 * @return array
339 * @access public
340 * @static
342 public function fetchUploadedFromTblChangeRequestMultiple(
343 $file, $rownumber, $key
345 $new_file = array(
346 'name' => $file['name']['multi_edit'][$rownumber][$key],
347 'type' => $file['type']['multi_edit'][$rownumber][$key],
348 'size' => $file['size']['multi_edit'][$rownumber][$key],
349 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
350 'error' => $file['error']['multi_edit'][$rownumber][$key],
353 return $new_file;
357 * sets the name if the file to the one selected in the tbl_change form
359 * @param string $key the md5 hash of the column name
360 * @param string $rownumber number of row to process
362 * @return boolean success
363 * @access public
365 public function setSelectedFromTblChangeRequest($key, $rownumber = null)
367 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
368 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
370 // ... whether with multiple rows ...
371 return $this->setLocalSelectedFile(
372 $_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]
374 } else {
375 return false;
380 * Returns possible error message.
382 * @access public
383 * @return Message|null error message
385 public function getError()
387 return $this->_error_message;
391 * Checks whether there was any error.
393 * @access public
394 * @return boolean whether an error occurred or not
396 public function isError()
398 return ! is_null($this->_error_message);
402 * checks the superglobals provided if the tbl_change form is submitted
403 * and uses the submitted/selected file
405 * @param string $key the md5 hash of the column name
406 * @param string $rownumber number of row to process
408 * @return boolean success
409 * @access public
411 public function checkTblChangeForm($key, $rownumber)
413 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
414 // well done ...
415 $this->_error_message = null;
416 return true;
417 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
418 // well done ...
419 $this->_error_message = null;
420 return true;
422 // all failed, whether just no file uploaded/selected or an error
424 return false;
428 * Sets named file to be read from UploadDir.
430 * @param string $name file name
432 * @return boolean success
433 * @access public
435 public function setLocalSelectedFile($name)
437 if (empty($GLOBALS['cfg']['UploadDir'])) {
438 return false;
441 $this->setName(
442 Util::userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name)
444 if (@is_link($this->getName())) {
445 $this->_error_message = __('File is a symbolic link');
446 $this->setName(null);
447 return false;
449 if (! $this->isReadable()) {
450 $this->_error_message = Message::error(__('File could not be read!'));
451 $this->setName(null);
452 return false;
455 return true;
459 * Checks whether file can be read.
461 * @access public
462 * @return boolean whether the file is readable or not
464 public function isReadable()
466 // suppress warnings from being displayed, but not from being logged
467 // any file access outside of open_basedir will issue a warning
468 return @is_readable($this->getName());
472 * If we are on a server with open_basedir, we must move the file
473 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
474 * directory - if needed
476 * @todo move check of $cfg['TempDir'] into Config?
477 * @access public
478 * @return boolean whether uploaded file is fine or not
480 public function checkUploadedFile()
482 if ($this->isReadable()) {
483 return true;
486 $tmp_subdir = ConfigFile::getDefaultTempDirectory();
487 if (is_null($tmp_subdir)) {
488 // cannot create directory or access, point user to FAQ 1.11
489 $this->_error_message = Message::error(__(
490 'Error moving the uploaded file, see [doc@faq1-11]FAQ 1.11[/doc].'
492 return false;
495 $new_file_to_upload = tempnam(
496 $tmp_subdir,
497 basename($this->getName())
500 // suppress warnings from being displayed, but not from being logged
501 // any file access outside of open_basedir will issue a warning
502 ob_start();
503 $move_uploaded_file_result = move_uploaded_file(
504 $this->getName(),
505 $new_file_to_upload
507 ob_end_clean();
508 if (! $move_uploaded_file_result) {
509 $this->_error_message = Message::error(__('Error while moving uploaded file.'));
510 return false;
513 $this->setName($new_file_to_upload);
514 $this->isTemp(true);
516 if (! $this->isReadable()) {
517 $this->_error_message = Message::error(__('Cannot read uploaded file.'));
518 return false;
521 return true;
525 * Detects what compression the file uses
527 * @todo move file read part into readChunk() or getChunk()
528 * @todo add support for compression plugins
529 * @access protected
530 * @return string|false false on error, otherwise string MIME type of
531 * compression, none for none
533 protected function detectCompression()
535 // suppress warnings from being displayed, but not from being logged
536 // f.e. any file access outside of open_basedir will issue a warning
537 ob_start();
538 $file = fopen($this->getName(), 'rb');
539 ob_end_clean();
541 if (! $file) {
542 $this->_error_message = Message::error(__('File could not be read!'));
543 return false;
547 * @todo
548 * get registered plugins for file compression
550 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
551 if ($plugin['classname']::canHandle($this->getName())) {
552 $this->setCompressionPlugin($plugin);
553 break;
558 $this->_compression = Util::getCompressionMimeType($file);
559 return $this->_compression;
563 * Sets whether the content should be decompressed before returned
565 * @param boolean $decompress whether to decompress
567 * @return void
569 public function setDecompressContent($decompress)
571 $this->_decompress = (bool) $decompress;
575 * Returns the file handle
577 * @return resource file handle
579 public function getHandle()
581 if (null === $this->_handle) {
582 $this->open();
584 return $this->_handle;
588 * Sets the file handle
590 * @param object $handle file handle
592 * @return void
594 public function setHandle($handle)
596 $this->_handle = $handle;
601 * Sets error message for unsupported compression.
603 * @return void
605 public function errorUnsupported()
607 $this->_error_message = Message::error(sprintf(
609 'You attempted to load file with unsupported compression (%s). '
610 . 'Either support for it is not implemented or disabled by your '
611 . 'configuration.'
613 $this->getCompression()
618 * Attempts to open the file.
620 * @return bool
622 public function open()
624 if (! $this->_decompress) {
625 $this->_handle = @fopen($this->getName(), 'r');
628 switch ($this->getCompression()) {
629 case false:
630 return false;
631 case 'application/bzip2':
632 if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
633 $this->_handle = @bzopen($this->getName(), 'r');
634 } else {
635 $this->errorUnsupported();
636 return false;
638 break;
639 case 'application/gzip':
640 if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
641 $this->_handle = @gzopen($this->getName(), 'r');
642 } else {
643 $this->errorUnsupported();
644 return false;
646 break;
647 case 'application/zip':
648 if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
649 return $this->openZip();
650 } else {
651 $this->errorUnsupported();
652 return false;
654 case 'none':
655 $this->_handle = @fopen($this->getName(), 'r');
656 break;
657 default:
658 $this->errorUnsupported();
659 return false;
662 return ($this->_handle !== false);
666 * Opens file from zip
668 * @param string|null $specific_entry Entry to open
670 * @return bool
672 public function openZip($specific_entry = null)
674 include_once './libraries/zip_extension.lib.php';
675 $result = PMA_getZipContents($this->getName(), $specific_entry);
676 if (! empty($result['error'])) {
677 $this->_error_message = Message::rawError($result['error']);
678 return false;
680 $this->_content = $result['data'];
681 $this->_offset = 0;
682 return true;
686 * Checks whether we've reached end of file
688 * @return bool
690 public function eof()
692 if (! is_null($this->_handle)) {
693 return feof($this->_handle);
695 return $this->_offset == strlen($this->_content);
699 * Closes the file
701 * @return void
703 public function close()
705 if (! is_null($this->_handle)) {
706 fclose($this->_handle);
707 $this->_handle = null;
708 } else {
709 $this->_content = '';
710 $this->_offset = 0;
712 $this->cleanUp();
716 * Reads data from file
718 * @param int $size Number of bytes to read
720 * @return string
722 public function read($size)
724 switch ($this->_compression) {
725 case 'application/bzip2':
726 return bzread($this->_handle, $size);
727 case 'application/gzip':
728 return gzread($this->_handle, $size);
729 case 'application/zip':
730 $result = mb_strcut($this->_content, $this->_offset, $size);
731 $this->_offset += strlen($result);
732 return $result;
733 case 'none':
734 default:
735 return fread($this->_handle, $size);
740 * Returns the character set of the file
742 * @return string character set of the file
744 public function getCharset()
746 return $this->_charset;
750 * Sets the character set of the file
752 * @param string $charset character set of the file
754 * @return void
756 public function setCharset($charset)
758 $this->_charset = $charset;
762 * Returns compression used by file.
764 * @return string MIME type of compression, none for none
765 * @access public
767 public function getCompression()
769 if (null === $this->_compression) {
770 return $this->detectCompression();
773 return $this->_compression;
777 * Returns the offset
779 * @return integer the offset
781 public function getOffset()
783 return $this->_offset;
787 * Returns the chunk size
789 * @return integer the chunk size
791 public function getChunkSize()
793 return $this->_chunk_size;
797 * Sets the chunk size
799 * @param integer $chunk_size the chunk size
801 * @return void
803 public function setChunkSize($chunk_size)
805 $this->_chunk_size = (int) $chunk_size;
809 * Returns the length of the content in the file
811 * @return integer the length of the file content
813 public function getContentLength()
815 return strlen($this->_content);