File.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. namespace Illuminate\Validation\Rules;
  3. use Illuminate\Contracts\Validation\DataAwareRule;
  4. use Illuminate\Contracts\Validation\Rule;
  5. use Illuminate\Contracts\Validation\ValidatorAwareRule;
  6. use Illuminate\Support\Arr;
  7. use Illuminate\Support\Facades\Validator;
  8. use Illuminate\Support\Str;
  9. use Illuminate\Support\Traits\Conditionable;
  10. use Illuminate\Support\Traits\Macroable;
  11. use InvalidArgumentException;
  12. class File implements Rule, DataAwareRule, ValidatorAwareRule
  13. {
  14. use Conditionable, Macroable;
  15. /**
  16. * The MIME types that the given file should match. This array may also contain file extensions.
  17. *
  18. * @var array
  19. */
  20. protected $allowedMimetypes = [];
  21. /**
  22. * The extensions that the given file should match.
  23. *
  24. * @var array
  25. */
  26. protected $allowedExtensions = [];
  27. /**
  28. * The minimum size in kilobytes that the file can be.
  29. *
  30. * @var null|int
  31. */
  32. protected $minimumFileSize = null;
  33. /**
  34. * The maximum size in kilobytes that the file can be.
  35. *
  36. * @var null|int
  37. */
  38. protected $maximumFileSize = null;
  39. /**
  40. * The required file encoding.
  41. *
  42. * @var string|null
  43. */
  44. protected $encoding = null;
  45. /**
  46. * An array of custom rules that will be merged into the validation rules.
  47. *
  48. * @var array
  49. */
  50. protected $customRules = [];
  51. /**
  52. * The error message after validation, if any.
  53. *
  54. * @var array
  55. */
  56. protected $messages = [];
  57. /**
  58. * The data under validation.
  59. *
  60. * @var array
  61. */
  62. protected $data;
  63. /**
  64. * The validator performing the validation.
  65. *
  66. * @var \Illuminate\Validation\Validator
  67. */
  68. protected $validator;
  69. /**
  70. * The callback that will generate the "default" version of the file rule.
  71. *
  72. * @var string|array|callable|null
  73. */
  74. public static $defaultCallback;
  75. /**
  76. * Set the default callback to be used for determining the file default rules.
  77. *
  78. * If no arguments are passed, the default file rule configuration will be returned.
  79. *
  80. * @param static|callable|null $callback
  81. * @return static|void
  82. *
  83. * @throws \InvalidArgumentException
  84. */
  85. public static function defaults($callback = null)
  86. {
  87. if (is_null($callback)) {
  88. return static::default();
  89. }
  90. if (! is_callable($callback) && ! $callback instanceof static) {
  91. throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
  92. }
  93. static::$defaultCallback = $callback;
  94. }
  95. /**
  96. * Get the default configuration of the file rule.
  97. *
  98. * @return static
  99. */
  100. public static function default()
  101. {
  102. $file = is_callable(static::$defaultCallback)
  103. ? call_user_func(static::$defaultCallback)
  104. : static::$defaultCallback;
  105. return $file instanceof Rule ? $file : new self();
  106. }
  107. /**
  108. * Limit the uploaded file to only image types.
  109. *
  110. * @param bool $allowSvg
  111. * @return ImageFile
  112. */
  113. public static function image($allowSvg = false)
  114. {
  115. return new ImageFile($allowSvg);
  116. }
  117. /**
  118. * Limit the uploaded file to the given MIME types or file extensions.
  119. *
  120. * @param string|array<int, string> $mimetypes
  121. * @return static
  122. */
  123. public static function types($mimetypes)
  124. {
  125. return tap(new static(), fn ($file) => $file->allowedMimetypes = (array) $mimetypes);
  126. }
  127. /**
  128. * Limit the uploaded file to the given file extensions.
  129. *
  130. * @param string|array<int, string> $extensions
  131. * @return $this
  132. */
  133. public function extensions($extensions)
  134. {
  135. $this->allowedExtensions = (array) $extensions;
  136. return $this;
  137. }
  138. /**
  139. * Indicate that the uploaded file should be exactly a certain size in kilobytes.
  140. *
  141. * @param string|int $size
  142. * @return $this
  143. */
  144. public function size($size)
  145. {
  146. $this->minimumFileSize = $this->toKilobytes($size);
  147. $this->maximumFileSize = $this->minimumFileSize;
  148. return $this;
  149. }
  150. /**
  151. * Indicate that the uploaded file should be between a minimum and maximum size in kilobytes.
  152. *
  153. * @param string|int $minSize
  154. * @param string|int $maxSize
  155. * @return $this
  156. */
  157. public function between($minSize, $maxSize)
  158. {
  159. $this->minimumFileSize = $this->toKilobytes($minSize);
  160. $this->maximumFileSize = $this->toKilobytes($maxSize);
  161. return $this;
  162. }
  163. /**
  164. * Indicate that the uploaded file should be no less than the given number of kilobytes.
  165. *
  166. * @param string|int $size
  167. * @return $this
  168. */
  169. public function min($size)
  170. {
  171. $this->minimumFileSize = $this->toKilobytes($size);
  172. return $this;
  173. }
  174. /**
  175. * Indicate that the uploaded file should be no more than the given number of kilobytes.
  176. *
  177. * @param string|int $size
  178. * @return $this
  179. */
  180. public function max($size)
  181. {
  182. $this->maximumFileSize = $this->toKilobytes($size);
  183. return $this;
  184. }
  185. /**
  186. * Indicate that the uploaded file should be in the given encoding.
  187. *
  188. * @param string $encoding
  189. * @return $this
  190. */
  191. public function encoding($encoding)
  192. {
  193. $this->encoding = $encoding;
  194. return $this;
  195. }
  196. /**
  197. * Convert a potentially human-friendly file size to kilobytes.
  198. *
  199. * @param string|int $size
  200. * @return ($size is int ? int : int|float)
  201. *
  202. * @throws \InvalidArgumentException
  203. */
  204. protected function toKilobytes($size)
  205. {
  206. if (! is_string($size)) {
  207. return $size;
  208. }
  209. $size = strtolower(trim($size));
  210. $value = (float) $size;
  211. return round(match (true) {
  212. Str::endsWith($size, 'kb') => $value * 1,
  213. Str::endsWith($size, 'mb') => $value * 1_000,
  214. Str::endsWith($size, 'gb') => $value * 1_000_000,
  215. Str::endsWith($size, 'tb') => $value * 1_000_000_000,
  216. default => throw new InvalidArgumentException('Invalid file size suffix.'),
  217. });
  218. }
  219. /**
  220. * Specify additional validation rules that should be merged with the default rules during validation.
  221. *
  222. * @param string|array $rules
  223. * @return $this
  224. */
  225. public function rules($rules)
  226. {
  227. $this->customRules = array_merge($this->customRules, Arr::wrap($rules));
  228. return $this;
  229. }
  230. /**
  231. * Determine if the validation rule passes.
  232. *
  233. * @param string $attribute
  234. * @param mixed $value
  235. * @return bool
  236. */
  237. public function passes($attribute, $value)
  238. {
  239. $this->messages = [];
  240. $validator = Validator::make(
  241. $this->data,
  242. [$attribute => $this->buildValidationRules()],
  243. $this->validator->customMessages,
  244. $this->validator->customAttributes
  245. );
  246. if ($validator->fails()) {
  247. return $this->fail($validator->messages()->all());
  248. }
  249. return true;
  250. }
  251. /**
  252. * Build the array of underlying validation rules based on the current state.
  253. *
  254. * @return array
  255. */
  256. protected function buildValidationRules()
  257. {
  258. $rules = ['file'];
  259. $rules = array_merge($rules, $this->buildMimetypes());
  260. if (! empty($this->allowedExtensions)) {
  261. $rules[] = 'extensions:'.implode(',', array_map(strtolower(...), $this->allowedExtensions));
  262. }
  263. $rules[] = match (true) {
  264. is_null($this->minimumFileSize) && is_null($this->maximumFileSize) => null,
  265. is_null($this->maximumFileSize) => "min:{$this->minimumFileSize}",
  266. is_null($this->minimumFileSize) => "max:{$this->maximumFileSize}",
  267. $this->minimumFileSize !== $this->maximumFileSize => "between:{$this->minimumFileSize},{$this->maximumFileSize}",
  268. default => "size:{$this->minimumFileSize}",
  269. };
  270. if ($this->encoding) {
  271. $rules[] = 'encoding:'.$this->encoding;
  272. }
  273. return array_merge(array_filter($rules), $this->customRules);
  274. }
  275. /**
  276. * Separate the given MIME types from extensions and return an array of correct rules to validate against.
  277. *
  278. * @return array
  279. */
  280. protected function buildMimetypes()
  281. {
  282. if (count($this->allowedMimetypes) === 0) {
  283. return [];
  284. }
  285. $rules = [];
  286. $mimetypes = array_filter(
  287. $this->allowedMimetypes,
  288. fn ($type) => str_contains($type, '/')
  289. );
  290. $mimes = array_diff($this->allowedMimetypes, $mimetypes);
  291. if (count($mimetypes) > 0) {
  292. $rules[] = 'mimetypes:'.implode(',', $mimetypes);
  293. }
  294. if (count($mimes) > 0) {
  295. $rules[] = 'mimes:'.implode(',', $mimes);
  296. }
  297. return $rules;
  298. }
  299. /**
  300. * Adds the given failures, and return false.
  301. *
  302. * @param array|string $messages
  303. * @return bool
  304. */
  305. protected function fail($messages)
  306. {
  307. $this->messages = array_merge($this->messages, Arr::wrap($messages));
  308. return false;
  309. }
  310. /**
  311. * Get the validation error message.
  312. *
  313. * @return array
  314. */
  315. public function message()
  316. {
  317. return $this->messages;
  318. }
  319. /**
  320. * Set the current validator.
  321. *
  322. * @param \Illuminate\Contracts\Validation\Validator $validator
  323. * @return $this
  324. */
  325. public function setValidator($validator)
  326. {
  327. $this->validator = $validator;
  328. return $this;
  329. }
  330. /**
  331. * Set the current data under validation.
  332. *
  333. * @param array $data
  334. * @return $this
  335. */
  336. public function setData($data)
  337. {
  338. $this->data = $data;
  339. return $this;
  340. }
  341. }