Source for file fileupload.class.php

Documentation is available at fileupload.class.php

  1. <?php
  2.  
  3. /**
  4. * The FileUpload class can be used to easily manage file uploads with the
  5. * PHP engine. The FileUpload class can be used individually or can be used
  6. * together with the FileUploadController class for more functionality.
  7. *
  8. * @author Gerard Klomp <gerard@theprodukt.com>
  9. * @version 2.0
  10. * @package TheProdukt
  11. * @category Classes
  12. * @copyright TheProdukt Graphic Buro VOF 2006
  13. */
  14. class FileUpload {
  15. /**
  16. * @var array $aAllowedFileTypes All the filetypes (extension, mime-types) are stored here
  17. */
  18. var $aAllowedFileTypes = array();
  19. /**
  20. * @var array $aUploaded All the info about the uploaded files is stored in this array
  21. */
  22. var $aUploaded = array();
  23. /**
  24. * @var array $aErrors Stores the errors (if any) which are encountered
  25. */
  26. var $aErrors = array();
  27. /**
  28. * @var null|string|array$sNewName Holds the new name for an individual file or an array of names for multiple upload
  29. */
  30. var $sNewName = null;
  31. /**
  32. * @var string $sDirectory Stores the path to the directory were all files should be stored
  33. */
  34. var $sDirectory;
  35. /**
  36. * @var integer $iMaxSize Sets the maximum allowed filesize of each file in bytes
  37. */
  38. var $iMaxSize = 0;
  39. /**
  40. * @var string|integer$iBaseNumber Sets the number where the automatic numbering system must start counting
  41. */
  42. var $mStartNumber = 1;
  43. /**
  44. * @var boolean $bMultiple Is set to true when an array of files is being uploaded
  45. */
  46. var $bMultiple = false;
  47. /**
  48. * @var boolean $bLowerCaseExtension If set to true all extension will be casted to lowercase
  49. */
  50. var $bLowerCaseExtension = false;
  51. /**
  52. * @var boolean $bIngoreEmptyUploads If set to true, empty uploads will not be classified as an error
  53. */
  54. var $bIgnoreEmptyUploads = false;
  55. /**
  56. * @var object $oFileObject The oFileObject stores the reference to the file object provided by the contructor
  57. */
  58. var $oFileObject;
  59. /**
  60. * The contructor loads the $_FILES array as an reference into a local
  61. * variable inside the class.
  62. *
  63. * @author Gerard Klomp <gerard@theprodukt.com>
  64. * @since 1.0
  65. * @version 1.0
  66. * @param object $oFileObject A reference to the $_FILES array
  67. * @return void
  68. */
  69. function FileUpload(&$oFileObject) {
  70. if (isset($oFileObject['name'], $oFileObject['type'], $oFileObject['size'])) {
  71. $this->oFileObject =& $oFileObject;
  72. $this->bMultiple = is_array($oFileObject['name']);
  73. }
  74. }
  75. /**
  76. * Valid filetypes can be added to the object by using this function.
  77. * The function's first argument can accept a string together with an
  78. * array of mimetypes as the second argument. Else one can input an
  79. * array with extension => array(mimetypes) as the first argument.
  80. *
  81. * @author Gerard Klomp <gerard@theprodukt.com>
  82. * @since 1.0
  83. * @version 2.0
  84. * @param mixed $mFileType The extension of the file or the complete array
  85. * @param mixed $mMimeTypes The array of mimetypes if the first argument is a string
  86. * @return void
  87. */
  88. function addFileType($mFileType, $mMimeTypes = null) {
  89. $this->aAllowedFileTypes = (is_array($mFileType) && $mMimeTypes === null
  90. ? array_merge($this->aAllowedFileTypes, $mFileType)
  91. : (is_string($mFileType) && is_array($mMimeTypes)
  92. ? array_merge($this->aAllowedFileTypes, array(strtolower($mFileType) => $mMimeTypes))
  93. : (is_string($mFileType) && is_string($mMimeTypes)
  94. ? array_merge($this->aAllowedFileTypes, array(strtolower($mFileType) => array($mMimeTypes)))
  95. : $this->aAllowedFileTypes)));
  96. }
  97. /**
  98. * Adds a new error to the array
  99. *
  100. * @author Gerard Klomp <gerard@theprodukt.com>
  101. * @version 2.0
  102. * @since 1.0
  103. * @param string $sFileName The name of the file
  104. * @param integer $iFileSize The size of the file in bytes
  105. * @param integer $iErrorCode The errorcode
  106. * @return void
  107. */
  108. function addError($sFileName, $iFileSize, $iErrorCode) {
  109. $aErrorCodes = array(
  110. 0 => 'There is no error, the file uploaded with success',
  111. 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
  112. 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
  113. 3 => 'The uploaded file was only partially uploaded',
  114. 4 => 'No file was uploaded',
  115. 6 => 'Missing a temporary folder',
  116. 11 => 'The filetype (extension/mime-type) of the file is not allowed',
  117. 13 => 'The uploaded file is too large',
  118. 14 => 'The file could not be moved',
  119. 15 => 'The file was not uploaded with the PHP engine',
  120. 16 => 'The file has been deleted due to a rollback'
  121. );
  122. if ($iErrorCode <> 4 || ($iErrorCode == 4 && $this->bIgnoreEmptyUploads === false)) {
  123. array_push($this->aErrors, array(
  124. 'name' => $sFileName,
  125. 'size' => $iFileSize,
  126. 'error' => $iErrorCode,
  127. 'message' => (isset($aErrorCodes[$iErrorCode]) ? $aErrorCodes[$iErrorCode] : 'An unknown error occured')
  128. ));
  129. }
  130. }
  131. /**
  132. * Adds a new uploaded file to the array
  133. *
  134. * @author Gerard Klomp <gerard@theprodukt.com>
  135. * @version 2.0
  136. * @since 1.0
  137. * @param string $sFileName The name of the file
  138. * @param integer $iFileSize The size of the file in bytes
  139. * @param string $sNewName The new name the file has been given
  140. * @param string $sMessage An additional message
  141. * @return void
  142. */
  143. function addUploaded($sFileName, $iFileSize, $sNewName = '', $sMessage = '') {
  144. array_push($this->aUploaded, array(
  145. 'name' => $sFileName,
  146. 'newName' => $sNewName,
  147. 'size' => $iFileSize,
  148. 'message' => $sMessage
  149. ));
  150. }
  151. /**
  152. * Sets the maximum size in bytes the uploaded file(s) must comply to
  153. *
  154. * @author Gerard Klomp <gerard@theprodukt.com>
  155. * @version 2.0
  156. * @since 1.0
  157. * @param integer $iMaxSize The maximum size in bytes
  158. * @return void
  159. */
  160. function setMaxSize($iMaxSize) {
  161. $this->iMaxSize = (is_numeric($iMaxSize) && $iMaxSize >= 0 ? (int)$iMaxSize : $this->iMaxSize);
  162. }
  163. /**
  164. * Sets the new name(s) or naming method for the files being uploaded.
  165. *
  166. * If only one file is being uploaded and the first argument is a string
  167. * that name will be used for the uploaded file.
  168. *
  169. * If multiple files are being uploaded you can feed an array of names
  170. * to this function which will be used for the uploaded files. If your
  171. * array with names is shorter than the number of files being uploaded
  172. * the class will use the original name for the files remaining.
  173. *
  174. * You also have the option to enter 'alpha' or 'num' as a string when
  175. * uploading an array of files. The new names of the files will be
  176. * according to the type selected (alpha starts with 'a' and num starts
  177. * with 1). This function can be usefull if you are using the class for
  178. * an image gallery.
  179. *
  180. * @author Gerard Klomp <gerard@theprodukt.com>
  181. * @version 2.0
  182. * @since 1.0
  183. * @param string|array$sNewName New name for an individual file/names for multiple files or the naming method
  184. * @return void
  185. */
  186. function setNewName($sNewName) {
  187. if ($this->bMultiple === true && is_array($sNewName)) {
  188. $this->sNewName = $sNewName;
  189. } else if ($this->bMultiple === true && in_array($sNewName, array('alpha', 'num'))) {
  190. switch($sNewName) {
  191. case 'alpha':
  192. $this->sNewName = SORT_STRING;
  193. break;
  194. case 'num':
  195. $this->sNewName = SORT_NUMERIC;
  196. break;
  197. }
  198. } else if ($this->bMultiple === false && is_string($sNewName)) {
  199. $this->sNewName = $sNewName;
  200. }
  201. }
  202. /**
  203. * If the bLowerCaseExtension is set to true, extensions of file uploads
  204. * will be casted to lowercase. This can be usefull if you do not wish
  205. * to check if the file has the extension .JPG or .jpg for example.
  206. *
  207. * @author Gerard Klomp <gerard@theprodukt.com>
  208. * @version 1.0
  209. * @param boolean $bSet Set to true when extensions should be casted
  210. * @return void
  211. */
  212. function setLowerCaseExtension($bSet = true) {
  213. $this->bLowerCaseExtension = (isset($bSet) && is_bool($bSet) ? $bSet : $this->bLowerCaseExtension);
  214. }
  215. /**
  216. * If the bIgnoreEmptyUploads is set to true, errors which are caused
  217. * with the errorcode 4 (no file uploaded) will not be added to the
  218. * error array. This can be usefull if you use a static ammount of
  219. * fields for multiple file uploads and you're using the rollback()
  220. * function if any errors occured. Because a not-uploaded error doesn't
  221. * always have to be a real error.
  222. *
  223. * @author Gerard Klomp <gerard@theprodukt.com>
  224. * @version 1.0
  225. * @param boolean $bIgnore
  226. * @return void
  227. */
  228. function setIgnoreEmptyUploads($bIgnore = true) {
  229. $this->bIgnoreEmptyUploads = (isset($bIgnore) && is_bool($bIgnore) ? $bIgnore : $this->bIgnoreEmptyUploads);
  230. }
  231. /**
  232. * This function sets the startnumber from where the automatic numbering
  233. * system should start counting.
  234. *
  235. * If you have enabled the automatic numbering and you set the startnumber
  236. * to 8 (example) the first uploaded file will get the number 8. If you
  237. * set the startnumber to 'auto' it will autodetect the highest number a
  238. * file has in the directory and will take that number +1 as a startnumber.
  239. *
  240. * @author Gerard Klomp <gerard@theprodukt.com>
  241. * @version 1.0
  242. * @param mixed $mNumber
  243. * @return void
  244. */
  245. function setStartNumber($mNumber = 'auto') {
  246. $this->mStartNumber = (is_numeric($mNumber) || (is_string($mNumber) && $mNumber == 'auto') ? $mNumber : $this->mStartNumber);
  247. }
  248. /**
  249. * This function returns an array with errors if they occured, if no
  250. * errors were encountered this function will return false. Remember
  251. * that rollback entries will only be entered after the move() has
  252. * been initiated.
  253. *
  254. * @author Gerard Klomp <gerard@theprodukt.com>
  255. * @version 1.0
  256. * @since 1.0
  257. * @return mixed
  258. */
  259. function getErrors() {
  260. return (count($this->aErrors) > 0 ? $this->aErrors : false);
  261. }
  262. /**
  263. * Returns the array with succesfull uploads
  264. *
  265. * @author Gerard Klomp <gerard@theprodukt.com>
  266. * @version 1.0
  267. * @since 1.0
  268. * @return array
  269. */
  270. function getUploaded() {
  271. return $this->aUploaded;
  272. }
  273. /**
  274. * Extracts the extension of the filename provided
  275. *
  276. * @author Gerard Klomp <gerard@theprodukt.com>
  277. * @version 1.0
  278. * @since 2.0
  279. * @param string $sFileName The full name of the file
  280. * @return string The extension
  281. */
  282. function getExtension($sFileName) {
  283. return substr($sFileName, strrpos($sFileName, '.')+1, strlen($sFileName)-1);
  284. }
  285. /**
  286. * Checks if the file extension and mimetype are allowed
  287. *
  288. * @author Gerard Klomp
  289. * @version 1.0
  290. * @since 2.0
  291. * @param string $sFileName The full name of the file
  292. * @param string $sMimeType The MIME-Type of the file
  293. * @return boolean Returns true if extension and mimetype are allowed, else false
  294. */
  295. function isFileTypeAllowed($sFileName, $sMimeType) {
  296. $sExtension = strtolower($this->getExtension($sFileName));
  297. return (isset($this->aAllowedFileTypes[$sExtension]) && in_array($sMimeType, $this->aAllowedFileTypes[$sExtension]));
  298. }
  299. /**
  300. * Generates a directory structure on the server according to the full
  301. * path given in the parameter. existing directories aren't touched.
  302. * New directories will be given default chmod.
  303. *
  304. * @author Gerard Klomp <gerard@theprodukt.com>
  305. * @version 1.0
  306. * @since 2.0
  307. * @param string $sDirPath The full path
  308. * @return void
  309. */
  310. function generateDirectoryStructure($sDirPath, $sChmod = '0777') {
  311. $sDirPath = trim($sDirPath, '/');
  312. $aDir = explode('/', $sDirPath);
  313. $sTempPath = '';
  314. for ($i=0; $i < count($aDir); $i++) {
  315. $sTempPath .= (strlen($sTempPath) == 0 ? $aDir[$i] : '/'.$aDir[$i]);
  316. if (!is_dir($sTempPath)) {
  317. mkdir($sTempPath, $sChmod);
  318. }
  319. }
  320. }
  321. /**
  322. * This function prepares the filename. It checks if the extension
  323. * should be casted to lowercase and returns the correct filename.
  324. *
  325. * @author Gerard Klomp <gerard@theprodukt.com>
  326. * @version 1.0
  327. * @since 2.0
  328. * @param string $sFileName
  329. * @return string
  330. */
  331. function prepareFileName($sFileName) {
  332. return ($this->bLowerCaseExtension === true
  333. ? substr($sFileName, 0, strrpos($sFileName, '.')).'.'.strtolower($this->getExtension($sFileName))
  334. : $sFileName);
  335. }
  336. /**
  337. * This is the main function which checks all the factors of the file(s)
  338. * and moves them.
  339. *
  340. * @author Gerard Klomp <gerard@theprodukt.com>
  341. * @version 2.2
  342. * @since 1.0
  343. * @param string $sDirPath The full path where the files should be stored
  344. * @return void
  345. */
  346. function move($sDirPath) {
  347. $this->sDirectory = rtrim($sDirPath, '/');
  348. if (!is_dir($this->sDirectory)) {
  349. $this->generateDirectoryStructure($this->sDirectory);
  350. }
  351. if (is_string($this->mStartNumber) && $this->mStartNumber == 'auto') {
  352. $this->mStartNumber = ($this->getHighestFileNumber($this->sDirectory) + 1);
  353. }
  354. if ($this->bMultiple === true) {
  355. $iFiles = count($this->oFileObject['name']);
  356. for ($i=0; $i < $iFiles; $i++) {
  357. $this->oFileObject['name'][$i] = $this->prepareFileName($this->oFileObject['name'][$i]);
  358. if ($this->oFileObject['error'][$i] <> 0) {
  359. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], $this->oFileObject['error'][$i]);
  360. } else if ($this->isFileTypeAllowed($this->oFileObject['name'][$i], $this->oFileObject['type'][$i]) === false) {
  361. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 11);
  362. } else if ($this->iMaxSize <> 0 && $this->oFileObject['size'][$i] > $this->iMaxSize) {
  363. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 13);
  364. } else if (is_uploaded_file($this->oFileObject['tmp_name'][$i]) === false) {
  365. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 15);
  366. } else if (is_null($this->sNewName) === true) {
  367. if (move_uploaded_file($this->oFileObject['tmp_name'][$i], $this->sDirectory . '/' . $this->oFileObject['name'][$i]) === false) {
  368. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 14);
  369. } else {
  370. $this->addUploaded($this->oFileObject['name'][$i], $this->oFileObject['size'][$i]);
  371. }
  372. } else if ($this->sNewName == SORT_NUMERIC) {
  373. if (move_uploaded_file($this->oFileObject['tmp_name'][$i], $this->sDirectory . '/' . ($this->mStartNumber + $i) . '.' . $this->getExtension($this->oFileObject['name'][$i])) === false) {
  374. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 14);
  375. } else {
  376. $this->addUploaded($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], (($this->mStartNumber + $i) . '.' . $this->getExtension($this->oFileObject['name'][$i])));
  377. }
  378. } else if ($this->sNewName == SORT_STRING) {
  379. if (move_uploaded_file($this->oFileObject['tmp_name'][$i], $this->sDirectory . '/' . chr(97 + $i) . '.' . $this->getExtension($this->oFileObject['name'][$i])) === false) {
  380. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 14);
  381. } else {
  382. $this->addUploaded($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], (chr(97 + $i) . '.' . $this->getExtension($this->oFileObject['name'][$i])));
  383. }
  384. } else if (is_array($this->sNewName)) {
  385. if (move_uploaded_file($this->oFileObject['tmp_name'][$i], $this->sDirectory . '/' . (isset($this->sNewName[$i]) ? $this->sNewName[$i] . '.' . $this->getExtension($this->oFileObject['name'][$i]) : $this->oFileObject['name'][$i])) === false) {
  386. $this->addError($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], 14);
  387. } else {
  388. $this->addUploaded($this->oFileObject['name'][$i], $this->oFileObject['size'][$i], (isset($this->sNewName[$i]) ? $this->sNewName[$i] . '.' . $this->getExtension($this->oFileObject['name'][$i]) : $this->oFileObject['name'][$i]));
  389. }
  390. }
  391. }
  392. } else {
  393. $this->oFileObject['name'] = $this->prepareFileName($this->oFileObject['name']);
  394. if ($this->oFileObject['error'] <> 0) {
  395. $this->addError($this->oFileObject['name'], $this->oFileObject['size'], $this->oFileObject['error']);
  396. } else if ($this->isFileTypeAllowed($this->oFileObject['name'], $this->oFileObject['type']) === false) {
  397. $this->addError($this->oFileObject['name'], $this->oFileObject['size'], 11);
  398. } else if ($this->iMaxSize <> 0 && $this->oFileObject['size'] > $this->iMaxSize) {
  399. $this->addError($this->oFileObject['name'], $this->oFileObject['size'], 13);
  400. } else if (is_uploaded_file($this->oFileObject['tmp_name']) === false) {
  401. $this->addError($this->oFileObject['name'], $this->oFileObject['size'], 15);
  402. } else if (move_uploaded_file($this->oFileObject['tmp_name'], (is_null($this->sNewName) === true ? $this->sDirectory . '/' . $this->oFileObject['name'] : $this->sDirectory . '/' . $this->sNewName . '.' . $this->getExtension($this->oFileObject['name']))) === false) {
  403. $this->addError($this->oFileObject['name'], $this->oFileObject['size'], 14);
  404. } else {
  405. $this->addUploaded($this->oFileObject['name'], $this->oFileObject['size'], (!is_null($this->sNewName) ? $this->sNewName.'.'.$this->getExtension($this->oFileObject['name']) : ''));
  406. }
  407. }
  408. }
  409. /**
  410. * Rollback deletes any uploaded pictures and destroys the empty directories
  411. * which are left. It does not touch any other directories which are not empty
  412. * and it doesn't delete any directories other than the ones provided when
  413. * issuing the move() command.
  414. *
  415. * @author Gerard Klomp <gerard@theprodukt.com>
  416. * @version 1.2
  417. * @since 2.0
  418. * @return void
  419. */
  420. function rollback() {
  421. $iUploaded = count($this->aUploaded);
  422. for ($i=0; $i < $iUploaded; $i++) {
  423. unlink($this->sDirectory . '/' . ($this->aUploaded[$i]['newName'] != '' ? $this->aUploaded[$i]['newName'] : $this->aUploaded[$i]['name']));
  424. $this->addError($this->aUploaded[$i]['name'], $this->aUploaded[$i]['size'], 16);
  425. }
  426. $aDir = explode('/', $this->sDirectory);
  427. $iDirs = count($aDir);
  428. for ($i = 0; $i < $iDirs; $i++) {
  429. $sCurrentDir = implode('/', $aDir);
  430. if (is_dir($sCurrentDir) && $this->isEmptyDir($sCurrentDir) === true) {
  431. rmdir($sCurrentDir);
  432. }
  433. array_pop($aDir);
  434. }
  435. }
  436. /**
  437. * Checks if a given directory is empty (has no files/dirs in it)
  438. *
  439. * @author Gerard Klomp <gerard@theprodukt.com>
  440. * @version 1.0
  441. * @since 2.0
  442. * @param string $sDirPath
  443. * @return boolean Returns true if directory is empty, else false
  444. */
  445. function isEmptyDir($sDirPath) {
  446. $bEmpty = true;
  447. if (is_dir($sDirPath) && ($rDir = opendir($sDirPath)) !== false) {
  448. while (($sItem = readdir($rDir)) !== false) {
  449. if (in_array($sItem, array('.', '..')) === false) {
  450. $bEmpty = false;
  451. }
  452. }
  453. closedir($rDir);
  454. return $bEmpty;
  455. } else {
  456. return false;
  457. }
  458. }
  459. /**
  460. * Returns the highest filenumber in the given directory. If the directory
  461. * is empty, it will return the value 0.
  462. *
  463. * @author Gerard Klomp <gerard@theprodukt.com>
  464. * @version 1.0
  465. * @since 2.0
  466. * @param string $sDirPath
  467. * @return integer
  468. */
  469. function getHighestFileNumber($sDirPath) {
  470. if ($this->isEmptyDir($sDirPath) === true) {
  471. return 0;
  472. } else {
  473. $aDirListing = $this->readDirectory($sDirPath);
  474. sort($aDirListing, SORT_NUMERIC);
  475. return end($aDirListing);
  476. }
  477. }
  478. /**
  479. * Lists the contents of a given directory.
  480. *
  481. * @author Gerard Klomp <gerard@theprodukt.com>
  482. * @version 1.0
  483. * @since 2.0
  484. * @param string $sDirPath The path to the directory to be listed
  485. * @param boolean $bLoseExtensions Should extensions be stripped from the filenames
  486. * @param array $aSkipList An array of items which should not be listed
  487. * @return array
  488. */
  489. function readDirectory($sDirPath, $bLoseExtensions = true, $aSkipList = array('.', '..')) {
  490. $aListing = array();
  491. if (is_dir($sDirPath) && ($rDir = opendir($sDirPath)) !== false) {
  492. while (($sItem = readdir($rDir)) !== false) {
  493. if (in_array($sItem, $aSkipList) === false) {
  494. $aListing[] = substr($sItem, 0, ($bLoseExtensions === true ? strrpos($sItem, '.') : strlen($sItem)));
  495. }
  496. }
  497. }
  498. return $aListing;
  499. }
  500. }

Documentation generated on Fri, 04 Aug 2006 17:49:23 +0200 by phpDocumentor 1.3.0RC3