Translation update done using Pootle.
[phpmyadmin/ammaryasirr.git] / libraries / File.class.php
blob66a88b80430fab3ea77ef7dfb8c3212a9e278c8b
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * file upload functions
6 * @package phpMyAdmin
7 */
9 /**
11 * @todo when uploading a file into a blob field, should we also consider using
12 * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
13 * @package phpMyAdmin
15 class PMA_File
17 /**
18 * @var string the temporary file name
19 * @access protected
21 var $_name = null;
23 /**
24 * @var string the content
25 * @access protected
27 var $_content = null;
29 /**
30 * @var string the error message
31 * @access protected
33 var $_error_message = '';
35 /**
36 * @var bool whether the file is temporary or not
37 * @access protected
39 var $_is_temp = false;
41 /**
42 * @var string type of compression
43 * @access protected
45 var $_compression = null;
47 /**
48 * @var integer
50 var $_offset = 0;
52 /**
53 * @var integer size of chunk to read with every step
55 var $_chunk_size = 32768;
57 /**
58 * @var resource file handle
60 var $_handle = null;
62 /**
63 * @var boolean whether to decompress content before returning
65 var $_decompress = false;
67 /**
68 * @var string charset of file
70 var $_charset = null;
72 /**
73 * @staticvar string most recent BLOB repository reference
75 static $_recent_bs_reference = null;
77 /**
78 * constructor
80 * @access public
81 * @param string $name file name
83 function __construct($name = false)
85 if ($name) {
86 $this->setName($name);
90 /**
91 * destructor
93 * @see PMA_File::cleanUp()
94 * @access public
96 function __destruct()
98 $this->cleanUp();
102 * deletes file if it is temporary, usally from a moved upload file
104 * @access public
105 * @return boolean success
107 function cleanUp()
109 if ($this->isTemp()) {
110 return $this->delete();
113 return true;
117 * deletes the file
119 * @access public
120 * @return boolean success
122 function delete()
124 return unlink($this->getName());
128 * checks or sets the temp flag for this file
129 * file objects with temp flags are deleted with object destruction
131 * @access public
132 * @param boolean sets the temp flag
133 * @return boolean PMA_File::$_is_temp
135 function isTemp($is_temp = null)
137 if (null !== $is_temp) {
138 $this->_is_temp = (bool) $is_temp;
141 return $this->_is_temp;
145 * accessor
147 * @access public
148 * @param string $name file name
150 function setName($name)
152 $this->_name = trim($name);
156 * @access public
157 * @return string binary file content
159 function getContent($as_binary = true, $offset = 0, $length = null)
161 if (null === $this->_content) {
162 if ($this->isUploaded() && ! $this->checkUploadedFile()) {
163 return false;
166 if (! $this->isReadable()) {
167 return false;
170 if (function_exists('file_get_contents')) {
171 $this->_content = file_get_contents($this->getName());
172 } elseif ($size = filesize($this->getName())) {
173 $this->_content = fread(fopen($this->getName(), 'rb'), $size);
177 if (! empty($this->_content) && $as_binary) {
178 return '0x' . bin2hex($this->_content);
181 if (null !== $length) {
182 return substr($this->_content, $offset, $length);
183 } elseif ($offset > 0) {
184 return substr($this->_content, $offset);
187 return $this->_content;
191 * @access public
193 function isUploaded()
195 return is_uploaded_file($this->getName());
199 * accessor
201 * @access public
202 * @return string PMA_File::$_name
204 function getName()
206 return $this->_name;
210 * @access public
211 * @param string name of file uploaded
212 * @return boolean success
214 function setUploadedFile($name)
216 $this->setName($name);
218 if (! $this->isUploaded()) {
219 $this->setName(null);
220 $this->_error_message = __('File was not an uploaded file.');
221 return false;
224 return true;
228 * @access public
229 * @param string $key the md5 hash of the column name
230 * @param string $rownumber
231 * @return boolean success
233 function setUploadedFromTblChangeRequest($key, $rownumber)
235 if (! isset($_FILES['fields_upload']) || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])) {
236 return false;
238 $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple($_FILES['fields_upload'], $rownumber, $key);
240 // for blobstreaming
241 $is_bs_upload = false;
243 // check if this field requires a repository upload
244 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
245 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ? true : false;
247 // if request is an upload to the BLOB repository
248 if ($is_bs_upload) {
249 $bs_db = $_REQUEST['db'];
250 $bs_table = $_REQUEST['table'];
251 $tmp_filename = $file['tmp_name'];
252 $tmp_file_type = $file['type'];
254 if (! $tmp_file_type) {
255 $tmp_file_type = null;
258 if (! $bs_db || ! $bs_table) {
259 $this->_error_message = __('Unknown error while uploading.');
260 return false;
262 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
263 PMA_File::setRecentBLOBReference($blob_url);
264 } // end if ($is_bs_upload)
266 // check for file upload errors
267 switch ($file['error']) {
268 // we do not use the PHP constants here cause not all constants
269 // are defined in all versions of PHP - but the correct constants names
270 // are given as comment
271 case 0: //UPLOAD_ERR_OK:
272 return $this->setUploadedFile($file['tmp_name']);
273 break;
274 case 4: //UPLOAD_ERR_NO_FILE:
275 break;
276 case 1: //UPLOAD_ERR_INI_SIZE:
277 $this->_error_message = __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
278 break;
279 case 2: //UPLOAD_ERR_FORM_SIZE:
280 $this->_error_message = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
281 break;
282 case 3: //UPLOAD_ERR_PARTIAL:
283 $this->_error_message = __('The uploaded file was only partially uploaded.');
284 break;
285 case 6: //UPLOAD_ERR_NO_TMP_DIR:
286 $this->_error_message = __('Missing a temporary folder.');
287 break;
288 case 7: //UPLOAD_ERR_CANT_WRITE:
289 $this->_error_message = __('Failed to write file to disk.');
290 break;
291 case 8: //UPLOAD_ERR_EXTENSION:
292 $this->_error_message = __('File upload stopped by extension.');
293 break;
294 default:
295 $this->_error_message = __('Unknown error in file upload.');
296 } // end switch
298 return false;
302 * strips some dimension from the multi-dimensional array from $_FILES
304 * <code>
305 * $file['name']['multi_edit'][$rownumber][$key] = [value]
306 * $file['type']['multi_edit'][$rownumber][$key] = [value]
307 * $file['size']['multi_edit'][$rownumber][$key] = [value]
308 * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
309 * $file['error']['multi_edit'][$rownumber][$key] = [value]
311 * // becomes:
313 * $file['name'] = [value]
314 * $file['type'] = [value]
315 * $file['size'] = [value]
316 * $file['tmp_name'] = [value]
317 * $file['error'] = [value]
318 * </code>
320 * @access public
321 * @static
322 * @param array $file the array
323 * @param string $rownumber
324 * @param string $key
325 * @return array
327 function fetchUploadedFromTblChangeRequestMultiple($file, $rownumber, $key)
329 $new_file = array(
330 'name' => $file['name']['multi_edit'][$rownumber][$key],
331 'type' => $file['type']['multi_edit'][$rownumber][$key],
332 'size' => $file['size']['multi_edit'][$rownumber][$key],
333 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
334 'error' => $file['error']['multi_edit'][$rownumber][$key],
337 return $new_file;
341 * sets the name if the file to the one selected in the tbl_change form
343 * @access public
344 * @param string $key the md5 hash of the column name
345 * @param string $rownumber
346 * @return boolean success
348 function setSelectedFromTblChangeRequest($key, $rownumber = null)
350 if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
351 && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])) {
352 // ... whether with multiple rows ...
353 // for blobstreaming
354 $is_bs_upload = false;
356 // check if this field requires a repository upload
357 if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
358 $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ? true : false;
361 // is a request to upload file to BLOB repository using uploadDir mechanism
362 if ($is_bs_upload) {
363 $bs_db = $_REQUEST['db'];
364 $bs_table = $_REQUEST['table'];
365 $tmp_filename = $GLOBALS['cfg']['UploadDir'] . '/' . $_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$rownumber];
367 // check if fileinfo library exists
368 if ($PMA_Config->get('FILEINFO_EXISTS')) {
369 // attempt to init fileinfo
370 $finfo = finfo_open(FILEINFO_MIME);
372 // fileinfo exists
373 if ($finfo) {
374 // pass in filename to fileinfo and close fileinfo handle after
375 $tmp_file_type = finfo_file($finfo, $tmp_filename);
376 finfo_close($finfo);
378 } else {
379 // no fileinfo library exists, use file command
380 $tmp_file_type = exec("file -bi " . escapeshellarg($tmp_filename));
383 if (! $tmp_file_type) {
384 $tmp_file_type = null;
387 if (! $bs_db || !$bs_table) {
388 $this->_error_message = __('Unknown error while uploading.');
389 return false;
391 $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
392 PMA_File::setRecentBLOBReference($blob_url);
393 } // end if ($is_bs_upload)
395 return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]);
396 } else {
397 return false;
402 * @access public
403 * @return string error message
405 function getError()
407 return $this->_error_message;
411 * @access public
412 * @return boolean whether an error occured or not
414 function isError()
416 return ! empty($this->_error_message);
420 * checks the superglobals provided if the tbl_change form is submitted
421 * and uses the submitted/selected file
423 * @access public
424 * @param string $key the md5 hash of the column name
425 * @param string $rownumber
426 * @return boolean success
428 function checkTblChangeForm($key, $rownumber)
430 if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
431 // well done ...
432 $this->_error_message = '';
433 return true;
434 } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
435 // well done ...
436 $this->_error_message = '';
437 return true;
439 // all failed, whether just no file uploaded/selected or an error
441 return false;
446 * @access public
447 * @param string $name
448 * @return boolean success
450 function setLocalSelectedFile($name)
452 if (empty($GLOBALS['cfg']['UploadDir'])) return false;
454 $this->setName(PMA_userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name));
455 if (! $this->isReadable()) {
456 $this->_error_message = __('File could not be read');
457 $this->setName(null);
458 return false;
461 return true;
465 * @access public
466 * @return boolean whether the file is readable or not
468 function isReadable()
470 // suppress warnings from being displayed, but not from being logged
471 // any file access outside of open_basedir will issue a warning
472 ob_start();
473 $is_readable = is_readable($this->getName());
474 ob_end_clean();
475 return $is_readable;
479 * If we are on a server with open_basedir, we must move the file
480 * before opening it. The FAQ 1.11 explains how to create the "./tmp"
481 * directory - if needed
483 * @todo move check of $cfg['TempDir'] into PMA_Config?
484 * @access public
485 * @return boolean whether uploaded fiel is fine or not
487 function checkUploadedFile()
489 if ($this->isReadable()) {
490 return true;
493 if (empty($GLOBALS['cfg']['TempDir']) || ! is_writable($GLOBALS['cfg']['TempDir'])) {
494 // cannot create directory or access, point user to FAQ 1.11
495 $this->_error_message = __('Error moving the uploaded file, see [a@./Documentation.html#faq1_11@Documentation]FAQ 1.11[/a]');
496 return false;
499 $new_file_to_upload = tempnam(realpath($GLOBALS['cfg']['TempDir']), basename($this->getName()));
501 // suppress warnings from being displayed, but not from being logged
502 // any file access outside of open_basedir will issue a warning
503 ob_start();
504 $move_uploaded_file_result = move_uploaded_file($this->getName(), $new_file_to_upload);
505 ob_end_clean();
506 if (! $move_uploaded_file_result) {
507 $this->_error_message = __('Error while moving uploaded file.');
508 return false;
511 $this->setName($new_file_to_upload);
512 $this->isTemp(true);
514 if (! $this->isReadable()) {
515 $this->_error_message = __('Cannot read (moved) upload file.');
516 return false;
519 return true;
523 * Detects what compression filse uses
525 * @todo move file read part into readChunk() or getChunk()
526 * @todo add support for compression plugins
527 * @access protected
528 * @return string MIME type of compression, none for none
530 function _detectCompression()
532 // suppress warnings from being displayed, but not from being logged
533 // f.e. any file access outside of open_basedir will issue a warning
534 ob_start();
535 $file = fopen($this->getName(), 'rb');
536 ob_end_clean();
538 if (! $file) {
539 $this->_error_message = __('File could not be read');
540 return false;
544 * @todo
545 * get registered plugins for file compression
547 foreach (PMA_getPlugins($type = 'compression') as $plugin) {
548 if (call_user_func_array(array($plugin['classname'], 'canHandle'), array($this->getName()))) {
549 $this->setCompressionPlugin($plugin);
550 break;
555 $test = fread($file, 4);
556 $len = strlen($test);
557 fclose($file);
559 if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
560 $this->_compression = 'application/gzip';
561 } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
562 $this->_compression = 'application/bzip2';
563 } elseif ($len >= 4 && $test == "PK\003\004") {
564 $this->_compression = 'application/zip';
565 } else {
566 $this->_compression = 'none';
569 return $this->_compression;
573 * whether the content should be decompressed before returned
575 function setDecompressContent($decompress)
577 $this->_decompress = (bool) $decompress;
580 function getHandle()
582 if (null === $this->_handle) {
583 $this->open();
585 return $this->_handle;
588 function setHandle($handle)
590 $this->_handle = $handle;
596 function open()
598 if (! $this->_decompress) {
599 $this->_handle = @fopen($this->getName(), 'r');
602 switch ($this->getCompression()) {
603 case false:
604 return false;
605 case 'application/bzip2':
606 if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
607 $this->_handle = @bzopen($this->getName(), 'r');
608 } else {
609 $this->_error_message = sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
610 return false;
612 break;
613 case 'application/gzip':
614 if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
615 $this->_handle = @gzopen($this->getName(), 'r');
616 } else {
617 $this->_error_message = sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
618 return false;
620 break;
621 case 'application/zip':
622 if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
623 include_once './libraries/zip_extension.lib.php';
624 $result = PMA_getZipContents($this->getName());
625 if (! empty($result['error'])) {
626 $this->_error_message = PMA_Message::rawError($result['error']);
627 return false;
628 } else {
629 $this->content_uncompressed = $result['data'];
631 unset($result);
632 } else {
633 $this->_error_message = sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
634 return false;
636 break;
637 case 'none':
638 $this->_handle = @fopen($this->getName(), 'r');
639 break;
640 default:
641 $this->_error_message = sprintf(__('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'), $this->getCompression());
642 return false;
643 break;
646 return true;
649 function getCharset()
651 return $this->_charset;
654 function setCharset($charset)
656 $this->_charset = $charset;
660 * @return string MIME type of compression, none for none
661 * @access public
663 function getCompression()
665 if (null === $this->_compression) {
666 return $this->_detectCompression();
669 return $this->_compression;
673 * advances the file pointer in the file handle by $length bytes/chars
675 * @param integer $length numbers of chars/bytes to skip
676 * @return boolean
677 * @todo this function is unused
679 function advanceFilePointer($length)
681 while ($length > 0) {
682 $this->getNextChunk($length);
683 $length -= $this->getChunkSize();
688 * http://bugs.php.net/bug.php?id=29532
689 * bzip reads a maximum of 8192 bytes on windows systems
690 * @todo this function is unused
692 function getNextChunk($max_size = null)
694 if (null !== $max_size) {
695 $size = min($max_size, $this->getChunkSize());
696 } else {
697 $size = $this->getChunkSize();
700 // $result = $this->handler->getNextChunk($size);
701 $result = '';
702 switch ($this->getCompression()) {
703 case 'application/bzip2':
704 $result = '';
705 while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
706 $result .= bzread($this->getHandle(), $size);
708 break;
709 case 'application/gzip':
710 $result = gzread($this->getHandle(), $size);
711 break;
712 case 'application/zip':
714 * if getNextChunk() is used some day,
715 * replace this code by code similar to the one
716 * in open()
718 include_once './libraries/unzip.lib.php';
719 $import_handle = new SimpleUnzip();
720 $import_handle->ReadFile($this->getName());
721 if ($import_handle->Count() == 0) {
722 $this->_error_message = __('No files found inside ZIP archive!');
723 return false;
724 } elseif ($import_handle->GetError(0) != 0) {
725 $this->_error_message = __('Error in ZIP archive:')
726 . ' ' . $import_handle->GetErrorMsg(0);
727 return false;
728 } else {
729 $result = $import_handle->GetData(0);
732 break;
733 case 'none':
734 $result = fread($this->getHandle(), $size);
735 break;
736 default:
737 return false;
740 if ($GLOBALS['charset_conversion']) {
741 $result = PMA_convert_string($this->getCharset(), 'utf-8', $result);
742 } else {
744 * Skip possible byte order marks (I do not think we need more
745 * charsets, but feel free to add more, you can use wikipedia for
746 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
748 * @todo BOM could be used for charset autodetection
750 if ($this->getOffset() === 0) {
751 // UTF-8
752 if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
753 $result = substr($result, 3);
754 // UTF-16 BE, LE
755 } elseif (strncmp($result, "\xFE\xFF", 2) == 0
756 || strncmp($result, "\xFF\xFE", 2) == 0) {
757 $result = substr($result, 2);
762 $this->_offset += $size;
763 if (0 === $result) {
764 return true;
766 return $result;
769 function getOffset()
771 return $this->_offset;
774 function getChunkSize()
776 return $this->_chunk_size;
779 function setChunkSize($chunk_size)
781 $this->_chunk_size = (int) $chunk_size;
784 function getContentLength()
786 return strlen($this->_content);
789 function eof()
791 if ($this->getHandle()) {
792 return feof($this->getHandle());
793 } else {
794 return ($this->getOffset() >= $this->getContentLength());
800 * sets reference to most recent BLOB repository reference
802 * @access public
803 * @param string - BLOB repository reference
805 static function setRecentBLOBReference($ref)
807 PMA_File::$_recent_bs_reference = $ref;
811 * retrieves reference to most recent BLOB repository reference
813 * @access public
814 * @return string - most recent BLOB repository reference
816 static function getRecentBLOBReference()
818 $ref = PMA_File::$_recent_bs_reference;
819 PMA_File::$_recent_bs_reference = null;
821 return $ref;