Skip to content

Commit

Permalink
Feature: add file validation rule (#6859)
Browse files Browse the repository at this point in the history
* feautre: add file validation rule

* refactor: refer to rule id using static id

* feature: add allowedMimeType validation

* refactor: rename to FileRequest and update descriptions

* refactor: add maxUploadSize accessors and leave deprecated ones for ffm

* refactor: change name to UploadedFile

* feature: add extra validation for wp/server defined rules

* chore: cleanup comments

---------

Co-authored-by: Jon Waldstein <[email protected]>
  • Loading branch information
jonwaldstein and Jon Waldstein authored Aug 8, 2023
1 parent 038c680 commit ba2eb5f
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 11 deletions.
104 changes: 93 additions & 11 deletions src/Framework/FieldsAPI/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
namespace Give\Framework\FieldsAPI;

use Give\Framework\ValidationRules\Rules\AllowedTypes;

use function get_allowed_mime_types;
use function wp_max_upload_size;
use Give\Framework\ValidationRules\Rules\File as FileRule;
use Give\Vendors\StellarWP\Validation\Rules\Max;

/**
* A file upload field.
*
* @since 2.12.0
* @unreleased Updated to use the new Validation File Rule
* @since 2.12.0
* @since 2.23.1 Moved default rule values inline since inherited constructor is final.
*/
class File extends Field
Expand All @@ -19,14 +19,15 @@ class File extends Field
use Concerns\HasEmailTag;
use Concerns\HasHelpText;
use Concerns\HasLabel;
use Concerns\AllowMultiple;

const TYPE = 'file';

/**
* Set the maximum file size.
*
* @param int $maxSize
* @deprecated use maxUploadSize() instead
*
* @param int $maxSize
*
* @return $this
*/
Expand All @@ -45,6 +46,8 @@ public function maxSize($maxSize)

/**
* Access the maximum file size.
*
* @deprecated use getMaxUploadSize() instead
*/
public function getMaxSize(): int
{
Expand All @@ -55,14 +58,88 @@ public function getMaxSize(): int
return $this->getRule('max')->getSize();
}

/**
* Set the maximum file upload size.
*
* @unreleased
*/
public function maxUploadSize(int $maxUploadSize): File
{
if ($this->hasRule(FileRule::id())) {
/** @var FileRule $rule */
$rule = $this->getRule(FileRule::id());
$rule->maxSize($maxUploadSize);
}

$this->rules((new FileRule())->maxSize($maxUploadSize));

return $this;
}

/**
* Access the maximum file upload size.
*
* @unreleased
*/
public function getMaxUploadSize(): int
{
if (!$this->hasRule(FileRule::id())) {
return wp_max_upload_size();
}

/** @var FileRule $rule */
$rule = $this->getRule(FileRule::id());

return $rule->getMaxSize();
}

/**
* Set the allowed mime types.
*
* @unreleased
*
* @param string[] $allowedMimeTypes
*/
public function allowedMimeTypes(array $allowedMimeTypes): File
{
if ($this->hasRule(FileRule::id())) {
/** @var FileRule $rule */
$rule = $this->getRule(FileRule::id());

$rule->allowedMimeTypes($allowedMimeTypes);
} else {
$this->rules((new FileRule())->allowedMimeTypes($allowedMimeTypes));
}


return $this;
}

/**
* Access the allowed mime types.
*
* @return string[]
*/
public function getAllowedMimeTypes(): array
{
if (!$this->hasRule(FileRule::id())) {
return get_allowed_mime_types();
}

/** @var FileRule $rule */
$rule = $this->getRule(FileRule::id());

return $rule->getAllowedMimeTypes();
}

/**
* Set the allowed file types.
*
* @param string[] $allowedTypes
* @deprecated use allowedMimeTypes() instead
*
* @return $this
* @param string[] $allowedTypes
*/
public function allowedTypes(array $allowedTypes)
public function allowedTypes(array $allowedTypes): File
{
if ($this->hasRule('allowedTypes')) {
/** @var AllowedTypes $rule */
Expand All @@ -78,14 +155,19 @@ public function allowedTypes(array $allowedTypes)
/**
* Access the allowed file types.
*
* @deprecated use getAllowedMimeTypes() instead
*
* @return string[]
*/
public function getAllowedTypes()
public function getAllowedTypes(): array
{
if (!$this->hasRule('allowedTypes')) {
return get_allowed_mime_types();
}

return $this->getRule('allowedTypes')->getAllowedTypes();
/** @var AllowedTypes $rule */
$rule = $this->getRule('allowedTypes');

return $rule->getAllowedTypes();
}
}
121 changes: 121 additions & 0 deletions src/Framework/Http/Types/UploadedFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Give\Framework\Http\Types;

/**
* The represents the shape of a file from a POST request.
*
* @see https://www.php.net/manual/en/reserved.variables.files.php
*
* @unreleased
*/
class UploadedFile
{
/**
* The original name of the file on the client machine.
*
* @var string
*/
protected $name;
/**
* The mime type of the file, if the browser provided this information. An example would be "image/gif". This mime type is however not checked on the PHP side and therefore don't take its value for granted
*
* @var string
*/
protected $browserMimeType;
/**
* The temporary filename of the file in which the uploaded file was stored on the server.
*
* @var string
*/
protected $temporaryName;
/**
* The error code associated with this file upload.
*
* @see https://www.php.net/manual/en/features.file-upload.errors.php
* @var int
*/
protected $error;
/**
* The size, in bytes, of the uploaded file
*
* @var int
*/
protected $size;

/**
* @unreleased
*/
public static function fromArray(array $fileArray): UploadedFile
{
$file = new self();

$file->name = (string)$fileArray['name'];
$file->browserMimeType = (string)$fileArray['type'];
$file->temporaryName = (string)$fileArray['tmp_name'];
$file->error = (int)$fileArray['error'];
$file->size = (int)$fileArray['size'];

return $file;
}

/**
* @unreleased
*/
public function getName(): string
{
return $this->name;
}

/**
* @unreleased
*/
public function getTemporaryName(): string
{
return $this->temporaryName;
}

/**
* @unreleased
*/
public function getBrowserMimeType(): string
{
return $this->browserMimeType;
}

/**
* @unreleased
*
* @see https://www.php.net/manual/en/function.is-uploaded-file.php
*/
public function isUploadedFile(): bool
{
return is_uploaded_file($this->temporaryName);
}

/**
* @unreleased
*
* @see https://www.php.net/manual/en/function.mime-content-type.php
*/
public function getMimeType(): string
{
return mime_content_type($this->temporaryName);
}

/**
* @unreleased
*/
public function getSize(): int
{
return $this->size;
}

/**
* @unreleased
*/
public function getError(): int
{
return $this->error;
}
}
113 changes: 113 additions & 0 deletions src/Framework/ValidationRules/Rules/File.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);

namespace Give\Framework\ValidationRules\Rules;

use Closure;
use Give\Framework\Http\Types\UploadedFile;
use Give\Vendors\StellarWP\Validation\Contracts\ValidationRule;

/**
* @unreleased
*/
class File implements ValidationRule
{
/**
* The size, in bytes, of the uploaded file
*
* @var int
*/
protected $maxSize;

/**
* @var string[]
*/
protected $allowedMimeTypes;

/**
* @unreleased
*/
public static function id(): string
{
return 'file';
}

/**
* @unreleased
*/
public function maxSize(int $maxSize): ValidationRule
{
$this->maxSize = $maxSize;

return $this;
}

/**
* @unreleased
*/
public function getMaxSize(): int
{
return $this->maxSize ?? wp_max_upload_size();
}

/**
* @unreleased
*/
public function allowedMimeTypes(array $allowedMimeTypes): ValidationRule
{
$this->allowedMimeTypes = $allowedMimeTypes;

return $this;
}

/**
* @unreleased
*
* @return string[]
*/
public function getAllowedMimeTypes(): array
{
return $this->allowedMimeTypes ?? get_allowed_mime_types();
}

/**
* @unreleased
**/
public function __invoke($value, Closure $fail, string $key, array $values)
{
try {
$file = UploadedFile::fromArray($value);

if (!$file->isUploadedFile()) {
$fail(sprintf(__('%s must be a valid file.', 'give'), '{field}'));
}

// check against both the allowed mime types defined by the file rule and the server
if (!in_array($file->getMimeType(), $this->getAllowedMimeTypes(), true) ||
!in_array($file->getMimeType(), get_allowed_mime_types(), true)) {
$fail(sprintf(__('%s must be a valid file type.', 'give'), '{field}'));
}

// check against both the max upload size defined by the file rule and the server
if ($file->getSize() > $this->getMaxSize() || $file->getSize() > wp_max_upload_size()) {
$fail(
sprintf(__('%s must be less than or equal to %d bytes.', 'give'), '{field}', $this->getMaxSize())
);
}

if ($file->getError() !== UPLOAD_ERR_OK) {
$fail(sprintf(__('%s must be a valid file.', 'give'), '{field}'));
}
} catch (\Throwable $e) {
$fail($e->getMessage());
}
}

/**
* @unreleased
*/
public static function fromString(string $options = null): ValidationRule
{
return new self();
}
}

0 comments on commit ba2eb5f

Please sign in to comment.