PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/phpMyAdmin/libraries/File.class.php

https://bitbucket.org/izubizarreta/https-bitbucket.org-bityvip
PHP | 827 lines | 442 code | 92 blank | 293 comment | 84 complexity | 149132fce122a04c49efca7d42cf2a5c MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.0, JSON, GPL-2.0, BSD-3-Clause, LGPL-2.1, MIT
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * file upload functions
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. /**
  9. *
  10. * @todo when uploading a file into a blob field, should we also consider using
  11. * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
  12. * @package PhpMyAdmin
  13. */
  14. class PMA_File
  15. {
  16. /**
  17. * @var string the temporary file name
  18. * @access protected
  19. */
  20. var $_name = null;
  21. /**
  22. * @var string the content
  23. * @access protected
  24. */
  25. var $_content = null;
  26. /**
  27. * @var string the error message
  28. * @access protected
  29. */
  30. var $_error_message = '';
  31. /**
  32. * @var bool whether the file is temporary or not
  33. * @access protected
  34. */
  35. var $_is_temp = false;
  36. /**
  37. * @var string type of compression
  38. * @access protected
  39. */
  40. var $_compression = null;
  41. /**
  42. * @var integer
  43. */
  44. var $_offset = 0;
  45. /**
  46. * @var integer size of chunk to read with every step
  47. */
  48. var $_chunk_size = 32768;
  49. /**
  50. * @var resource file handle
  51. */
  52. var $_handle = null;
  53. /**
  54. * @var boolean whether to decompress content before returning
  55. */
  56. var $_decompress = false;
  57. /**
  58. * @var string charset of file
  59. */
  60. var $_charset = null;
  61. /**
  62. * @staticvar string most recent BLOB repository reference
  63. */
  64. static $_recent_bs_reference = null;
  65. /**
  66. * constructor
  67. *
  68. * @access public
  69. * @param string $name file name
  70. */
  71. function __construct($name = false)
  72. {
  73. if ($name) {
  74. $this->setName($name);
  75. }
  76. }
  77. /**
  78. * destructor
  79. *
  80. * @see PMA_File::cleanUp()
  81. * @access public
  82. */
  83. function __destruct()
  84. {
  85. $this->cleanUp();
  86. }
  87. /**
  88. * deletes file if it is temporary, usally from a moved upload file
  89. *
  90. * @access public
  91. * @return boolean success
  92. */
  93. function cleanUp()
  94. {
  95. if ($this->isTemp()) {
  96. return $this->delete();
  97. }
  98. return true;
  99. }
  100. /**
  101. * deletes the file
  102. *
  103. * @access public
  104. * @return boolean success
  105. */
  106. function delete()
  107. {
  108. return unlink($this->getName());
  109. }
  110. /**
  111. * checks or sets the temp flag for this file
  112. * file objects with temp flags are deleted with object destruction
  113. *
  114. * @access public
  115. * @param boolean sets the temp flag
  116. * @return boolean PMA_File::$_is_temp
  117. */
  118. function isTemp($is_temp = null)
  119. {
  120. if (null !== $is_temp) {
  121. $this->_is_temp = (bool) $is_temp;
  122. }
  123. return $this->_is_temp;
  124. }
  125. /**
  126. * accessor
  127. *
  128. * @access public
  129. * @param string $name file name
  130. */
  131. function setName($name)
  132. {
  133. $this->_name = trim($name);
  134. }
  135. /**
  136. * @access public
  137. * @return string binary file content
  138. */
  139. function getContent($as_binary = true, $offset = 0, $length = null)
  140. {
  141. if (null === $this->_content) {
  142. if ($this->isUploaded() && ! $this->checkUploadedFile()) {
  143. return false;
  144. }
  145. if (! $this->isReadable()) {
  146. return false;
  147. }
  148. if (function_exists('file_get_contents')) {
  149. $this->_content = file_get_contents($this->getName());
  150. } elseif ($size = filesize($this->getName())) {
  151. $this->_content = fread(fopen($this->getName(), 'rb'), $size);
  152. }
  153. }
  154. if (! empty($this->_content) && $as_binary) {
  155. return '0x' . bin2hex($this->_content);
  156. }
  157. if (null !== $length) {
  158. return substr($this->_content, $offset, $length);
  159. } elseif ($offset > 0) {
  160. return substr($this->_content, $offset);
  161. }
  162. return $this->_content;
  163. }
  164. /**
  165. * @access public
  166. * @return bool
  167. */
  168. function isUploaded()
  169. {
  170. return is_uploaded_file($this->getName());
  171. }
  172. /**
  173. * accessor
  174. *
  175. * @access public
  176. * @return string PMA_File::$_name
  177. */
  178. function getName()
  179. {
  180. return $this->_name;
  181. }
  182. /**
  183. * @access public
  184. * @param string name of file uploaded
  185. * @return boolean success
  186. */
  187. function setUploadedFile($name)
  188. {
  189. $this->setName($name);
  190. if (! $this->isUploaded()) {
  191. $this->setName(null);
  192. $this->_error_message = __('File was not an uploaded file.');
  193. return false;
  194. }
  195. return true;
  196. }
  197. /**
  198. * @access public
  199. * @param string $key the md5 hash of the column name
  200. * @param string $rownumber
  201. * @return boolean success
  202. */
  203. function setUploadedFromTblChangeRequest($key, $rownumber)
  204. {
  205. if (! isset($_FILES['fields_upload']) || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])) {
  206. return false;
  207. }
  208. $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple($_FILES['fields_upload'], $rownumber, $key);
  209. // for blobstreaming
  210. $is_bs_upload = false;
  211. // check if this field requires a repository upload
  212. if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
  213. $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ? true : false;
  214. }
  215. // if request is an upload to the BLOB repository
  216. if ($is_bs_upload) {
  217. $bs_db = $_REQUEST['db'];
  218. $bs_table = $_REQUEST['table'];
  219. $tmp_filename = $file['tmp_name'];
  220. $tmp_file_type = $file['type'];
  221. if (! $tmp_file_type) {
  222. $tmp_file_type = null;
  223. }
  224. if (! $bs_db || ! $bs_table) {
  225. $this->_error_message = __('Unknown error while uploading.');
  226. return false;
  227. }
  228. $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
  229. PMA_File::setRecentBLOBReference($blob_url);
  230. } // end if ($is_bs_upload)
  231. // check for file upload errors
  232. switch ($file['error']) {
  233. // we do not use the PHP constants here cause not all constants
  234. // are defined in all versions of PHP - but the correct constants names
  235. // are given as comment
  236. case 0: //UPLOAD_ERR_OK:
  237. return $this->setUploadedFile($file['tmp_name']);
  238. break;
  239. case 4: //UPLOAD_ERR_NO_FILE:
  240. break;
  241. case 1: //UPLOAD_ERR_INI_SIZE:
  242. $this->_error_message = __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
  243. break;
  244. case 2: //UPLOAD_ERR_FORM_SIZE:
  245. $this->_error_message = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
  246. break;
  247. case 3: //UPLOAD_ERR_PARTIAL:
  248. $this->_error_message = __('The uploaded file was only partially uploaded.');
  249. break;
  250. case 6: //UPLOAD_ERR_NO_TMP_DIR:
  251. $this->_error_message = __('Missing a temporary folder.');
  252. break;
  253. case 7: //UPLOAD_ERR_CANT_WRITE:
  254. $this->_error_message = __('Failed to write file to disk.');
  255. break;
  256. case 8: //UPLOAD_ERR_EXTENSION:
  257. $this->_error_message = __('File upload stopped by extension.');
  258. break;
  259. default:
  260. $this->_error_message = __('Unknown error in file upload.');
  261. } // end switch
  262. return false;
  263. }
  264. /**
  265. * strips some dimension from the multi-dimensional array from $_FILES
  266. *
  267. * <code>
  268. * $file['name']['multi_edit'][$rownumber][$key] = [value]
  269. * $file['type']['multi_edit'][$rownumber][$key] = [value]
  270. * $file['size']['multi_edit'][$rownumber][$key] = [value]
  271. * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
  272. * $file['error']['multi_edit'][$rownumber][$key] = [value]
  273. *
  274. * // becomes:
  275. *
  276. * $file['name'] = [value]
  277. * $file['type'] = [value]
  278. * $file['size'] = [value]
  279. * $file['tmp_name'] = [value]
  280. * $file['error'] = [value]
  281. * </code>
  282. *
  283. * @access public
  284. * @static
  285. * @param array $file the array
  286. * @param string $rownumber
  287. * @param string $key
  288. * @return array
  289. */
  290. function fetchUploadedFromTblChangeRequestMultiple($file, $rownumber, $key)
  291. {
  292. $new_file = array(
  293. 'name' => $file['name']['multi_edit'][$rownumber][$key],
  294. 'type' => $file['type']['multi_edit'][$rownumber][$key],
  295. 'size' => $file['size']['multi_edit'][$rownumber][$key],
  296. 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
  297. 'error' => $file['error']['multi_edit'][$rownumber][$key],
  298. );
  299. return $new_file;
  300. }
  301. /**
  302. * sets the name if the file to the one selected in the tbl_change form
  303. *
  304. * @access public
  305. * @param string $key the md5 hash of the column name
  306. * @param string $rownumber
  307. * @return boolean success
  308. */
  309. function setSelectedFromTblChangeRequest($key, $rownumber = null)
  310. {
  311. if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
  312. && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])) {
  313. // ... whether with multiple rows ...
  314. // for blobstreaming
  315. $is_bs_upload = false;
  316. // check if this field requires a repository upload
  317. if (isset($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key])) {
  318. $is_bs_upload = ($_REQUEST['upload_blob_repo']['multi_edit'][$rownumber][$key] == "on") ? true : false;
  319. }
  320. // is a request to upload file to BLOB repository using uploadDir mechanism
  321. if ($is_bs_upload) {
  322. $bs_db = $_REQUEST['db'];
  323. $bs_table = $_REQUEST['table'];
  324. $tmp_filename = $GLOBALS['cfg']['UploadDir'] . '/' . $_REQUEST['fields_uploadlocal_' . $key]['multi_edit'][$rownumber];
  325. // check if fileinfo library exists
  326. if ($PMA_Config->get('FILEINFO_EXISTS')) {
  327. // attempt to init fileinfo
  328. $finfo = finfo_open(FILEINFO_MIME);
  329. // fileinfo exists
  330. if ($finfo) {
  331. // pass in filename to fileinfo and close fileinfo handle after
  332. $tmp_file_type = finfo_file($finfo, $tmp_filename);
  333. finfo_close($finfo);
  334. }
  335. } else {
  336. // no fileinfo library exists, use file command
  337. $tmp_file_type = exec("file -bi " . escapeshellarg($tmp_filename));
  338. }
  339. if (! $tmp_file_type) {
  340. $tmp_file_type = null;
  341. }
  342. if (! $bs_db || !$bs_table) {
  343. $this->_error_message = __('Unknown error while uploading.');
  344. return false;
  345. }
  346. $blob_url = PMA_BS_UpLoadFile($bs_db, $bs_table, $tmp_file_type, $tmp_filename);
  347. PMA_File::setRecentBLOBReference($blob_url);
  348. } // end if ($is_bs_upload)
  349. return $this->setLocalSelectedFile($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]);
  350. } else {
  351. return false;
  352. }
  353. }
  354. /**
  355. * @access public
  356. * @return string error message
  357. */
  358. function getError()
  359. {
  360. return $this->_error_message;
  361. }
  362. /**
  363. * @access public
  364. * @return boolean whether an error occured or not
  365. */
  366. function isError()
  367. {
  368. return ! empty($this->_error_message);
  369. }
  370. /**
  371. * checks the superglobals provided if the tbl_change form is submitted
  372. * and uses the submitted/selected file
  373. *
  374. * @access public
  375. * @param string $key the md5 hash of the column name
  376. * @param string $rownumber
  377. * @return boolean success
  378. */
  379. function checkTblChangeForm($key, $rownumber)
  380. {
  381. if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
  382. // well done ...
  383. $this->_error_message = '';
  384. return true;
  385. } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
  386. // well done ...
  387. $this->_error_message = '';
  388. return true;
  389. }
  390. // all failed, whether just no file uploaded/selected or an error
  391. return false;
  392. }
  393. /**
  394. *
  395. * @access public
  396. * @param string $name
  397. * @return boolean success
  398. */
  399. function setLocalSelectedFile($name)
  400. {
  401. if (empty($GLOBALS['cfg']['UploadDir'])) return false;
  402. $this->setName(PMA_userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name));
  403. if (! $this->isReadable()) {
  404. $this->_error_message = __('File could not be read');
  405. $this->setName(null);
  406. return false;
  407. }
  408. return true;
  409. }
  410. /**
  411. * @access public
  412. * @return boolean whether the file is readable or not
  413. */
  414. function isReadable()
  415. {
  416. // suppress warnings from being displayed, but not from being logged
  417. // any file access outside of open_basedir will issue a warning
  418. ob_start();
  419. $is_readable = is_readable($this->getName());
  420. ob_end_clean();
  421. return $is_readable;
  422. }
  423. /**
  424. * If we are on a server with open_basedir, we must move the file
  425. * before opening it. The FAQ 1.11 explains how to create the "./tmp"
  426. * directory - if needed
  427. *
  428. * @todo move check of $cfg['TempDir'] into PMA_Config?
  429. * @access public
  430. * @return boolean whether uploaded fiel is fine or not
  431. */
  432. function checkUploadedFile()
  433. {
  434. if ($this->isReadable()) {
  435. return true;
  436. }
  437. if (empty($GLOBALS['cfg']['TempDir']) || ! is_writable($GLOBALS['cfg']['TempDir'])) {
  438. // cannot create directory or access, point user to FAQ 1.11
  439. $this->_error_message = __('Error moving the uploaded file, see [a@./Documentation.html#faq1_11@Documentation]FAQ 1.11[/a]');
  440. return false;
  441. }
  442. $new_file_to_upload = tempnam(realpath($GLOBALS['cfg']['TempDir']), basename($this->getName()));
  443. // suppress warnings from being displayed, but not from being logged
  444. // any file access outside of open_basedir will issue a warning
  445. ob_start();
  446. $move_uploaded_file_result = move_uploaded_file($this->getName(), $new_file_to_upload);
  447. ob_end_clean();
  448. if (! $move_uploaded_file_result) {
  449. $this->_error_message = __('Error while moving uploaded file.');
  450. return false;
  451. }
  452. $this->setName($new_file_to_upload);
  453. $this->isTemp(true);
  454. if (! $this->isReadable()) {
  455. $this->_error_message = __('Cannot read (moved) upload file.');
  456. return false;
  457. }
  458. return true;
  459. }
  460. /**
  461. * Detects what compression filse uses
  462. *
  463. * @todo move file read part into readChunk() or getChunk()
  464. * @todo add support for compression plugins
  465. * @access protected
  466. * @return string MIME type of compression, none for none
  467. */
  468. function _detectCompression()
  469. {
  470. // suppress warnings from being displayed, but not from being logged
  471. // f.e. any file access outside of open_basedir will issue a warning
  472. ob_start();
  473. $file = fopen($this->getName(), 'rb');
  474. ob_end_clean();
  475. if (! $file) {
  476. $this->_error_message = __('File could not be read');
  477. return false;
  478. }
  479. /**
  480. * @todo
  481. * get registered plugins for file compression
  482. foreach (PMA_getPlugins($type = 'compression') as $plugin) {
  483. if (call_user_func_array(array($plugin['classname'], 'canHandle'), array($this->getName()))) {
  484. $this->setCompressionPlugin($plugin);
  485. break;
  486. }
  487. }
  488. */
  489. $test = fread($file, 4);
  490. $len = strlen($test);
  491. fclose($file);
  492. if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
  493. $this->_compression = 'application/gzip';
  494. } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
  495. $this->_compression = 'application/bzip2';
  496. } elseif ($len >= 4 && $test == "PK\003\004") {
  497. $this->_compression = 'application/zip';
  498. } else {
  499. $this->_compression = 'none';
  500. }
  501. return $this->_compression;
  502. }
  503. /**
  504. * whether the content should be decompressed before returned
  505. */
  506. function setDecompressContent($decompress)
  507. {
  508. $this->_decompress = (bool) $decompress;
  509. }
  510. function getHandle()
  511. {
  512. if (null === $this->_handle) {
  513. $this->open();
  514. }
  515. return $this->_handle;
  516. }
  517. function setHandle($handle)
  518. {
  519. $this->_handle = $handle;
  520. }
  521. /**
  522. * @return bool
  523. */
  524. function open()
  525. {
  526. if (! $this->_decompress) {
  527. $this->_handle = @fopen($this->getName(), 'r');
  528. }
  529. switch ($this->getCompression()) {
  530. case false:
  531. return false;
  532. case 'application/bzip2':
  533. if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
  534. $this->_handle = @bzopen($this->getName(), 'r');
  535. } else {
  536. $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());
  537. return false;
  538. }
  539. break;
  540. case 'application/gzip':
  541. if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
  542. $this->_handle = @gzopen($this->getName(), 'r');
  543. } else {
  544. $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());
  545. return false;
  546. }
  547. break;
  548. case 'application/zip':
  549. if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
  550. include_once './libraries/zip_extension.lib.php';
  551. $result = PMA_getZipContents($this->getName());
  552. if (! empty($result['error'])) {
  553. $this->_error_message = PMA_Message::rawError($result['error']);
  554. return false;
  555. } else {
  556. $this->content_uncompressed = $result['data'];
  557. }
  558. unset($result);
  559. } else {
  560. $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());
  561. return false;
  562. }
  563. break;
  564. case 'none':
  565. $this->_handle = @fopen($this->getName(), 'r');
  566. break;
  567. default:
  568. $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());
  569. return false;
  570. break;
  571. }
  572. return true;
  573. }
  574. function getCharset()
  575. {
  576. return $this->_charset;
  577. }
  578. function setCharset($charset)
  579. {
  580. $this->_charset = $charset;
  581. }
  582. /**
  583. * @return string MIME type of compression, none for none
  584. * @access public
  585. */
  586. function getCompression()
  587. {
  588. if (null === $this->_compression) {
  589. return $this->_detectCompression();
  590. }
  591. return $this->_compression;
  592. }
  593. /**
  594. * advances the file pointer in the file handle by $length bytes/chars
  595. *
  596. * @param integer $length numbers of chars/bytes to skip
  597. * @return boolean
  598. * @todo this function is unused
  599. */
  600. function advanceFilePointer($length)
  601. {
  602. while ($length > 0) {
  603. $this->getNextChunk($length);
  604. $length -= $this->getChunkSize();
  605. }
  606. }
  607. /**
  608. * http://bugs.php.net/bug.php?id=29532
  609. * bzip reads a maximum of 8192 bytes on windows systems
  610. * @todo this function is unused
  611. * @param int $max_size
  612. * @return bool|string
  613. */
  614. function getNextChunk($max_size = null)
  615. {
  616. if (null !== $max_size) {
  617. $size = min($max_size, $this->getChunkSize());
  618. } else {
  619. $size = $this->getChunkSize();
  620. }
  621. // $result = $this->handler->getNextChunk($size);
  622. $result = '';
  623. switch ($this->getCompression()) {
  624. case 'application/bzip2':
  625. $result = '';
  626. while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
  627. $result .= bzread($this->getHandle(), $size);
  628. }
  629. break;
  630. case 'application/gzip':
  631. $result = gzread($this->getHandle(), $size);
  632. break;
  633. case 'application/zip':
  634. /*
  635. * if getNextChunk() is used some day,
  636. * replace this code by code similar to the one
  637. * in open()
  638. *
  639. include_once './libraries/unzip.lib.php';
  640. $import_handle = new SimpleUnzip();
  641. $import_handle->ReadFile($this->getName());
  642. if ($import_handle->Count() == 0) {
  643. $this->_error_message = __('No files found inside ZIP archive!');
  644. return false;
  645. } elseif ($import_handle->GetError(0) != 0) {
  646. $this->_error_message = __('Error in ZIP archive:')
  647. . ' ' . $import_handle->GetErrorMsg(0);
  648. return false;
  649. } else {
  650. $result = $import_handle->GetData(0);
  651. }
  652. */
  653. break;
  654. case 'none':
  655. $result = fread($this->getHandle(), $size);
  656. break;
  657. default:
  658. return false;
  659. }
  660. if ($GLOBALS['charset_conversion']) {
  661. $result = PMA_convert_string($this->getCharset(), 'utf-8', $result);
  662. } else {
  663. /**
  664. * Skip possible byte order marks (I do not think we need more
  665. * charsets, but feel free to add more, you can use wikipedia for
  666. * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
  667. *
  668. * @todo BOM could be used for charset autodetection
  669. */
  670. if ($this->getOffset() === 0) {
  671. // UTF-8
  672. if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
  673. $result = substr($result, 3);
  674. // UTF-16 BE, LE
  675. } elseif (strncmp($result, "\xFE\xFF", 2) == 0
  676. || strncmp($result, "\xFF\xFE", 2) == 0) {
  677. $result = substr($result, 2);
  678. }
  679. }
  680. }
  681. $this->_offset += $size;
  682. if (0 === $result) {
  683. return true;
  684. }
  685. return $result;
  686. }
  687. function getOffset()
  688. {
  689. return $this->_offset;
  690. }
  691. function getChunkSize()
  692. {
  693. return $this->_chunk_size;
  694. }
  695. function setChunkSize($chunk_size)
  696. {
  697. $this->_chunk_size = (int) $chunk_size;
  698. }
  699. function getContentLength()
  700. {
  701. return strlen($this->_content);
  702. }
  703. function eof()
  704. {
  705. if ($this->getHandle()) {
  706. return feof($this->getHandle());
  707. } else {
  708. return ($this->getOffset() >= $this->getContentLength());
  709. }
  710. }
  711. /**
  712. * sets reference to most recent BLOB repository reference
  713. *
  714. * @access public
  715. * @param string - BLOB repository reference
  716. */
  717. static function setRecentBLOBReference($ref)
  718. {
  719. PMA_File::$_recent_bs_reference = $ref;
  720. }
  721. /**
  722. * retrieves reference to most recent BLOB repository reference
  723. *
  724. * @access public
  725. * @return string - most recent BLOB repository reference
  726. */
  727. static function getRecentBLOBReference()
  728. {
  729. $ref = PMA_File::$_recent_bs_reference;
  730. PMA_File::$_recent_bs_reference = null;
  731. return $ref;
  732. }
  733. }
  734. ?>