PageRenderTime 78ms CodeModel.GetById 44ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Varien/File/Uploader.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 556 lines | 283 code | 62 blank | 211 comment | 51 complexity | 0818f811f6f2e96d4c47176c76d9dd70 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Varien
  22. * @package Varien_File
  23. * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * File upload class
  28. *
  29. * ATTENTION! This class must be used like abstract class and must added
  30. * validation by protected file extension list to extended class
  31. *
  32. * @category Varien
  33. * @package Varien_File
  34. * @author Magento Core Team <core@magentocommerce.com>
  35. */
  36. class Varien_File_Uploader
  37. {
  38. /**
  39. * Uploaded file handle (copy of $_FILES[] element)
  40. *
  41. * @var array
  42. * @access protected
  43. */
  44. protected $_file;
  45. /**
  46. * Uploaded file mime type
  47. *
  48. * @var string
  49. * @access protected
  50. */
  51. protected $_fileMimeType;
  52. /**
  53. * Upload type. Used to right handle $_FILES array.
  54. *
  55. * @var Varien_File_Uploader::SINGLE_STYLE|Varien_File_Uploader::MULTIPLE_STYLE
  56. * @access protected
  57. */
  58. protected $_uploadType;
  59. /**
  60. * The name of uploaded file. By default it is original file name, but when
  61. * we will change file name, this variable will be changed too.
  62. *
  63. * @var string
  64. * @access protected
  65. */
  66. protected $_uploadedFileName;
  67. /**
  68. * The name of destination directory
  69. *
  70. * @var string
  71. * @access protected
  72. */
  73. protected $_uploadedFileDir;
  74. /**
  75. * If this variable is set to TRUE, our library will be able to automaticaly create
  76. * non-existant directories.
  77. *
  78. * @var bool
  79. * @access protected
  80. */
  81. protected $_allowCreateFolders = true;
  82. /**
  83. * If this variable is set to TRUE, uploaded file name will be changed if some file with the same
  84. * name already exists in the destination directory (if enabled).
  85. *
  86. * @var bool
  87. * @access protected
  88. */
  89. protected $_allowRenameFiles = false;
  90. /**
  91. * If this variable is set to TRUE, files despersion will be supported.
  92. *
  93. * @var bool
  94. * @access protected
  95. */
  96. protected $_enableFilesDispersion = false;
  97. /**
  98. * This variable is used both with $_enableFilesDispersion == true
  99. * It helps to avoid problems after migrating from case-insensitive file system to case-insensitive
  100. * (e.g. NTFS->ext or ext->NTFS)
  101. *
  102. * @var bool
  103. * @access protected
  104. */
  105. protected $_caseInsensitiveFilenames = true;
  106. /**
  107. * @var string
  108. * @access protected
  109. */
  110. protected $_dispretionPath = null;
  111. protected $_fileExists = false;
  112. protected $_allowedExtensions = null;
  113. /**
  114. * Validate callbacks storage
  115. *
  116. * @var array
  117. * @access protected
  118. */
  119. protected $_validateCallbacks = array();
  120. const SINGLE_STYLE = 0;
  121. const MULTIPLE_STYLE = 1;
  122. const TMP_NAME_EMPTY = 666;
  123. /**
  124. * Resulting of uploaded file
  125. *
  126. * @var array|bool Array with file info keys: path, file. Result is
  127. * FALSE when file not uploaded
  128. */
  129. protected $_result;
  130. function __construct($fileId)
  131. {
  132. $this->_setUploadFileId($fileId);
  133. if( !file_exists($this->_file['tmp_name']) ) {
  134. $code = empty($this->_file['tmp_name']) ? self::TMP_NAME_EMPTY : 0;
  135. throw new Exception('File was not uploaded.', $code);
  136. } else {
  137. $this->_fileExists = true;
  138. }
  139. }
  140. /**
  141. * After save logic
  142. *
  143. * @param array $result
  144. * @return Varien_File_Uploader
  145. */
  146. protected function _afterSave($result)
  147. {
  148. return $this;
  149. }
  150. /**
  151. * Used to save uploaded file into destination folder with
  152. * original or new file name (if specified)
  153. *
  154. * @param string $destinationFolder
  155. * @param string $newFileName
  156. * @access public
  157. * @return void|bool
  158. */
  159. public function save($destinationFolder, $newFileName = null)
  160. {
  161. $this->_validateFile();
  162. if ($this->_allowCreateFolders) {
  163. $this->_createDestinationFolder($destinationFolder);
  164. }
  165. if (!is_writable($destinationFolder)) {
  166. throw new Exception('Destination folder is not writable or does not exists.');
  167. }
  168. $this->_result = false;
  169. $destinationFile = $destinationFolder;
  170. $fileName = isset($newFileName) ? $newFileName : self::getCorrectFileName($this->_file['name']);
  171. if ($this->_enableFilesDispersion) {
  172. $fileName = $this->correctFileNameCase($fileName);
  173. $this->setAllowCreateFolders(true);
  174. $this->_dispretionPath = self::getDispretionPath($fileName);
  175. $destinationFile.= $this->_dispretionPath;
  176. $this->_createDestinationFolder($destinationFile);
  177. }
  178. if ($this->_allowRenameFiles) {
  179. $fileName = self::getNewFileName(self::_addDirSeparator($destinationFile) . $fileName);
  180. }
  181. $destinationFile = self::_addDirSeparator($destinationFile) . $fileName;
  182. $this->_result = move_uploaded_file($this->_file['tmp_name'], $destinationFile);
  183. if ($this->_result) {
  184. chmod($destinationFile, 0777);
  185. if ($this->_enableFilesDispersion) {
  186. $fileName = str_replace(DIRECTORY_SEPARATOR, '/',
  187. self::_addDirSeparator($this->_dispretionPath)) . $fileName;
  188. }
  189. $this->_uploadedFileName = $fileName;
  190. $this->_uploadedFileDir = $destinationFolder;
  191. $this->_result = $this->_file;
  192. $this->_result['path'] = $destinationFolder;
  193. $this->_result['file'] = $fileName;
  194. $this->_afterSave($this->_result);
  195. }
  196. return $this->_result;
  197. }
  198. /**
  199. * Validate file before save
  200. *
  201. * @access public
  202. */
  203. protected function _validateFile()
  204. {
  205. if( $this->_fileExists === false ) {
  206. return;
  207. }
  208. $filePath = $this->_file['tmp_name'];
  209. $fileName = $this->_file['name'];
  210. //is file extension allowed
  211. $fileExtension = substr($fileName, strrpos($fileName, '.')+1);
  212. if (!$this->checkAllowedExtension($fileExtension)) {
  213. throw new Exception('Disallowed file type.');
  214. }
  215. //run validate callbacks
  216. foreach ($this->_validateCallbacks as $params) {
  217. if (is_object($params['object']) && method_exists($params['object'], $params['method'])) {
  218. $params['object']->$params['method']($filePath);
  219. }
  220. }
  221. }
  222. /**
  223. * Add validation callback model for us in self::_validateFile()
  224. *
  225. * @param string $callbackName
  226. * @param object $callbackObject
  227. * @param string $callbackMethod Method name of $callbackObject. It must
  228. * have interface (string $tmpFilePath)
  229. * @return Varien_File_Uploader
  230. */
  231. public function addValidateCallback($callbackName, $callbackObject, $callbackMethod)
  232. {
  233. $this->_validateCallbacks[$callbackName] = array(
  234. 'object' => $callbackObject,
  235. 'method' => $callbackMethod
  236. );
  237. return $this;
  238. }
  239. /**
  240. * Delete validation callback model for us in self::_validateFile()
  241. *
  242. * @param string $callbackName
  243. * @access public
  244. * @return Varien_File_Uploader
  245. */
  246. public function removeValidateCallback($callbackName)
  247. {
  248. if (isset($this->_validateCallbacks[$callbackName])) {
  249. unset($this->_validateCallbacks[$callbackName]);
  250. }
  251. return $this;
  252. }
  253. /**
  254. * Correct filename with special chars and spaces
  255. *
  256. * @param string $fileName
  257. * @return string
  258. */
  259. static public function getCorrectFileName($fileName)
  260. {
  261. $fileName = preg_replace('/[^a-z0-9_\\-\\.]+/i', '_', $fileName);
  262. $fileInfo = pathinfo($fileName);
  263. if (preg_match('/^_+$/', $fileInfo['filename'])) {
  264. $fileName = 'file.' . $fileInfo['extension'];
  265. }
  266. return $fileName;
  267. }
  268. /**
  269. * Convert filename to lowercase in case of case-insensitive file names
  270. *
  271. * @param string
  272. * @return string
  273. */
  274. public function correctFileNameCase($fileName)
  275. {
  276. if ($this->_caseInsensitiveFilenames) {
  277. return strtolower($fileName);
  278. }
  279. return $fileName;
  280. }
  281. static protected function _addDirSeparator($dir)
  282. {
  283. if (substr($dir,-1) != DIRECTORY_SEPARATOR) {
  284. $dir.= DIRECTORY_SEPARATOR;
  285. }
  286. return $dir;
  287. }
  288. /**
  289. * Used to check if uploaded file mime type is valid or not
  290. *
  291. * @param array $validTypes
  292. * @access public
  293. * @return bool
  294. */
  295. public function checkMimeType($validTypes=Array())
  296. {
  297. if (count($validTypes) > 0) {
  298. if (!in_array($this->_getMimeType(), $validTypes)) {
  299. return false;
  300. }
  301. }
  302. return true;
  303. }
  304. /**
  305. * Returns a name of uploaded file
  306. *
  307. * @access public
  308. * @return string
  309. */
  310. public function getUploadedFileName()
  311. {
  312. return $this->_uploadedFileName;
  313. }
  314. /**
  315. * Used to set {@link _allowCreateFolders} value
  316. *
  317. * @param mixed $flag
  318. * @access public
  319. * @return Varien_File_Uploader
  320. */
  321. public function setAllowCreateFolders($flag)
  322. {
  323. $this->_allowCreateFolders = $flag;
  324. return $this;
  325. }
  326. /**
  327. * Used to set {@link _allowRenameFiles} value
  328. *
  329. * @param mixed $flag
  330. * @access public
  331. * @return Varien_File_Uploader
  332. */
  333. public function setAllowRenameFiles($flag)
  334. {
  335. $this->_allowRenameFiles = $flag;
  336. return $this;
  337. }
  338. /**
  339. * Used to set {@link _enableFilesDispersion} value
  340. *
  341. * @param mixed $flag
  342. * @access public
  343. * @return Varien_File_Uploader
  344. */
  345. public function setFilesDispersion($flag)
  346. {
  347. $this->_enableFilesDispersion = $flag;
  348. return $this;
  349. }
  350. /**
  351. * Filenames Case-sensitivity setter
  352. *
  353. * @param mixed $flag
  354. * @return Varien_File_Uploader
  355. */
  356. public function setFilenamesCaseSensitivity($flag)
  357. {
  358. $this->_caseInsensitiveFilenames = $flag;
  359. return $this;
  360. }
  361. public function setAllowedExtensions($extensions = array())
  362. {
  363. foreach ((array)$extensions as $extension) {
  364. $this->_allowedExtensions[] = strtolower($extension);
  365. }
  366. return $this;
  367. }
  368. /**
  369. * Check if specified extension is allowed
  370. *
  371. * @param string $extension
  372. * @return boolean
  373. */
  374. public function checkAllowedExtension($extension)
  375. {
  376. if (!is_array($this->_allowedExtensions) || empty($this->_allowedExtensions)) {
  377. return true;
  378. }
  379. return in_array(strtolower($extension), $this->_allowedExtensions);
  380. }
  381. /**
  382. * @deprecated after 1.5.0.0-beta2
  383. *
  384. * @param string $extension
  385. * @return boolean
  386. */
  387. public function chechAllowedExtension($extension)
  388. {
  389. return $this->checkAllowedExtension($extension);
  390. }
  391. private function _getMimeType()
  392. {
  393. return $this->_file['type'];
  394. }
  395. private function _setUploadFileId($fileId)
  396. {
  397. if (empty($_FILES)) {
  398. throw new Exception('$_FILES array is empty');
  399. }
  400. if (is_array($fileId)) {
  401. $this->_uploadType = self::MULTIPLE_STYLE;
  402. $this->_file = $fileId;
  403. } else {
  404. preg_match("/^(.*?)\[(.*?)\]$/", $fileId, $file);
  405. if (count($file) > 0 && (count($file[0]) > 0) && (count($file[1]) > 0)) {
  406. array_shift($file);
  407. $this->_uploadType = self::MULTIPLE_STYLE;
  408. $fileAttributes = $_FILES[$file[0]];
  409. $tmp_var = array();
  410. foreach ($fileAttributes as $attributeName => $attributeValue) {
  411. $tmp_var[$attributeName] = $attributeValue[$file[1]];
  412. }
  413. $fileAttributes = $tmp_var;
  414. $this->_file = $fileAttributes;
  415. } elseif( count($fileId) > 0 && isset($_FILES[$fileId])) {
  416. $this->_uploadType = self::SINGLE_STYLE;
  417. $this->_file = $_FILES[$fileId];
  418. } elseif( $fileId == '' ) {
  419. throw new Exception('Invalid parameter given. A valid $_FILES[] identifier is expected.');
  420. }
  421. }
  422. }
  423. private function _createDestinationFolder($destinationFolder)
  424. {
  425. if (!$destinationFolder) {
  426. return $this;
  427. }
  428. if (substr($destinationFolder, -1) == DIRECTORY_SEPARATOR) {
  429. $destinationFolder = substr($destinationFolder, 0, -1);
  430. }
  431. if (!(@is_dir($destinationFolder) || @mkdir($destinationFolder, 0777, true))) {
  432. throw new Exception("Unable to create directory '{$destinationFolder}'.");
  433. }
  434. return $this;
  435. $destinationFolder = str_replace('/', DIRECTORY_SEPARATOR, $destinationFolder);
  436. $path = explode(DIRECTORY_SEPARATOR, $destinationFolder);
  437. $newPath = null;
  438. $oldPath = null;
  439. foreach ($path as $key => $directory) {
  440. if (trim($directory) == '') {
  441. continue;
  442. }
  443. if (strlen($directory) === 2 && $directory{1} === ':') {
  444. $newPath = $directory;
  445. continue;
  446. }
  447. $newPath .= ($newPath != DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR . $directory : $directory;
  448. if(is_dir($newPath)) {
  449. $oldPath = $newPath;
  450. continue;
  451. } else {
  452. if(is_writable($oldPath)) {
  453. mkdir($newPath, 0777);
  454. } else {
  455. throw new Exception("Unable to create directory '{$newPath}'. Access forbidden.");
  456. }
  457. }
  458. $oldPath = $newPath;
  459. }
  460. return $this;
  461. }
  462. static public function getNewFileName($destFile)
  463. {
  464. $fileInfo = pathinfo($destFile);
  465. if (file_exists($destFile)) {
  466. $index = 1;
  467. $baseName = $fileInfo['filename'] . '.' . $fileInfo['extension'];
  468. while( file_exists($fileInfo['dirname'] . DIRECTORY_SEPARATOR . $baseName) ) {
  469. $baseName = $fileInfo['filename']. '_' . $index . '.' . $fileInfo['extension'];
  470. $index ++;
  471. }
  472. $destFileName = $baseName;
  473. } else {
  474. return $fileInfo['basename'];
  475. }
  476. return $destFileName;
  477. }
  478. static public function getDispretionPath($fileName)
  479. {
  480. $char = 0;
  481. $dispretionPath = '';
  482. while (($char < 2) && ($char < strlen($fileName))) {
  483. if (empty($dispretionPath)) {
  484. $dispretionPath = DIRECTORY_SEPARATOR
  485. . ('.' == $fileName[$char] ? '_' : $fileName[$char]);
  486. } else {
  487. $dispretionPath = self::_addDirSeparator($dispretionPath)
  488. . ('.' == $fileName[$char] ? '_' : $fileName[$char]);
  489. }
  490. $char ++;
  491. }
  492. return $dispretionPath;
  493. }
  494. }