PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/library/core/class.upload.php

https://github.com/Emaratilicious/Garden
PHP | 319 lines | 209 code | 48 blank | 62 comment | 40 complexity | 17fbdf658b445fe05f6a7ecba4ca4a2d MD5 | raw file
  1. <?php if (!defined('APPLICATION')) exit();
  2. /*
  3. Copyright 2008, 2009 Vanilla Forums Inc.
  4. This file is part of Garden.
  5. Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6. Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7. You should have received a copy of the GNU General Public License along with Garden. If not, see <http://www.gnu.org/licenses/>.
  8. Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9. */
  10. /**
  11. * Handles uploading files.
  12. */
  13. class Gdn_Upload extends Gdn_Pluggable {
  14. /// PROPERTIES ///
  15. protected $_AllowedFileExtensions;
  16. protected $_MaxFileSize;
  17. protected $_UploadedFile;
  18. /// METHODS ///
  19. /**
  20. * Class constructor
  21. */
  22. public function __construct() {
  23. $this->Clear();
  24. parent::__construct();
  25. $this->ClassName = 'Gdn_Upload';
  26. }
  27. /**
  28. * Adds an extension (or array of extensions) to the array of allowed file
  29. * extensions.
  30. *
  31. * @param mixed The name (or array of names) of the extension to allow.
  32. */
  33. public function AllowFileExtension($Extension) {
  34. if ($Extension === NULL)
  35. $this->_AllowedFileExtensions = array();
  36. elseif (is_array($Extension))
  37. $this->_AllowedFileExtensions = array_merge($this->_AllowedFileExtensions, $Extension);
  38. else
  39. $this->_AllowedFileExtensions[] = $Extension;
  40. }
  41. public static function CanUpload($UploadPath=NULL) {
  42. if (is_null($UploadPath))
  43. $UploadPath = PATH_LOCAL_UPLOADS;
  44. if (ini_get('file_uploads') != 1)
  45. return FALSE;
  46. if (!is_dir($UploadPath))
  47. @mkdir($UploadPath);
  48. if (!is_dir($UploadPath))
  49. return FALSE;
  50. if (!IsWritable($UploadPath) || !is_readable($UploadPath))
  51. return FALSE;
  52. return TRUE;
  53. }
  54. public function Clear() {
  55. $this->_MaxFileSize = self::UnformatFileSize(Gdn::Config('Garden.Upload.MaxFileSize', ''));
  56. $this->_AllowedFileExtensions = Gdn::Config('Garden.Upload.AllowedFileExtensions', array());
  57. }
  58. /**
  59. * Copy an upload locally so that it can be operated on.
  60. *
  61. * @param string $Name
  62. */
  63. public function CopyLocal($Name) {
  64. $Parsed = self::Parse($Name);
  65. $LocalPath = '';
  66. $this->EventArguments['Parsed'] = $Parsed;
  67. $this->EventArguments['Path'] =& $LocalPath;
  68. $this->FireEvent('CopyLocal');
  69. if (!$LocalPath) {
  70. $LocalPath = PATH_LOCAL_UPLOADS.'/'.$Parsed['Name'];
  71. }
  72. return $LocalPath;
  73. }
  74. /**
  75. * Delete an uploaded file.
  76. *
  77. * @param string $Name The name of the upload as saved in the database.
  78. */
  79. public function Delete($Name) {
  80. $Parsed = $this->Parse($Name);
  81. // Throw an event so that plugins that have stored the file somewhere else can delete it.
  82. $this->EventArguments['Parsed'] =& $Parsed;
  83. $Handled = FALSE;
  84. $this->EventArguments['Handled'] =& $Handled;
  85. $this->FireEvent('Delete');
  86. if (!$Handled) {
  87. $Path = PATH_LOCAL_UPLOADS.'/'.ltrim($Name, '/');
  88. @unlink($Path);
  89. }
  90. }
  91. /** Format a number of bytes with the largest unit.
  92. * @param int $Bytes The number of bytes.
  93. * @param int $Precision The number of decimal places in the formatted number.
  94. * @return string the formatted filesize.
  95. */
  96. public static function FormatFileSize($Bytes, $Precision = 1) {
  97. $Units = array('B', 'K', 'M', 'G', 'T');
  98. $Bytes = max((int)$Bytes, 0);
  99. $Pow = floor(($Bytes ? log($Bytes) : 0) / log(1024));
  100. $Pow = min($Pow, count($Units) - 1);
  101. $Bytes /= pow(1024, $Pow);
  102. $Result = round($Bytes, $Precision).$Units[$Pow];
  103. return $Result;
  104. }
  105. public static function Parse($Name) {
  106. $Result = FALSE;
  107. $Name = str_replace('\\', '/', $Name);
  108. if (preg_match('`^https?://`', $Name)) {
  109. $Result = array('Name' => $Name, 'Type' => 'external', 'SaveName' => $Name, 'SaveFormat' => '%s', 'Url' => $Name, );
  110. return $Result;
  111. } elseif (StringBeginsWith($Name, PATH_LOCAL_UPLOADS)) {
  112. $Name = ltrim(substr($Name, strlen(PATH_LOCAL_UPLOADS)), '/');
  113. // This is an upload.
  114. $Result = array('Name' => $Name, 'Type' => '', 'SaveName' => $Name, 'SaveFormat' => '%s');
  115. } elseif (preg_match ('`^~([^/]*)/(.*)$`', $Name, $Matches)) {
  116. // The first part of the name tells us the type.
  117. $Type = $Matches[1];
  118. $Name = $Matches[2];
  119. $Result = array('Name' => $Name, 'Type' => $Type, 'SaveName' => "~$Type/$Name", 'SaveFormat' => "~$Type/%s");
  120. } else {
  121. $Name = ltrim($Name, '/');
  122. // This is an upload in the uploads folder.
  123. $Result = array('Name' => $Name, 'Type' => '', 'SaveName' => $Name, 'SaveFormat' => '%s');
  124. }
  125. $UrlPrefix = self::Urls($Result['Type']);
  126. if ($UrlPrefix === FALSE)
  127. $Result['Url'] = FALSE;
  128. else
  129. $Result['Url'] = $UrlPrefix.'/'.$Result['Name'];
  130. return $Result;
  131. }
  132. /**
  133. * Take a string formatted filesize and return the number of bytes.
  134. * @param string $Formatted The formatted filesize.
  135. * @return int The number of bytes in the string.
  136. */
  137. public static function UnformatFileSize($Formatted) {
  138. $Units = array('B' => 1, 'K' => 1024, 'M' => 1024 * 1024, 'G' => 1024 * 1024 * 1024, 'T' => 1024 * 1024 * 1024 * 1024);
  139. if(preg_match('/([0-9.]+)\s*([A-Z]*)/i', $Formatted, $Matches)) {
  140. $Number = floatval($Matches[1]);
  141. $Unit = strtoupper(substr($Matches[2], 0, 1));
  142. $Mult = GetValue($Unit, $Units, 1);
  143. $Result = round($Number * $Mult, 0);
  144. return $Result;
  145. } else {
  146. return FALSE;
  147. }
  148. }
  149. public function GetUploadedFileName() {
  150. return GetValue('name', $this->_UploadedFile);
  151. }
  152. public function GetUploadedFileExtension() {
  153. $Name = $this->_UploadedFile['name'];
  154. $Info = pathinfo($Name);
  155. return GetValue('extension', $Info, '');
  156. }
  157. public function GenerateTargetName($TargetFolder, $Extension = '') {
  158. if ($Extension == '')
  159. $Extension = $this->GetUploadedFileExtension();
  160. $Name = RandomString(12);
  161. while (file_exists($TargetFolder . DS . $Name . '.' . $Extension)) {
  162. $Name = RandomString(12);
  163. }
  164. return $TargetFolder . DS . $Name . '.' . $Extension;
  165. }
  166. public function SaveAs($Source, $Target) {
  167. $this->EventArguments['Path'] = $Source;
  168. $Parsed = self::Parse($Target);
  169. $this->EventArguments['Parsed'] =& $Parsed;
  170. $Handled = FALSE;
  171. $this->EventArguments['Handled'] =& $Handled;
  172. $this->FireEvent('SaveAs');
  173. // Check to see if the event handled the save.
  174. if (!$Handled) {
  175. $Target = PATH_LOCAL_UPLOADS.'/'.$Parsed['Name'];
  176. if (!file_exists(dirname($Target)))
  177. mkdir(dirname($Target));
  178. if (!move_uploaded_file($Source, $Target))
  179. throw new Exception(sprintf(T('Failed to move uploaded file to target destination (%s).'), $Target));
  180. }
  181. return $Parsed;
  182. }
  183. public static function Url($Name) {
  184. $Parsed = self::Parse($Name);
  185. return $Parsed['Url'];
  186. }
  187. /**
  188. * Returns the url prefix for a given type.
  189. * If there is a plugin that wants to store uploads at a different location or in a different way then they register themselves by subscribing to the Gdn_Upload_GetUrls_Handler event.
  190. * After that they will be available here.
  191. *
  192. * @param string $Type The type of upload to get the prefix for.
  193. * @return string The url prefix.
  194. */
  195. public static function Urls($Type = NULL) {
  196. static $Urls = NULL;
  197. if ($Urls === NULL) {
  198. $Urls = array('' => Asset('/uploads', TRUE));
  199. $Sender = new stdClass();
  200. $Sender->Returns = array();
  201. $Sender->EventArguments = array();
  202. $Sender->EventArguments['Urls'] =& $Urls;
  203. Gdn::PluginManager()->CallEventHandlers($Sender, 'Gdn_Upload', 'GetUrls');
  204. }
  205. if ($Type === NULL)
  206. return $Urls;
  207. if (isset($Urls[$Type]))
  208. return $Urls[$Type];
  209. return FALSE;
  210. }
  211. /**
  212. * Validates the uploaded file. Returns the temporary name of the uploaded file.
  213. */
  214. public function ValidateUpload($InputName, $ThrowException = TRUE) {
  215. $Ex = FALSE;
  216. if (!array_key_exists($InputName, $_FILES) || (!is_uploaded_file($_FILES[$InputName]['tmp_name']) && GetValue('error', $_FILES[$InputName], 0) == 0)) {
  217. // Check the content length to see if we exceeded the max post size.
  218. $ContentLength = Gdn::Request()->GetValueFrom('server', 'CONTENT_LENGTH');
  219. $MaxPostSize = self::UnformatFileSize(ini_get('post_max_size'));
  220. if($ContentLength > $MaxPostSize) {
  221. $Ex = sprintf(T('Gdn_Upload.Error.MaxPostSize', 'The file is larger than the maximum post size. (%s)'), self::FormatFileSize($MaxPostSize));
  222. } else {
  223. $Ex = T('The file failed to upload.');
  224. }
  225. } else {
  226. switch ($_FILES[$InputName]['error']) {
  227. case 1:
  228. case 2:
  229. $MaxFileSize = self::UnformatFileSize(ini_get('upload_max_filesize'));
  230. $Ex = sprintf(T('Gdn_Upload.Error.PhpMaxFileSize', 'The file is larger than the server\'s maximum file size. (%s)'), self::FormatFileSize($MaxFileSize));
  231. break;
  232. case 3:
  233. case 4:
  234. $Ex = T('The file failed to upload.');
  235. break;
  236. case 6:
  237. $Ex = T('The temporary upload folder has not been configured.');
  238. break;
  239. case 7:
  240. $Ex = T('Failed to write the file to disk.');
  241. break;
  242. case 8:
  243. $Ex = T('The upload was stopped by extension.');
  244. break;
  245. }
  246. }
  247. $Foo = self::FormatFileSize($this->_MaxFileSize);
  248. // Check the maxfilesize again just in case the value was spoofed in the form.
  249. if (!$Ex && $this->_MaxFileSize > 0 && filesize($_FILES[$InputName]['tmp_name']) > $this->_MaxFileSize) {
  250. $Ex = sprintf(T('Gdn_Upload.Error.MaxFileSize', 'The file is larger than the maximum file size. (%s)'), self::FormatFileSize($this->_MaxFileSize));
  251. } elseif(!$Ex) {
  252. // Make sure that the file extension is allowed.
  253. $Extension = pathinfo($_FILES[$InputName]['name'], PATHINFO_EXTENSION);
  254. if (!InArrayI($Extension, $this->_AllowedFileExtensions))
  255. $Ex = sprintf(T('You cannot upload files with this extension (%s). Allowed extension(s) are %s.'), $Extension, implode(', ', $this->_AllowedFileExtensions));
  256. }
  257. if($Ex) {
  258. if($ThrowException) {
  259. throw new Gdn_UserException($Ex);
  260. } else {
  261. $this->Exception = $Ex;
  262. return FALSE;
  263. }
  264. } else {
  265. // If all validations were successful, return the tmp name/location of the file.
  266. $this->_UploadedFile = $_FILES[$InputName];
  267. return $this->_UploadedFile['tmp_name'];
  268. }
  269. }
  270. }