From 6cc002ff2f2f59b7722cf47d437d8f6c6744b337 Mon Sep 17 00:00:00 2001
From: Wilmer Arambula <42547589+terabytesoftw@users.noreply.github.com>
Date: Thu, 16 Nov 2023 05:36:39 -0300
Subject: [PATCH] Initital commit. (#1)
---
.github/workflows/build.yml | 1 +
.github/workflows/mutation.yml | 32 ++
README.md | 70 ++-
composer-require-checker.json | 7 +
composer.json | 55 ++-
config/extension.php | 17 +
phpstan.neon | 2 +-
phpunit.xml.dist | 4 +-
psalm.xml | 20 +
src/Asset/Cdn/FilePondEncodePlugin.php | 21 +
src/Asset/Cdn/FilePondImageCropPlugin.php | 29 ++
.../FilePondImageExifOrientationPlugin.php | 21 +
src/Asset/Cdn/FilePondImagePreviewPlugin.php | 28 ++
.../Cdn/FilePondImageTransformPlugin.php | 29 ++
src/Asset/Cdn/FilePondPdfPreviewPlugin.php | 36 ++
src/Asset/Cdn/FilePondRenamePlugin.php | 29 ++
src/Asset/Cdn/FilePondValidateSizePlugin.php | 21 +
src/Asset/Cdn/FilePondValidateTypePlugin.php | 21 +
src/Asset/Dev/FilePondEncodePlugin.php | 34 ++
src/Asset/Dev/FilePondImageCropPlugin.php | 42 ++
.../FilePondImageExifOrientationPlugin.php | 34 ++
src/Asset/Dev/FilePondImagePreviewPlugin.php | 42 ++
.../Dev/FilePondImageTransformPlugin.php | 42 ++
src/Asset/Dev/FilePondPdfPreviewPlugin.php | 50 +++
src/Asset/Dev/FilePondRenamePlugin.php | 42 ++
src/Asset/Dev/FilePondValidateSizePlugin.php | 34 ++
src/Asset/Dev/FilePondValidateTypePlugin.php | 34 ++
src/Asset/FilePondAsset.php | 53 +++
src/Asset/FilePondCdnAsset.php | 38 ++
src/Asset/FilePondProdAsset.php | 53 +++
src/Asset/Prod/FilePondEncodePlugin.php | 34 ++
src/Asset/Prod/FilePondImageCropPlugin.php | 42 ++
.../FilePondImageExifOrientationPlugin.php | 34 ++
src/Asset/Prod/FilePondImagePreviewPlugin.php | 42 ++
.../Prod/FilePondImageTransformPlugin.php | 42 ++
src/Asset/Prod/FilePondPdfPreviewPlugin.php | 50 +++
src/Asset/Prod/FilePondRenamePlugin.php | 42 ++
src/Asset/Prod/FilePondValidateSizePlugin.php | 34 ++
src/Asset/Prod/FilePondValidateTypePlugin.php | 34 ++
src/Example.php | 13 -
src/File.php | 57 +++
src/FilePond.php | 418 ++++++++++++++++++
src/FileProcessing.php | 93 ++++
tests/ExampleTest.php | 18 -
tests/ExceptionTest.php | 32 ++
tests/FileProcessingTest.php | 189 ++++++++
tests/RenderTest.php | 268 +++++++++++
tests/Support/TestForm.php | 12 +
tests/Support/main.php | 9 +
tests/Support/runtime/.gitkeep | 0
tests/TestCase.php | 68 +++
tests/bootstrap.php | 19 +
52 files changed, 2435 insertions(+), 56 deletions(-)
create mode 100644 .github/workflows/mutation.yml
create mode 100644 composer-require-checker.json
create mode 100644 config/extension.php
create mode 100644 psalm.xml
create mode 100644 src/Asset/Cdn/FilePondEncodePlugin.php
create mode 100644 src/Asset/Cdn/FilePondImageCropPlugin.php
create mode 100644 src/Asset/Cdn/FilePondImageExifOrientationPlugin.php
create mode 100644 src/Asset/Cdn/FilePondImagePreviewPlugin.php
create mode 100644 src/Asset/Cdn/FilePondImageTransformPlugin.php
create mode 100644 src/Asset/Cdn/FilePondPdfPreviewPlugin.php
create mode 100644 src/Asset/Cdn/FilePondRenamePlugin.php
create mode 100644 src/Asset/Cdn/FilePondValidateSizePlugin.php
create mode 100644 src/Asset/Cdn/FilePondValidateTypePlugin.php
create mode 100644 src/Asset/Dev/FilePondEncodePlugin.php
create mode 100644 src/Asset/Dev/FilePondImageCropPlugin.php
create mode 100644 src/Asset/Dev/FilePondImageExifOrientationPlugin.php
create mode 100644 src/Asset/Dev/FilePondImagePreviewPlugin.php
create mode 100644 src/Asset/Dev/FilePondImageTransformPlugin.php
create mode 100644 src/Asset/Dev/FilePondPdfPreviewPlugin.php
create mode 100644 src/Asset/Dev/FilePondRenamePlugin.php
create mode 100644 src/Asset/Dev/FilePondValidateSizePlugin.php
create mode 100644 src/Asset/Dev/FilePondValidateTypePlugin.php
create mode 100644 src/Asset/FilePondAsset.php
create mode 100644 src/Asset/FilePondCdnAsset.php
create mode 100644 src/Asset/FilePondProdAsset.php
create mode 100644 src/Asset/Prod/FilePondEncodePlugin.php
create mode 100644 src/Asset/Prod/FilePondImageCropPlugin.php
create mode 100644 src/Asset/Prod/FilePondImageExifOrientationPlugin.php
create mode 100644 src/Asset/Prod/FilePondImagePreviewPlugin.php
create mode 100644 src/Asset/Prod/FilePondImageTransformPlugin.php
create mode 100644 src/Asset/Prod/FilePondPdfPreviewPlugin.php
create mode 100644 src/Asset/Prod/FilePondRenamePlugin.php
create mode 100644 src/Asset/Prod/FilePondValidateSizePlugin.php
create mode 100644 src/Asset/Prod/FilePondValidateTypePlugin.php
delete mode 100644 src/Example.php
create mode 100644 src/File.php
create mode 100644 src/FilePond.php
create mode 100644 src/FileProcessing.php
delete mode 100644 tests/ExampleTest.php
create mode 100644 tests/ExceptionTest.php
create mode 100644 tests/FileProcessingTest.php
create mode 100644 tests/RenderTest.php
create mode 100644 tests/Support/TestForm.php
create mode 100644 tests/Support/main.php
create mode 100644 tests/Support/runtime/.gitkeep
create mode 100644 tests/TestCase.php
create mode 100644 tests/bootstrap.php
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 719d5a9..d7d0a2d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,6 +25,7 @@ jobs:
phpunit:
uses: yiisoft/actions/.github/workflows/phpunit.yml@master
with:
+ extensions: intl
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
new file mode 100644
index 0000000..a787f09
--- /dev/null
+++ b/.github/workflows/mutation.yml
@@ -0,0 +1,32 @@
+on:
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - 'README.md'
+ - 'CHANGELOG.md'
+ - '.gitignore'
+ - '.gitattributes'
+ - 'psalm.xml'
+
+ push:
+ paths-ignore:
+ - 'docs/**'
+ - 'README.md'
+ - 'CHANGELOG.md'
+ - '.gitignore'
+ - '.gitattributes'
+ - 'psalm.xml'
+
+name: mutation test
+
+jobs:
+ mutation:
+ uses: php-forge/actions/.github/workflows/roave-infection.yml@main
+ secrets:
+ AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
+ STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
+ with:
+ os: >-
+ ['ubuntu-latest']
+ php: >-
+ ['8.1']
diff --git a/README.md b/README.md
index 7fc0a59..8d0d338 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-
+
-
Yii2-Template.
+ FilePond.
@@ -13,32 +13,76 @@
-
-
+
+
-
-
+
+
-
+
+
+
+
-
-
+
+
-## Requirements
+## Installation
+
+The preferred way to install this extension is through [composer](https://getcomposer.org/download/).
+
+Either run
+
+```
+php composer.phar require --dev --prefer-dist yii2-extensions/filepond
+```
-The minimun version of `PHP` required by this package is `PHP 8.1`.
+or add
-For install this package, you need [composer](https://getcomposer.org/).
+```
+"yii2-extensions/filepond": "dev-main"
+```
+
+to the require-dev section of your `composer.json` file.
## Usage
-[Check the documentation docs](/docs/README.md) to learn about usage.
+### View
+
+```php
+=
+ $form
+ ->field($formModel, 'image_file')
+ ->widget(
+ FilePond::class,
+ [
+ 'labelIdle' => Yii::t(
+ 'yii.blog', 'Drag & Drop your files or Browse',
+ ),
+ 'loadFileDefault' => $imageFile,
+ 'imagePreviewHeight' => 170,
+ 'imageCropAspectRatio' => '1:1',
+ ],
+ )
+?>
+```
+
+### Controller or Model
+
+```php
+$imageFile = FileProcessing::saveWithReturningFile(
+ $categoryForm->image_file,
+ Yii::getAlias('@uploads'),
+ "category{$category->id}",
+ false
+);
+```
## Testing
diff --git a/composer-require-checker.json b/composer-require-checker.json
new file mode 100644
index 0000000..0f177e7
--- /dev/null
+++ b/composer-require-checker.json
@@ -0,0 +1,7 @@
+{
+ "symbol-whitelist": [
+ "YII_BEGIN_TIME",
+ "YII_DEBUG",
+ "YII_ENV"
+ ]
+}
diff --git a/composer.json b/composer.json
index cd5155d..8f75cfc 100644
--- a/composer.json
+++ b/composer.json
@@ -1,41 +1,78 @@
{
- "name": "yii2/template",
- "type": "library",
- "description": "_____",
+ "name": "yii2-extensions/filepond",
+ "type": "yii2-extension",
+ "description": "FilePond widget for PHP.",
"keywords": [
- "_____"
+ "yii2",
+ "filepond",
+ "widget"
],
"license": "mit",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.1",
- "yiisoft/yii2": "^2.2"
+ "ext-intl": "*",
+ "npm-asset/filepond": "^4.3",
+ "npm-asset/filepond-plugin-file-encode": "^2.1",
+ "npm-asset/filepond-plugin-file-rename": "^1.1",
+ "npm-asset/filepond-plugin-file-validate-size": "^2.2",
+ "npm-asset/filepond-plugin-file-validate-type": "^1.2",
+ "npm-asset/filepond-plugin-image-crop": "^2.0",
+ "npm-asset/filepond-plugin-image-exif-orientation": "^1.0",
+ "npm-asset/filepond-plugin-image-preview": "^4.6",
+ "npm-asset/filepond-plugin-image-transform": "^3.8",
+ "npm-asset/filepond-plugin-pdf-preview": "^1.0",
+ "oomphinc/composer-installers-extender": "^2.0",
+ "php-forge/awesome-widget": "dev-main",
+ "php-forge/html": "dev-main",
+ "yiisoft/yii2": "*"
},
"require-dev": {
"maglnet/composer-require-checker": "^4.6",
+ "php-forge/support": "dev-main",
"phpunit/phpunit": "^10.2",
+ "roave/infection-static-analysis-plugin": "^1.32",
"yii2-extensions/phpstan": "dev-main"
},
"autoload": {
"psr-4": {
- "yii\\template\\": "src"
+ "Yii\\FilePond\\": "src"
}
},
"autoload-dev": {
"psr-4": {
- "yii\\template\\tests\\": "tests"
+ "Yii\\FilePond\\Tests\\": "tests"
}
},
"extra": {
"branch-alias": {
- "dev-main": "1.0.x-dev"
+ "dev-master": "1.0.x-dev"
+ },
+ "config-plugin": {
+ "yii2-filepond": "extension.php"
+ },
+ "config-plugin-options": {
+ "source-directory": "config"
+ },
+ "installer-types": [
+ "bower-asset",
+ "npm-asset"
+ ],
+ "installer-paths": {
+ "./node_modules/{$name}/": [
+ "type:bower-asset",
+ "type:npm-asset"
+ ]
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
- "yiisoft/yii2-composer": true
+ "yiisoft/yii2-composer": true,
+ "composer/installers": true,
+ "oomphinc/composer-installers-extender": true,
+ "infection/extension-installer": true
}
},
"scripts": {
diff --git a/config/extension.php b/config/extension.php
new file mode 100644
index 0000000..1509abf
--- /dev/null
+++ b/config/extension.php
@@ -0,0 +1,17 @@
+ [
+ 'i18n' => [
+ 'translations' => [
+ 'yii.filepond' => [
+ 'class' => PhpMessageSource::class,
+ ],
+ ],
+ ],
+ ],
+];
diff --git a/phpstan.neon b/phpstan.neon
index 0b239cb..3d3be82 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -8,7 +8,7 @@ parameters:
- YII_ENV_PROD
- YII_ENV_TEST
- level: 2
+ level: 5
paths:
- src
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index f29a28d..cf30a25 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -2,7 +2,7 @@
-
+
tests
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..cefdd03
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Asset/Cdn/FilePondEncodePlugin.php b/src/Asset/Cdn/FilePondEncodePlugin.php
new file mode 100644
index 0000000..aebd548
--- /dev/null
+++ b/src/Asset/Cdn/FilePondEncodePlugin.php
@@ -0,0 +1,21 @@
+ [
+ 'dist/filepond-plugin-file-encode.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Dev/FilePondImageCropPlugin.php b/src/Asset/Dev/FilePondImageCropPlugin.php
new file mode 100644
index 0000000..682f549
--- /dev/null
+++ b/src/Asset/Dev/FilePondImageCropPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-crop.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Dev/FilePondImageExifOrientationPlugin.php b/src/Asset/Dev/FilePondImageExifOrientationPlugin.php
new file mode 100644
index 0000000..139fc75
--- /dev/null
+++ b/src/Asset/Dev/FilePondImageExifOrientationPlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-image-exif-orientation.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Dev/FilePondImagePreviewPlugin.php b/src/Asset/Dev/FilePondImagePreviewPlugin.php
new file mode 100644
index 0000000..fc4bf18
--- /dev/null
+++ b/src/Asset/Dev/FilePondImagePreviewPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-preview.css',
+ 'dist/filepond-plugin-image-preview.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Dev/FilePondImageTransformPlugin.php b/src/Asset/Dev/FilePondImageTransformPlugin.php
new file mode 100644
index 0000000..6883592
--- /dev/null
+++ b/src/Asset/Dev/FilePondImageTransformPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-transform.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Dev/FilePondPdfPreviewPlugin.php b/src/Asset/Dev/FilePondPdfPreviewPlugin.php
new file mode 100644
index 0000000..14e6c63
--- /dev/null
+++ b/src/Asset/Dev/FilePondPdfPreviewPlugin.php
@@ -0,0 +1,50 @@
+ [
+ 'dist/filepond-plugin-pdf-preview.css',
+ 'dist/filepond-plugin-pdf-preview.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Dev/FilePondRenamePlugin.php b/src/Asset/Dev/FilePondRenamePlugin.php
new file mode 100644
index 0000000..21d4bd0
--- /dev/null
+++ b/src/Asset/Dev/FilePondRenamePlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-file-rename.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Dev/FilePondValidateSizePlugin.php b/src/Asset/Dev/FilePondValidateSizePlugin.php
new file mode 100644
index 0000000..2eed593
--- /dev/null
+++ b/src/Asset/Dev/FilePondValidateSizePlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-file-validate-size.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Dev/FilePondValidateTypePlugin.php b/src/Asset/Dev/FilePondValidateTypePlugin.php
new file mode 100644
index 0000000..e12dbb0
--- /dev/null
+++ b/src/Asset/Dev/FilePondValidateTypePlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-file-validate-type.js',
+ ],
+ ];
+}
diff --git a/src/Asset/FilePondAsset.php b/src/Asset/FilePondAsset.php
new file mode 100644
index 0000000..9fafa60
--- /dev/null
+++ b/src/Asset/FilePondAsset.php
@@ -0,0 +1,53 @@
+ [
+ 'dist/filepond.css',
+ 'dist/filepond.js',
+ ],
+ ];
+}
diff --git a/src/Asset/FilePondCdnAsset.php b/src/Asset/FilePondCdnAsset.php
new file mode 100644
index 0000000..e5e7710
--- /dev/null
+++ b/src/Asset/FilePondCdnAsset.php
@@ -0,0 +1,38 @@
+ [
+ 'dist/filepond.min.css',
+ 'dist/filepond.min.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Prod/FilePondEncodePlugin.php b/src/Asset/Prod/FilePondEncodePlugin.php
new file mode 100644
index 0000000..1ba80be
--- /dev/null
+++ b/src/Asset/Prod/FilePondEncodePlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-file-encode.min.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Prod/FilePondImageCropPlugin.php b/src/Asset/Prod/FilePondImageCropPlugin.php
new file mode 100644
index 0000000..83174f5
--- /dev/null
+++ b/src/Asset/Prod/FilePondImageCropPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-crop.min.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Prod/FilePondImageExifOrientationPlugin.php b/src/Asset/Prod/FilePondImageExifOrientationPlugin.php
new file mode 100644
index 0000000..7ec8bae
--- /dev/null
+++ b/src/Asset/Prod/FilePondImageExifOrientationPlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-image-exif-orientation.min.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Prod/FilePondImagePreviewPlugin.php b/src/Asset/Prod/FilePondImagePreviewPlugin.php
new file mode 100644
index 0000000..cd359b7
--- /dev/null
+++ b/src/Asset/Prod/FilePondImagePreviewPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-preview.min.css',
+ 'dist/filepond-plugin-image-preview.min.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Prod/FilePondImageTransformPlugin.php b/src/Asset/Prod/FilePondImageTransformPlugin.php
new file mode 100644
index 0000000..76e009c
--- /dev/null
+++ b/src/Asset/Prod/FilePondImageTransformPlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-image-transform.min.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondAsset::class,
+ ];
+}
diff --git a/src/Asset/Prod/FilePondPdfPreviewPlugin.php b/src/Asset/Prod/FilePondPdfPreviewPlugin.php
new file mode 100644
index 0000000..4493f9f
--- /dev/null
+++ b/src/Asset/Prod/FilePondPdfPreviewPlugin.php
@@ -0,0 +1,50 @@
+ [
+ 'dist/filepond-plugin-pdf-preview.min.css',
+ 'dist/filepond-plugin-pdf-preview.min.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondProdAsset::class,
+ ];
+}
diff --git a/src/Asset/Prod/FilePondRenamePlugin.php b/src/Asset/Prod/FilePondRenamePlugin.php
new file mode 100644
index 0000000..880c239
--- /dev/null
+++ b/src/Asset/Prod/FilePondRenamePlugin.php
@@ -0,0 +1,42 @@
+ [
+ 'dist/filepond-plugin-file-rename.min.js',
+ ],
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public $depends = [
+ FilePondProdAsset::class,
+ ];
+}
diff --git a/src/Asset/Prod/FilePondValidateSizePlugin.php b/src/Asset/Prod/FilePondValidateSizePlugin.php
new file mode 100644
index 0000000..a6348cd
--- /dev/null
+++ b/src/Asset/Prod/FilePondValidateSizePlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-file-validate-size.min.js',
+ ],
+ ];
+}
diff --git a/src/Asset/Prod/FilePondValidateTypePlugin.php b/src/Asset/Prod/FilePondValidateTypePlugin.php
new file mode 100644
index 0000000..60d63a6
--- /dev/null
+++ b/src/Asset/Prod/FilePondValidateTypePlugin.php
@@ -0,0 +1,34 @@
+ [
+ 'dist/filepond-plugin-file-validate-type.min.js',
+ ],
+ ];
+}
diff --git a/src/Example.php b/src/Example.php
deleted file mode 100644
index 067eeb8..0000000
--- a/src/Example.php
+++ /dev/null
@@ -1,13 +0,0 @@
-attributes;
+ $attributes['type'] = 'file';
+
+ if (!array_key_exists('name', $attributes)) {
+ $name = Utils::generateInputName($this->formModel->formName(), $this->attribute);
+ } else {
+ $name = is_string($attributes['name']) ? $attributes['name'] : '';
+ }
+
+ $attributes['id'] = Utils::generateInputId($this->formModel->formName(), $this->attribute);
+ $attributes['name'] = Utils::generateArrayableName($name);
+
+ // input type="file" not supported value attribute.
+ unset($attributes['value']);
+
+ return HtmlBuilder::create('input', '', $attributes);
+ }
+}
diff --git a/src/FilePond.php b/src/FilePond.php
new file mode 100644
index 0000000..1caa72c
--- /dev/null
+++ b/src/FilePond.php
@@ -0,0 +1,418 @@
+ return `my_new_name${file.extension}`;
+ */
+ public string $fileRename = '';
+ /**
+ * @var string The file validate type detect type function.
+ *
+ * ```JS
+ * fileValidateTypeDetectType: (source, type) =>
+ * new Promise((resolve, reject) => {
+ * // Do custom type detection here and return with promise
+ *
+ * resolve(type);
+ * }),
+ * ```
+ *
+ * @link https://pqina.nl/filepond/docs/api/plugins/file-validate-type/#custom-type-detection
+ */
+ public string $fileValidateTypeDetectType = '';
+ /**
+ * @var string The file validate When message shown to indicate the allowed file types.
+ */
+ public string $fileValidateTypeLabelExpectedTypes = '';
+ /**
+ * @var string The aspect ratio of the crop in human-readable format, for example, '1:1' or '16:10'.
+ */
+ public string|null $imageCropAspectRatio = null;
+ /**
+ * @var string The image preview height.
+ *
+ * Fixed image preview height, overrides min and max preview height.
+ */
+ public string|null $imagePreviewHeight = null;
+ /**
+ * @var bool Whether to show the image preview markup.
+ */
+ public bool $imagePreviewMarkupShow = true;
+ /**
+ * @var string The image preview max file size.
+ *
+ * Maximum file size for images to preview immediately, if files are larger and the browser doesn't support
+ * createImageBitmap the preview is queued till FilePond is in rest state.
+ *
+ * By default, no maximum file size is defined, expects a string, like `2MB` or `500KB`.
+ */
+ public string|null $imagePreviewMaxFileSize = null;
+ /**
+ * @var int The image preview max height.
+ *
+ * Maximum height of the image preview in pixels.
+ */
+ public int $imagePreviewMaxHeight = 256;
+ /**
+ * @var int The image preview max instant preview file size.
+ *
+ * Maximum file size for images to preview immediately, if files are larger and the browser doesn't support
+ * createImageBitmap the preview is queued till FilePond is in rest state.
+ */
+ public int $imagePreviewMaxInstantPreviewFileSize = 1000000;
+ /**
+ * @var int The image preview min height.
+ *
+ * Minimum height of the image preview in pixels.
+ */
+ public int $imagePreviewMinHeight = 44;
+ /**
+ * @var string The image preview transparency indicator.
+ *
+ * Show a grid behind the preview, set to a color value (for example '#f00') to set transparent image background
+ * color.
+ *
+ * Please note that this is only for preview purposes.
+ *
+ * The background color or grid isn't embedded in the output image.
+ */
+ public string|null $imagePreviewTransparencyIndicator = null;
+ /**
+ * @var array The image transform after create blob.
+ *
+ * A hook to make changes to the file after the file has been created.
+ */
+ public array|null $imageTransformAfterCreateBlob = null;
+ /**
+ * @var array The image transform before create blob.
+ *
+ * A hook to make changes to the canvas before the file is created.
+ */
+ public array|null $imageTransformBeforeCreateBlob = null;
+ /**
+ * @var int The image transform output quality.
+ *
+ * A number between 0 and 100 indicating image quality (e.g. 92 => 92%).
+ */
+ public int|null $imageTransformOutputQuality = null;
+ /**
+ * @var array The image transform client transforms.
+ *
+ * An array of transforms to apply on the client, useful if we, for instance, want to do resizing on the client but
+ * cropping on the server. Null means apply all transforms ('resize', 'crop').
+ */
+ public array|null $imageTransformClientTransforms = null;
+ /**
+ * @var string The image transform output quality mode.
+ *
+ * Should output quality be enforced, set the 'optional' to only apply when a transform is required due to other
+ * requirements (e.g. resize or crop).
+ */
+ public string $imageTransformOutputQualityMode = 'always';
+ /**
+ * @var bool Whether to strip the image head.
+ */
+ public bool $imageTransformOutputStripImageHead = true;
+ /**
+ * @var array The image transform variants.
+ *
+ * An object that can be used to output many files based on different transform instructions.
+ */
+ public array|null $imageTransformVariants = null;
+ /**
+ * @var bool Whether the image transform variants include original.
+ *
+ * Should the transform plugin output the original file.
+ */
+ public bool $imageTransformVariantsIncludeDefault = true;
+ /**
+ * @var string The image transform variants default name.
+ *
+ * The name to use in front of the file name.
+ */
+ public string|null $imageTransformVariantsDefaultName = null;
+ /**
+ * @var bool Whether the image transform variants include original.
+ *
+ * Should the transform plugin output the original file.
+ */
+ public bool $imageTransformVariantsIncludeOriginal = false;
+ /**
+ * @var string The label to show when there are no files.
+ */
+ public string $labelIdle = 'Drag & Drop your files or Browse ';
+ /**
+ * @var string Label for max files size.
+ */
+ public string $labelMaxFileSize = '';
+ /**
+ * @var string Label for max file size exceeded.
+ */
+ public string $labelMaxFileSizeExceeded = '';
+ /**
+ * @var string Label for max total file size.
+ */
+ public string $labelMaxTotalFileSize = '';
+ /**
+ * @var string Label for max total file size exceeded.
+ */
+ public string $labelMaxTotalFileSizeExceeded = '';
+ /**
+ * @var string Label for a file type not allowed error.
+ */
+ public string $labelFileTypeNotAllowed = '';
+ /**
+ * @var string The default file to load.
+ */
+ public string $loadFileDefault = '';
+ /**
+ * @var int The maximum number of files allowed.
+ */
+ public int $maxFiles = 1;
+ /**
+ * @var string The maximum file size allowed.
+ */
+ public string|null $maxFileSize = null;
+ /**
+ * @var string The maximum total file size allowed.
+ */
+ public string|null $maxTotalFileSize = null;
+ /**
+ * @var string The minimum file size allowed.
+ */
+ public string|null $minFileSize = null;
+ /**
+ * @phpstan-var string[] The default plugins to load.
+ */
+ public array $pluginDefault = [
+ 'FilePondPluginFileEncode',
+ 'FilePondPluginFileValidateSize',
+ 'FilePondPluginFileValidateType',
+ 'FilePondPluginImageExifOrientation',
+ 'FilePondPluginImagePreview',
+ ];
+ /**
+ * @var int The pdf preview height.
+ */
+ public int $pdfPreviewHeight = 320;
+ /**
+ * @var string The pdf component extra params.
+ */
+ public string $pdfComponentExtraParams = 'toolbar=0&view=fit&page=1';
+ /**
+ * @var bool Whether the field is required.
+ */
+ public bool $required = false;
+
+ public function init(): void
+ {
+ $this->config = array_merge(
+ [
+ 'acceptedFileTypes' => $this->acceptedFileTypes,
+ 'allowFileRename' => $this->allowFileRename,
+ 'allowFileTypeValidation' => $this->allowFileTypeValidation,
+ 'allowFileValidateSize' => $this->allowFileValidateSize,
+ 'allowImageCrop' => $this->allowImageCrop,
+ 'allowImageExifOrientation' => $this->allowImageExifOrientation,
+ 'allowImagePreview' => $this->allowImagePreview,
+ 'allowImageTransform' => $this->allowImageTransform,
+ 'allowMultiple' => $this->allowMultiple,
+ 'className' => $this->cssClass,
+ 'fileValidateTypeLabelExpectedTypes' => Yii::t(
+ 'yii.filepond',
+ 'Expects {allButLastType} or {lastType}',
+ ),
+ 'imageCropAspectRatio' => $this->imageCropAspectRatio,
+ 'imagePreviewHeight' => $this->imagePreviewHeight,
+ 'imagePreviewMarkupShow' => $this->imagePreviewMarkupShow,
+ 'imagePreviewMaxFileSize' => $this->imagePreviewMaxFileSize,
+ 'imagePreviewMaxHeight' => $this->imagePreviewMaxHeight,
+ 'imagePreviewMaxInstantPreviewFileSize' => $this->imagePreviewMaxFileSize,
+ 'imagePreviewMinHeight' => $this->imagePreviewMinHeight,
+ 'imagePreviewTransparencyIndicator' => $this->imagePreviewTransparencyIndicator,
+ 'imageTransformAfterCreateBlob' => $this->imageTransformAfterCreateBlob,
+ 'imageTransformBeforeCreateBlob' => $this->imageTransformBeforeCreateBlob,
+ 'imageTransformClientTransforms' => $this->imageTransformClientTransforms,
+ 'imageTransformOutputQuality' => $this->imageTransformOutputQuality,
+ 'imageTransformOutputQualityMode' => $this->imageTransformOutputQuality,
+ 'imageTransformOutputStripImageHead' => $this->imageTransformOutputStripImageHead,
+ 'imageTransformVariants' => $this->imageTransformVariants,
+ 'imageTransformVariantsDefaultName' => $this->imageTransformVariantsDefaultName,
+ 'imageTransformVariantsIncludeOriginal' => $this->imageTransformVariantsIncludeDefault,
+ 'labelFileTypeNotAllowed' => Yii::t('yii.filepond', 'File type not allowed'),
+ 'labelIdle' => $this->labelIdle,
+ 'labelMaxFileSize' => Yii::t('yii.filepond', 'Maximum file size is {filesize}'),
+ 'labelMaxFileSizeExceeded' => Yii::t('yii.filepond', 'File is too large'),
+ 'labelMaxTotalFileSize' => Yii::t('yii.filepond', 'Maximum total file size is {filesize}'),
+ 'labelMaxTotalFileSizeExceeded' => Yii::t('yii.filepond', 'Maximum total size exceeded'),
+ 'maxFiles' => $this->maxFiles,
+ 'maxFileSize' => $this->maxFileSize,
+ 'maxTotalFileSize' => $this->maxTotalFileSize,
+ 'minFileSize' => $this->minFileSize,
+ 'pdfPreviewHeight' => $this->pdfPreviewHeight,
+ 'pdfComponentExtraParams' => $this->pdfComponentExtraParams,
+ 'required' => $this->required,
+ ],
+ $this->config,
+ );
+ }
+
+ public function run(): string
+ {
+ if ($this->model === null) {
+ throw new RuntimeException('The model is not set.');
+ }
+
+ if ($this->attribute === null) {
+ throw new RuntimeException('The attribute is not set.');
+ }
+
+ $this->registerClientScript();
+
+ return $this->renderInputFile();
+ }
+
+ /**
+ * @throws JsonException
+ */
+ private function getScript(): string
+ {
+ $closure = $this->fileRename;
+
+ if ($this->fileValidateTypeDetectType !== '') {
+ $closure = "{$this->fileValidateTypeDetectType} {$closure}";
+ }
+
+ $id = Utils::generateInputId($this->model->formName(), $this->attribute);
+ $loadFileDefault = $this->loadFileDefault;
+ $pluginConfig = implode(', ', $this->pluginDefault);
+ $setOptions = json_encode($this->config, JSON_THROW_ON_ERROR);
+
+ return <<getView();
+
+ if ($this->environment === '') {
+ $this->environment = YII_ENV;
+ }
+
+ match ($this->environment) {
+ 'cdn' => FilePondCdnAsset::register($view),
+ 'prod' => FilePondProdAsset::register($view),
+ default => FilePondAsset::register($view),
+ };
+
+ $view->registerJs($this->getScript());
+ }
+
+ /**
+ * @return string the generated input tag.
+ */
+ private function renderInputFile(): string
+ {
+ $attributes = $this->attributes;
+
+ if (array_key_exists('allowMultiple', $this->config) && $this->config['allowMultiple']) {
+ $attributes['multiple'] = true;
+ }
+
+ if (array_key_exists('className', $this->config) && is_string($this->config['className'])) {
+ CssClass::add($attributes, $this->config['className']);
+ }
+
+ if (array_key_exists('required', $this->config) && $this->config['required']) {
+ $attributes['required'] = true;
+ }
+
+ CssClass::add($attributes, 'filepond');
+
+ return File::widget($this->model, $this->attribute)->attributes($attributes)->render();
+ }
+}
diff --git a/src/FileProcessing.php b/src/FileProcessing.php
new file mode 100644
index 0000000..ddf7bae
--- /dev/null
+++ b/src/FileProcessing.php
@@ -0,0 +1,93 @@
+data) && is_string($file->name)) {
+ $filename = self::sanitizeFilename($file->name, $newFileName);
+
+ $result = self::writeFile($path, base64_decode($file->data), $filename);
+
+ if ($result) {
+ $savedFiles[] = $withPath ? $path . $filename : $filename;
+ }
+ }
+ }
+
+ return $savedFiles;
+ }
+
+ private static function sanitizeFilename(string $filename, $newFileName): string
+ {
+ $info = pathinfo($filename);
+
+ $name = ($newFileName !== null)
+ ? self::sanitizeFilenamePart($newFileName)
+ : self::sanitizeFilenamePart($info['filename']);
+
+ $extension = isset($info['extension']) ? self::sanitizeFilenamePart($info['extension']) : '';
+
+ return ($name !== '' ? $name : '_') . '.' . $extension;
+ }
+
+ private static function sanitizeFilenamePart(string $str): string
+ {
+ return preg_replace("/[^a-zA-Z0-9\s]/", '', $str);
+ }
+
+ private static function writeFile(string $path, string $data, string $filename): bool
+ {
+ $handle = fopen($path . DIRECTORY_SEPARATOR . $filename, 'wb');
+ $result = fwrite($handle, $data);
+
+ fclose($handle);
+
+ return $result !== false;
+ }
+}
diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php
deleted file mode 100644
index 2825a4e..0000000
--- a/tests/ExampleTest.php
+++ /dev/null
@@ -1,18 +0,0 @@
-assertTrue($example->getExample());
- }
-}
diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php
new file mode 100644
index 0000000..ed740db
--- /dev/null
+++ b/tests/ExceptionTest.php
@@ -0,0 +1,32 @@
+expectException(RuntimeException::class);
+ $this->expectExceptionMessage('The attribute is not set.');
+
+ FilePond::widget(['model' => new TestForm()]);
+ }
+
+ public function testNotSetModel(): void
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('The model is not set.');
+
+ FilePond::widget(['attribute' => 'array']);
+ }
+}
diff --git a/tests/FileProcessingTest.php b/tests/FileProcessingTest.php
new file mode 100644
index 0000000..3419f8a
--- /dev/null
+++ b/tests/FileProcessingTest.php
@@ -0,0 +1,189 @@
+ json_encode(
+ [
+ 'id' => 'opqgdavos',
+ 'name' => 'test.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ ],
+ __DIR__ . '/Support/runtime/',
+ );
+
+ $this->assertFileExists(__DIR__ . '/Support/runtime/test.txt');
+ }
+
+ /**
+ * @throws JsonException
+ */
+ public function testSaveWithEmptyData(): void
+ {
+ FileProcessing::save(
+ [
+ 0 => json_encode([], JSON_THROW_ON_ERROR),
+ ],
+ __DIR__ . '/Support/runtime',
+ );
+
+ $this->assertFileDoesNotExist(__DIR__ . '/Support/runtime/test.txt');
+ }
+
+ /**
+ * @throws JsonException
+ */
+ public function testSaveWithReturningFile(): void
+ {
+ $files = FileProcessing::saveWithReturningFile(
+ [
+ 0 => json_encode(
+ [
+ 'id' => 'opqgdavos',
+ 'name' => 'test.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ ],
+ __DIR__ . '/Support/runtime/',
+ 'category',
+ false,
+ );
+
+ $this->assertFileExists(__DIR__ . '/Support/runtime/category.txt');
+ $this->assertSame('category.txt', $files);
+ }
+
+ /**
+ * @throws JsonException
+ */
+ public function testSaveWithReturningFiles(): void
+ {
+ $files = FileProcessing::saveWithReturningFiles(
+ [
+ 0 => json_encode(
+ [
+ 'id' => 'opqgdavos',
+ 'name' => 'test.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ 1 => json_encode(
+ [
+ 'id' => 'opqgdavo2',
+ 'name' => 'test1.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ ],
+ __DIR__ . '/Support/runtime/',
+ );
+
+ $this->assertFileExists(__DIR__ . '/Support/runtime/test.txt');
+ $this->assertFileExists(__DIR__ . '/Support/runtime/test1.txt');
+ $this->assertSame(
+ [
+ __DIR__ . '/Support/runtime/test.txt',
+ __DIR__ . '/Support/runtime/test1.txt',
+ ],
+ $files,
+ );
+ }
+
+ /**
+ * @throws JsonException
+ */
+ public function testSaveWithReturningFilesEmptyData(): void
+ {
+ $files = FileProcessing::saveWithReturningFiles(
+ [
+ 0 => json_encode([], JSON_THROW_ON_ERROR),
+ ],
+ __DIR__ . '/Support/runtime/',
+ );
+
+ $this->assertFileDoesNotExist(__DIR__ . '/Support/runtime/test.txt');
+ $this->assertSame([], $files);
+ }
+
+ public function testSaveWithReturningFilesWithoutPath(): void
+ {
+ $files = FileProcessing::saveWithReturningFiles(
+ [
+ 0 => json_encode(
+ [
+ 'id' => 'opqgdavos',
+ 'name' => 'test.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ 1 => json_encode(
+ [
+ 'id' => 'opqgdavo2',
+ 'name' => 'test1.txt',
+ 'type' => 'text/plain',
+ 'size' => 7,
+ 'metadata' => [],
+ 'data' => 'VGVzdE1lCg==',
+ ],
+ JSON_THROW_ON_ERROR,
+ ),
+ ],
+ __DIR__ . '/Support/runtime/',
+ withPath: false,
+ );
+
+ $this->assertFileExists(__DIR__ . '/Support/runtime/test.txt');
+ $this->assertFileExists(__DIR__ . '/Support/runtime/test1.txt');
+ $this->assertSame(['test.txt', 'test1.txt'], $files);
+ }
+}
diff --git a/tests/RenderTest.php b/tests/RenderTest.php
new file mode 100644
index 0000000..71d53e7
--- /dev/null
+++ b/tests/RenderTest.php
@@ -0,0 +1,268 @@
+mockApplication();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'allowMultiple' => true,
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+ }
+
+ public function testCssClass(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'cssClass' => 'TestClass',
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('"className":"TestClass"', $result);
+ }
+
+ public function testConfig(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'config' => ['forceRevert' => true, 'storeAsFile' => true],
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('"forceRevert":true,"storeAsFile":true', $result);
+ }
+
+ public function testConfigWithClassName(): void
+ {
+ $this->mockApplication();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'cssClass' => 'test-class',
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+ }
+
+ public function testFileRename(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'fileRename' => << new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('functionFileRename()', $result);
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'fileRename' => << <<
+ new Promise((resolve, reject) => {
+ // Do custom type detection here and return with promise
+ resolve(type);
+ }),
+ JS,
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('fileValidateTypeDetectType: (source, type) =>', $result);
+ $this->assertStringContainsString('functionFileRename()', $result);
+ }
+
+ public function testLabelIdle(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'labelIdle' => 'Drag & Drop or Browse ',
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString(
+ '"labelIdle":"Drag & Drop or Browse <\/span>"',
+ $result,
+ );
+ }
+
+ public function testMaxFiles(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'maxFiles' => 3,
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('"maxFiles":3', $result);
+ }
+
+ public function testName(): void
+ {
+ $this->mockApplication();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'attributes' => ['name' => 'test-name'],
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+ }
+
+ public function testPluingDefault(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'maxFiles' => 3,
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertStringContainsString('FilePondPluginFileEncode', $result);
+ $this->assertStringContainsString('FilePondPluginFileValidateSize', $result);
+ $this->assertStringContainsString('FilePondPluginFileValidateType', $result);
+ $this->assertStringContainsString('FilePondPluginImageExifOrientation', $result);
+ $this->assertStringContainsString('FilePondPluginImagePreview', $result);
+ }
+
+ public function testRender(): void
+ {
+ $this->mockApplication();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'model' => new TestForm(),
+ ],
+ );
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+ }
+
+ public function testRequired(): void
+ {
+ $this->mockApplication();
+
+ $view = Yii::$app->getView();
+
+ $filePond = FilePond::widget(
+ [
+ 'attribute' => 'array',
+ 'model' => new TestForm(),
+ 'required' => true,
+ ],
+ );
+
+ $result = $view->renderFile(__DIR__ . '/Support/main.php');
+
+ $this->assertSame(
+ '',
+ $filePond,
+ );
+ $this->assertStringContainsString('"required":true', $result);
+ }
+}
diff --git a/tests/Support/TestForm.php b/tests/Support/TestForm.php
new file mode 100644
index 0000000..d2c738e
--- /dev/null
+++ b/tests/Support/TestForm.php
@@ -0,0 +1,12 @@
+
+beginPage();?>1head();?>2beginBody();?>3endBody();?>4endPage();
diff --git a/tests/Support/runtime/.gitkeep b/tests/Support/runtime/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..35b7b16
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,68 @@
+destroyApplication();
+ }
+
+ protected function mockApplication(): void
+ {
+ new Application(
+ [
+ 'id' => 'testapp',
+ 'aliases' => [
+ '@bower' => dirname(__DIR__) . '/node_modules',
+ '@npm' => dirname(__DIR__) . '/node_modules',
+ ],
+ 'basePath' => __DIR__,
+ 'vendorPath' => dirname(__DIR__) . '/vendor',
+ 'components' => [
+ 'assetManager' => [
+ 'basePath' => __DIR__ . '/Support/runtime',
+ 'baseUrl' => '/',
+ ],
+ 'i18n' => [
+ 'translations' => [
+ 'yii.filepond' => [
+ 'class' => PhpMessageSource::class,
+ ],
+ ],
+ ],
+ 'request' => [
+ 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq',
+ 'scriptFile' => __DIR__ . '/index.php',
+ 'scriptUrl' => '/index.php',
+ ],
+ ],
+ ],
+ );
+ }
+
+ /**
+ * Destroys application in Yii::$app by setting it to null.
+ */
+ protected function destroyApplication()
+ {
+ Yii::$app = null;
+ Yii::$container = new Container();
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..3b39113
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,19 @@
+