incremented patch version 1
[openemr.git] / phpmyadmin / libraries / File.class.php
blob62b87637cf4afefbd73add805059513a6d5fdeb4
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * file upload functions
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
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 PhpMyAdmin
20 class PMA_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 string the error message
36 * @access protected
38 var $_error_message = '';
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 string $name file name
82 * @access public
84 public function __construct($name = false)
86 if ($name) {
87 $this->setName($name);
91 /**
92 * destructor
94 * @see PMA_File::cleanUp()
95 * @access public
97 public function __destruct()
99 $this->cleanUp();
103 * deletes file if it is temporary, usally 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 PMA_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 * @param boolean $as_binary whether to return content as binary
163 * @param integer $offset starting offset
164 * @param integer $length length
166 * @return mixed the binary file content as a string,
167 * or false if no content
169 * @access public
171 public function getContent($as_binary = true, $offset = 0, $length = null)
173 if (null === $this->_content) {
174 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
175 return false;
178 if (! $this->isReadable()) {
179 return false;
182 if (function_exists('file_get_contents')) {
183 $this->_content = file_get_contents($this->getName());
184 } elseif ($size = filesize($this->getName())) {
185 $this->_content = fread(fopen($this->getName(), 'rb'), $size);
189 if (! empty($this->_content) && $as_binary) {
190 return '0x' . bin2hex($this->_content);
193 if (null !== $length) {
194 return substr($this->_content, $offset, $length);
195 } elseif ($offset > 0) {
196 return substr($this->_content, $offset);
199 return $this->_content;
203 * Whether file is uploaded.
205 * @access public
207 * @return bool
209 public function isUploaded()
211 return is_uploaded_file($this->getName());
215 * accessor
217 * @access public
218 * @return string PMA_File::$_name
220 public function getName()
222 return $this->_name;
226 * Initializes object from uploaded file.
228 * @param string $name name of file uploaded
230 * @return boolean success
231 * @access public
233 public function setUploadedFile($name)
235 $this->setName($name);
237 if (! $this->isUploaded()) {
238 $this->setName(null);
239 $this->_error_message = __('File was not an uploaded file.');
240 return false;
243 return true;
247 * Loads uploaded file from table change request.
249 * @param string $key the md5 hash of the column name
250 * @param string $rownumber
252 * @return boolean success
253 * @access public
255 public function setUploadedFromTblChangeRequest($key, $rownumber)
257 if (! isset($_FILES['fields_upload'])
258 || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])
260 return false;
262 $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple(
263 $_FILES['fields_upload'],
264 $rownumber,
265 $key
268 // check for file upload errors
269 switch ($file['error']) {
270 // we do not use the PHP constants here cause not all constants
271 // are defined in all versions of PHP - but the correct constants names
272 // are given as comment
273 case 0: //UPLOAD_ERR_OK:
274 return $this->setUploadedFile($file['tmp_name']);
275 break;
276 case 4: //UPLOAD_ERR_NO_FILE:
277 break;
278 case 1: //UPLOAD_ERR_INI_SIZE:
279 $this->_error_message = __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
280 break;
281 case 2: //UPLOAD_ERR_FORM_SIZE:
282 $this->_error_message = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
283 break;
284 case 3: //UPLOAD_ERR_PARTIAL:
285 $this->_error_message = __('The uploaded file was only partially uploaded.');
286 break;
287 case 6: //UPLOAD_ERR_NO_TMP_DIR:
288 $this->_error_message = __('Missing a temporary folder.');
289 break;
290 case 7: //UPLOAD_ERR_CANT_WRITE:
291 $this->_error_message = __('Failed to write file to disk.');
292 break;
293 case 8: //UPLOAD_ERR_EXTENSION:
294 $this->_error_message = __('File upload stopped by extension.');
295 break;
296 default:
297 $this->_error_message = __('Unknown error in file upload.');
298 } // end switch
300 return false;
304 * strips some dimension from the multi-dimensional array from $_FILES
306 * <code>
307 * $file['name']['multi_edit'][$rownumber][$key] = [value]
308 * $file['type']['multi_edit'][$rownumber][$key] = [value]
309 * $file['size']['multi_edit'][$rownumber][$key] = [value]
310 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
311 * $file['error']['multi_edit'][$rownumber][$key] = [value]
313 * // becomes:
315 * $file['name'] = [value]
316 * $file['type'] = [value]
317 * $file['size'] = [value]
318 * $file['tmp_name'] = [value]
319 * $file['error'] = [value]
320 * </code>
322 * @param array $file the array
323 * @param string $rownumber
324 * @param string $key
326 * @return array
327 * @access public
328 * @static
330 public function fetchUploadedFromTblChangeRequestMultiple($file, $rownumber, $key)
332 $new_file = array(
333 'name' => $file['name']['multi_edit'][$rownumber][$key],
334 'type' => $file['type']['multi_edit'][$rownumber][$key],
335 'size' => $file['size']['multi_edit'][$rownumber][$key],
336 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
337 'error' => $file['error']['multi_edit'][$rownumber][$key],
340 return $new_file;
344 * sets the name if the file to the one selected in the tbl_change form
346 * @param string $key the md5 hash of the column name
347 * @param string $rownumber
349 * @return boolean success
350 * @access public
352 public function setSelectedFromTblChangeRequest($key, $rownumber = null)
354 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
355 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
357 // ... whether with multiple rows ...
358 return $this->setLocalSelectedFile(
359 $_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]
361 } else {
362 return false;
367 * Returns possible error message.
369 * @access public
370 * @return string error message
372 public function getError()
374 return $this->_error_message;
378 * Checks whether there was any error.
380 * @access public
381 * @return boolean whether an error occured or not
383 public function isError()
385 return ! empty($this->_error_message);
389 * checks the superglobals provided if the tbl_change form is submitted
390 * and uses the submitted/selected file
392 * @param string $key the md5 hash of the column name
393 * @param string $rownumber
395 * @return boolean success
396 * @access public
398 public function checkTblChangeForm($key, $rownumber)
400 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
401 // well done ...
402 $this->_error_message = '';
403 return true;
404 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
405 // well done ...
406 $this->_error_message = '';
407 return true;
409 // all failed, whether just no file uploaded/selected or an error
411 return false;
415 * Sets named file to be read from UploadDir.
417 * @param string $name file name
419 * @return boolean success
420 * @access public
422 public function setLocalSelectedFile($name)
424 if (empty($GLOBALS['cfg']['UploadDir'])) {
425 return false;
428 $this->setName(
429 PMA_Util::userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name)
431 if (! $this->isReadable()) {
432 $this->_error_message = __('File could not be read');
433 $this->setName(null);
434 return false;
437 return true;
441 * Checks whether file can be read.
443 * @access public
444 * @return boolean whether the file is readable or not
446 public function isReadable()
448 // suppress warnings from being displayed, but not from being logged
449 // any file access outside of open_basedir will issue a warning
450 ob_start();
451 $is_readable = is_readable($this->getName());
452 ob_end_clean();
453 return $is_readable;
457 * If we are on a server with open_basedir, we must move the file
458 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
459 * directory - if needed
461 * @todo move check of $cfg['TempDir'] into PMA_Config?
462 * @access public
463 * @return boolean whether uploaded fiel is fine or not
465 public function checkUploadedFile()
467 if ($this->isReadable()) {
468 return true;
471 if (empty($GLOBALS['cfg']['TempDir'])
472 || ! is_writable($GLOBALS['cfg']['TempDir'])
474 // cannot create directory or access, point user to FAQ 1.11
475 $this->_error_message = __('Error moving the uploaded file, see [doc@faq1-11]FAQ 1.11[/doc]');
476 return false;
479 $new_file_to_upload = tempnam(
480 realpath($GLOBALS['cfg']['TempDir']),
481 basename($this->getName())
484 // suppress warnings from being displayed, but not from being logged
485 // any file access outside of open_basedir will issue a warning
486 ob_start();
487 $move_uploaded_file_result = move_uploaded_file(
488 $this->getName(),
489 $new_file_to_upload
491 ob_end_clean();
492 if (! $move_uploaded_file_result) {
493 $this->_error_message = __('Error while moving uploaded file.');
494 return false;
497 $this->setName($new_file_to_upload);
498 $this->isTemp(true);
500 if (! $this->isReadable()) {
501 $this->_error_message = __('Cannot read (moved) upload file.');
502 return false;
505 return true;
509 * Detects what compression filse uses
511 * @todo move file read part into readChunk() or getChunk()
512 * @todo add support for compression plugins
513 * @access protected
514 * @return string MIME type of compression, none for none
516 protected function detectCompression()
518 // suppress warnings from being displayed, but not from being logged
519 // f.e. any file access outside of open_basedir will issue a warning
520 ob_start();
521 $file = fopen($this->getName(), 'rb');
522 ob_end_clean();
524 if (! $file) {
525 $this->_error_message = __('File could not be read');
526 return false;
530 * @todo
531 * get registered plugins for file compression
533 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
534 if (call_user_func_array(array($plugin['classname'], 'canHandle'), array($this->getName()))) {
535 $this->setCompressionPlugin($plugin);
536 break;
541 $test = fread($file, 4);
542 $len = strlen($test);
543 fclose($file);
545 if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
546 $this->_compression = 'application/gzip';
547 } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
548 $this->_compression = 'application/bzip2';
549 } elseif ($len >= 4 && $test == "PK\003\004") {
550 $this->_compression = 'application/zip';
551 } else {
552 $this->_compression = 'none';
555 return $this->_compression;
559 * Sets whether the content should be decompressed before returned
561 * @param boolean $decompress whether to decompres
563 * @return void
565 public function setDecompressContent($decompress)
567 $this->_decompress = (bool) $decompress;
571 * Returns the file handle
573 * @return object file handle
575 public function getHandle()
577 if (null === $this->_handle) {
578 $this->open();
580 return $this->_handle;
584 * Sets the file handle
586 * @param object $handle file handle
588 * @return void
590 public function setHandle($handle)
592 $this->_handle = $handle;
597 * Sets error message for unsupported compression.
599 * @return void
601 public function errorUnsupported()
603 $this->_error_message = sprintf(
604 __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'),
605 $this->getCompression()
610 * Attempts to open the file.
612 * @return bool
614 public function open()
616 if (! $this->_decompress) {
617 $this->_handle = @fopen($this->getName(), 'r');
620 switch ($this->getCompression()) {
621 case false:
622 return false;
623 case 'application/bzip2':
624 if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
625 $this->_handle = @bzopen($this->getName(), 'r');
626 } else {
627 $this->errorUnsupported();
628 return false;
630 break;
631 case 'application/gzip':
632 if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
633 $this->_handle = @gzopen($this->getName(), 'r');
634 } else {
635 $this->errorUnsupported();
636 return false;
638 break;
639 case 'application/zip':
640 if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
641 include_once './libraries/zip_extension.lib.php';
642 $result = PMA_getZipContents($this->getName());
643 if (! empty($result['error'])) {
644 $this->_error_message = PMA_Message::rawError($result['error']);
645 return false;
646 } else {
647 $this->content_uncompressed = $result['data'];
649 unset($result);
650 } else {
651 $this->errorUnsupported();
652 return false;
654 break;
655 case 'none':
656 $this->_handle = @fopen($this->getName(), 'r');
657 break;
658 default:
659 $this->errorUnsupported();
660 return false;
661 break;
664 return true;
668 * Returns the character set of the file
670 * @return string character set of the file
672 public function getCharset()
674 return $this->_charset;
678 * Sets the character set of the file
680 * @param string $charset character set of the file
682 * @return void
684 public function setCharset($charset)
686 $this->_charset = $charset;
690 * Returns compression used by file.
692 * @return string MIME type of compression, none for none
693 * @access public
695 public function getCompression()
697 if (null === $this->_compression) {
698 return $this->detectCompression();
701 return $this->_compression;
705 * advances the file pointer in the file handle by $length bytes/chars
707 * @param integer $length numbers of chars/bytes to skip
709 * @return boolean
710 * @todo this function is unused
712 public function advanceFilePointer($length)
714 while ($length > 0) {
715 $this->getNextChunk($length);
716 $length -= $this->getChunkSize();
721 * http://bugs.php.net/bug.php?id=29532
722 * bzip reads a maximum of 8192 bytes on windows systems
724 * @param int $max_size maximum size of the next chunk to be returned
726 * @return bool|string
727 * @todo this function is unused
729 public function getNextChunk($max_size = null)
731 if (null !== $max_size) {
732 $size = min($max_size, $this->getChunkSize());
733 } else {
734 $size = $this->getChunkSize();
737 // $result = $this->handler->getNextChunk($size);
738 $result = '';
739 switch ($this->getCompression()) {
740 case 'application/bzip2':
741 $result = '';
742 while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
743 $result .= bzread($this->getHandle(), $size);
745 break;
746 case 'application/gzip':
747 $result = gzread($this->getHandle(), $size);
748 break;
749 case 'application/zip':
751 * if getNextChunk() is used some day,
752 * replace this code by code similar to the one
753 * in open()
755 include_once './libraries/unzip.lib.php';
756 $import_handle = new SimpleUnzip();
757 $import_handle->ReadFile($this->getName());
758 if ($import_handle->Count() == 0) {
759 $this->_error_message = __('No files found inside ZIP archive!');
760 return false;
761 } elseif ($import_handle->GetError(0) != 0) {
762 $this->_error_message = __('Error in ZIP archive:')
763 . ' ' . $import_handle->GetErrorMsg(0);
764 return false;
765 } else {
766 $result = $import_handle->GetData(0);
769 break;
770 case 'none':
771 $result = fread($this->getHandle(), $size);
772 break;
773 default:
774 return false;
777 if ($GLOBALS['charset_conversion']) {
778 $result = PMA_convert_string($this->getCharset(), 'utf-8', $result);
779 } else {
781 * Skip possible byte order marks (I do not think we need more
782 * charsets, but feel free to add more, you can use wikipedia for
783 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
785 * @todo BOM could be used for charset autodetection
787 if ($this->getOffset() === 0) {
788 // UTF-8
789 if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
790 $result = substr($result, 3);
791 // UTF-16 BE, LE
792 } elseif (strncmp($result, "\xFE\xFF", 2) == 0
793 || strncmp($result, "\xFF\xFE", 2) == 0) {
794 $result = substr($result, 2);
799 $this->_offset += $size;
800 if (0 === $result) {
801 return true;
803 return $result;
807 * Returns the offset
809 * @return integer the offset
811 public function getOffset()
813 return $this->_offset;
817 * Returns the chunk size
819 * @return integer the chunk size
821 public function getChunkSize()
823 return $this->_chunk_size;
827 * Sets the chunk size
829 * @param integer $chunk_size the chunk size
831 * @return void
833 public function setChunkSize($chunk_size)
835 $this->_chunk_size = (int) $chunk_size;
839 * Returns the length of the content in the file
841 * @return integer the length of the file content
843 public function getContentLength()
845 return strlen($this->_content);
849 * Returns whether the end of the file has been reached
851 * @return boolean whether the end of the file has been reached
853 public function eof()
855 if ($this->getHandle()) {
856 return feof($this->getHandle());
857 } else {
858 return ($this->getOffset() >= $this->getContentLength());