Skip to content

Commit

Permalink
FileElement: Register the FileValidator by default
Browse files Browse the repository at this point in the history
  • Loading branch information
nilmerg committed Jan 24, 2023
1 parent 4423b2d commit f92343a
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/FormElement/FileElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace ipl\Html\FormElement;

use ipl\Html\Attributes;
use ipl\Validator\FileValidator;
use ipl\Validator\ValidatorChain;
use Psr\Http\Message\UploadedFileInterface;
use ipl\Html\Common\MultipleAttribute;

Expand All @@ -15,6 +17,9 @@ class FileElement extends InputElement
/** @var UploadedFileInterface|UploadedFileInterface[] */
protected $value;

/** @var int The default maximum file size */
protected static $defaultMaxFileSize;

public function __construct($name, $attributes = null)
{
$this->getAttributes()->get('accept')->setSeparator(', ');
Expand Down Expand Up @@ -55,9 +60,103 @@ public function getValue()
return $this->hasValue() ? $this->value : null;
}

protected function addDefaultValidators(ValidatorChain $chain): void
{
$chain->add(new FileValidator([
'maxSize' => $this->getDefaultMaxFileSize(),
'mimeType' => array_filter(
(array) $this->getAttributes()->get('accept')->getValue(),
function ($type) {
// file inputs also allow file extensions in the accept attribute. These
// must not be passed as they don't resemble valid mime type definitions.
return is_string($type) && ltrim($type)[0] !== '.';
}
)
]));
}

protected function registerAttributeCallbacks(Attributes $attributes)
{
parent::registerAttributeCallbacks($attributes);
$this->registerMultipleAttributeCallback($attributes);
}

/**
* Get the system's default maximum file upload size
*
* @return int
*/
public function getDefaultMaxFileSize(): int
{
if (static::$defaultMaxFileSize === null) {
$ini = $this->convertIniToInteger(trim(static::getPostMaxSize()));
$max = $this->convertIniToInteger(trim(static::getUploadMaxFilesize()));
$min = max($ini, $max);
if ($ini > 0) {
$min = min($min, $ini);
}

if ($max > 0) {
$min = min($min, $max);
}

static::$defaultMaxFileSize = $min;
}

return static::$defaultMaxFileSize;
}

/**
* Converts a ini setting to a integer value
*
* @param string $setting
*
* @return int
*/
private function convertIniToInteger(string $setting): int
{
if (! is_numeric($setting)) {
$type = strtoupper(substr($setting, -1));
$setting = (int) substr($setting, 0, -1);

switch ($type) {
case 'K':
$setting *= 1024;
break;

case 'M':
$setting *= 1024 * 1024;
break;

case 'G':
$setting *= 1024 * 1024 * 1024;
break;

default:
break;
}
}

return (int) $setting;
}

/**
* Get the `post_max_size` INI setting
*
* @return string
*/
protected static function getPostMaxSize(): string
{
return ini_get('post_max_size') ?: '8M';
}

/**
* Get the `upload_max_filesize` INI setting
*
* @return string
*/
protected static function getUploadMaxFilesize(): string
{
return ini_get('upload_max_filesize') ?: '2M';
}
}
84 changes: 84 additions & 0 deletions tests/FormElement/FileElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
use GuzzleHttp\Psr7\UploadedFile;
use ipl\Html\Form;
use ipl\Html\FormElement\FileElement;
use ipl\I18n\NoopTranslator;
use ipl\I18n\StaticTranslator;
use ipl\Tests\Html\Lib\FileElementWithAdjustableConfig;
use ipl\Tests\Html\TestCase;

class FileElementTest extends TestCase
{
protected function setUp(): void
{
StaticTranslator::$instance = new NoopTranslator();
}

public function testElementLoading(): void
{
$form = (new Form())
Expand Down Expand Up @@ -61,4 +69,80 @@ public function testValueAttributeIsNotRendered()
$file->setValue('test');
$this->assertHtml('<input name="test_file" type="file">', $file);
}

public function testDefaultMaxFileSizeAsBytesIsParsedCorrectly()
{
$element = new FileElementWithAdjustableConfig('test');
$element->setValue(new UploadedFile('test', 500, 0));

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '500';
$element::$postMaxSize = '1000'; // Just needs to be bigger than 500

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));
}

public function testDefaultMaxFileSizeAsKiloBytesIsParsedCorrectly()
{
$element = new FileElementWithAdjustableConfig('test');
$element->setValue(new UploadedFile('test', 1024, 0));

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '1K';
$element::$postMaxSize = '2K'; // Just needs to be bigger than 1K

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));
}

public function testDefaultMaxFileSizeAsMegaBytesIsParsedCorrectly()
{
$element = new FileElementWithAdjustableConfig('test');
$element->setValue(new UploadedFile('test', 102400, 0));

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '1M';
$element::$postMaxSize = '2M'; // Just needs to be bigger than 1M

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));
}

public function testDefaultMaxFileSizeAsGigaBytesIsParsedCorrectly()
{
$element = new FileElementWithAdjustableConfig('test');
$element->setValue(new UploadedFile('test', 1073741824, 0));

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '1G';
$element::$postMaxSize = '2G'; // Just needs to be bigger than 1G

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));
}

/**
* @depends testDefaultMaxFileSizeAsKiloBytesIsParsedCorrectly
*/
public function testUploadMaxFilesizeOverrulesPostMaxSize()
{
$element = new FileElementWithAdjustableConfig('test');
$element->setValue(new UploadedFile('test', 1024, 0));

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '1K';
$element::$postMaxSize = '2K'; // Just needs to be bigger than 1K

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));

// ...if possible

$element::$defaultMaxFileSize = null;
$element::$uploadMaxFilesize = '2K';
$element::$postMaxSize = '1K';

$this->assertTrue($element->isValid(), implode("\n", $element->getMessages()));

$element->setValue(new UploadedFile('test', 2048, 0));
$element::$defaultMaxFileSize = null;

$this->assertFalse($element->isValid());
}
}
24 changes: 24 additions & 0 deletions tests/Lib/FileElementWithAdjustableConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace ipl\Tests\Html\Lib;

use ipl\Html\FormElement\FileElement;

class FileElementWithAdjustableConfig extends FileElement
{
public static $defaultMaxFileSize;

public static $postMaxSize = '8M';

public static $uploadMaxFilesize = '2M';

protected static function getPostMaxSize(): string
{
return self::$postMaxSize;
}

protected static function getUploadMaxFilesize(): string
{
return self::$uploadMaxFilesize;
}
}

0 comments on commit f92343a

Please sign in to comment.