diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2c1bae6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +Tests export-ignore +.github export-ignore + +.gitattributes export-ignore +.gitignore export-ignore + +.php-cs-fixer.dist.php export-ignore +phpstan.neon export-ignore +phpunit.xml.dist export-ignore + +shell.nix export-ignore diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..eb468b6 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,144 @@ +name: CI + +on: + - pull_request + +jobs: + check-composer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Validate composer.json + run: nix-shell --pure --run project-validate-composer + + php-linting: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - 8.0 + - 8.1 + - 8.2 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: PHP lint + run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l" + + xml-linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Validate XML + run: nix-shell --pure --run project-validate-xml + + coding-guideline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Check Coding Guideline + run: nix-shell --pure --run project-coding-guideline + + code-quality: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - php-version: '8.0' + typo3-version: '^11.5' + - php-version: '8.1' + typo3-version: '^11.5' + - php-version: '8.2' + typo3-version: '^11.5' + - php-version: '8.1' + typo3-version: '^12.4' + - php-version: '8.2' + typo3-version: '^12.4' + steps: + - uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer req "typo3/cms-core:${{ matrix.typo3-version }}" --prefer-dist --no-progress --no-interaction + + - name: Code Quality (by PHPStan) + run: ./vendor/bin/phpstan analyse + + tests-mysql: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - php-version: '8.0' + typo3-version: '^11.5' + - php-version: '8.1' + typo3-version: '^11.5' + - php-version: '8.2' + typo3-version: '^11.5' + - php-version: '8.1' + typo3-version: '^12.4' + - php-version: '8.2' + typo3-version: '^12.4' + steps: + - uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: none + tools: composer:v2 + + - name: Setup MySQL + uses: mirromutth/mysql-action@v1.1 + with: + mysql version: '8' + mysql database: 'typo3' + mysql root password: 'root' + + - name: Wait for MySQL + run: | + while ! mysqladmin ping --host=127.0.0.1 --password=root --silent; do + sleep 1 + done + + - name: Install dependencies + run: composer req "typo3/cms-core:${{ matrix.typo3-version }}" --prefer-dist --no-progress --no-interaction + + - name: PHPUnit Tests + env: + typo3DatabaseDriver: "pdo_mysql" + typo3DatabaseName: "typo3" + typo3DatabaseHost: "127.0.0.1" + typo3DatabaseUsername: "root" + typo3DatabasePassword: "root" + run: ./vendor/bin/phpunit --testdox diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7e2bc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/composer.lock +/.php-cs-fixer.cache +/.Build/ +/vendor/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..201cb6c --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,63 @@ +ignoreVCSIgnored(true) + ->in(realpath(__DIR__)); + +return (new \PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'braces' => ['allow_single_line_closure' => true], + 'cast_spaces' => ['space' => 'none'], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'dir_constant' => true, + 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']], + 'function_typehint_space' => true, + 'lowercase_cast' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'native_function_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']], + 'php_unit_mock_short_will_return' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'return_type_declaration' => ['space_before' => 'none'], + 'single_quote' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_trait_insert_per_statement' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]) + ->setFinder($finder); diff --git a/Classes/Form/FormElement/FileCollectionElement.php b/Classes/Form/FormElement/FileCollectionElement.php new file mode 100644 index 0000000..cff437a --- /dev/null +++ b/Classes/Form/FormElement/FileCollectionElement.php @@ -0,0 +1,97 @@ + + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\FormFileCollection\Form\FormElement; + +use TYPO3\CMS\Core\Resource\FileCollectionRepository; +use TYPO3\CMS\Core\Resource\FileInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement; + +/** + * Elements are created with constructor arguments and don't have DI available. + */ +final class FileCollectionElement extends AbstractFormElement +{ + public function setProperty(string $key, $value): void + { + if ($key === 'fileCollection' && is_array($value)) { + $this->setProperty('options', $this->getOptions($value)); + return; + } + + parent::setProperty($key, $value); + } + + /** + * @param array $configuration + * + * @return array + */ + public function getOptions(array $configuration): array + { + $uid = (int)($configuration['uid'] ?? 0); + $collection = $this->getRepository()->findByUid($uid); + if ($collection === null) { + return []; + } + + if (method_exists($collection, 'loadContents')) { + $collection->loadContents(); + } + + $options = []; + foreach ($collection->getItems() as $file) { + if (!$file instanceof FileInterface) { + continue; + } + + $options = $this->addOption($configuration, $options, $file); + } + + return $options; + } + + /** + * @param array $configuration + * @param array $options + * + * @return array + */ + private function addOption( + array $configuration, + array $options, + FileInterface $file + ): array { + $value = $file->getProperty($configuration['valueProperty'] ?? 'identifier'); + $label = $file->getProperty($configuration['labelProperty'] ?? 'identifier'); + + $options[$value] = $label; + return $options; + } + + private function getRepository(): FileCollectionRepository + { + return GeneralUtility::makeInstance(FileCollectionRepository::class); + } +} diff --git a/Configuration/Form/Setup.yaml b/Configuration/Form/Setup.yaml new file mode 100644 index 0000000..efc15b5 --- /dev/null +++ b/Configuration/Form/Setup.yaml @@ -0,0 +1,8 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + FileCollection: + implementationClassName: WerkraumMedia\FormFileCollection\Form\FormElement\FileCollectionElement diff --git a/README.md b/README.md deleted file mode 100644 index 18ca8e8..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# form_file_collection \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..1d2c986 --- /dev/null +++ b/README.rst @@ -0,0 +1,62 @@ +==================================== +TYPO3 Extension Form File Collection +==================================== + +Adds new EXT:form element used to render a file collection allowing the visitor to choose files. + +Concept +======= + +The form element will fetch the configured file collection and assign proper options based on the contained files. +That allows using existing templates that allow to select a single or multiple options. + +EXT:form integration +==================== + +The provided Configuration needs to be loaded via TypoScript. +Use a free identifier: + +.. code:: plain + + plugin.tx_form.settings.yamlConfigurations { + 80 = EXT:form_file_collection/Configuration/Form/Setup.yaml + } + +This will register a new form element type ``FileCollection`` that can be used like this: + +.. code:: yaml + + - + type: FileCollection + identifier: file-collection-1 + label: 'File Collection' + properties: + fileCollection: + # UID of the sys_file_collection to use + uid: 1 + # Optional, default is identifier + # Defines the property to use as value for form element. + valueProperty: 'identifier' + # Optional, default is identifier + # Defines the property to use as label for form element. + labelProperty: 'identifier' + +No template is configured by default, choose one of the existing ones or provide your own: + +.. code:: yaml + + TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + FileCollection: + renderingOptions: + # Allows to switch between different rendering like "Checkbox", "MultiCheckbox" or "RadioButton", etc. + templateName: 'MultiCheckbox' + +Example +------- + +A concrete example can be found within ``Tests/Fixtures/form_file_collection_example``. diff --git a/Tests/Fixtures/BasicDatabase.php b/Tests/Fixtures/BasicDatabase.php new file mode 100644 index 0000000..0c60934 --- /dev/null +++ b/Tests/Fixtures/BasicDatabase.php @@ -0,0 +1,66 @@ + [ + [ + 'uid' => 1, + 'pid' => 0, + 'slug' => '/', + 'title' => 'Page Title', + ], + [ + 'uid' => 2, + 'pid' => 1, + 'slug' => '/page-2', + 'title' => 'Page 2 Title', + ], + ], + 'tt_content' => [ + [ + 'uid' => 1, + 'pid' => 1, + 'header' => 'Form', + 'header_layout' => '0', + 'CType' => 'form_formframework', + 'pi_flexform' => ' + + + + + + EXT:form_file_collection_example/Configuration/Forms/Example.form.yaml + + + 0 + + + + + + ', + ], + [ + 'uid' => 2, + 'pid' => 2, + 'header' => 'Form', + 'header_layout' => '0', + 'CType' => 'form_formframework', + 'pi_flexform' => ' + + + + + + EXT:form_file_collection_example/Configuration/Forms/ExampleCustomLabelAndValue.form.yaml + + + 0 + + + + + + ', + ], + ], +]; diff --git a/Tests/Fixtures/Fileadmin/Files/FirstResult.png b/Tests/Fixtures/Fileadmin/Files/FirstResult.png new file mode 100644 index 0000000..dc6266b Binary files /dev/null and b/Tests/Fixtures/Fileadmin/Files/FirstResult.png differ diff --git a/Tests/Fixtures/Fileadmin/Files/SecondResult.png b/Tests/Fixtures/Fileadmin/Files/SecondResult.png new file mode 100644 index 0000000..dc6266b Binary files /dev/null and b/Tests/Fixtures/Fileadmin/Files/SecondResult.png differ diff --git a/Tests/Fixtures/Rendering.typoscript b/Tests/Fixtures/Rendering.typoscript new file mode 100644 index 0000000..58c0fa4 --- /dev/null +++ b/Tests/Fixtures/Rendering.typoscript @@ -0,0 +1,5 @@ + + +page > +page = PAGE +page.10 =< styles.content.get diff --git a/Tests/Fixtures/SingleFileDatabase.php b/Tests/Fixtures/SingleFileDatabase.php new file mode 100644 index 0000000..d1c60f6 --- /dev/null +++ b/Tests/Fixtures/SingleFileDatabase.php @@ -0,0 +1,41 @@ + [ + [ + 'uid' => 1, + 'pid' => 0, + 'missing' => 0, + 'storage' => 1, + 'type' => 2, + 'metadata' => 0, + 'identifier' => '/Files/FirstResult.png', + 'identifier_hash' => '29b827d0daa29658d8a0d952dfd20f559bbe3bcf', + 'folder_hash' => '86d12d536195df2100a5ec04ab80c08f9bed3d31', + 'extension' => 'png', + 'mime_type' => 'image/png', + 'name' => 'FirstResult.png', + 'sha1' => 'b13f2bbf275d592534eab659c1430c2702ce31fc', + 'size' => '42383', + ], + ], + 'sys_file_collection' => [ + [ + 'uid' => 1, + 'pid' => 1, + 'title' => 'Example for Form', + 'files' => 1, + ], + ], + 'sys_file_reference' => [ + [ + 'uid' => 1, + 'pid' => 1, + 'uid_local' => 1, + 'uid_foreign' => 1, + 'tablenames' => 'sys_file_collection', + 'fieldname' => 'files', + 'sorting_foreign' => 2, + ], + ], +]; diff --git a/Tests/Fixtures/Sites/default/config.yaml b/Tests/Fixtures/Sites/default/config.yaml new file mode 100644 index 0000000..a127453 --- /dev/null +++ b/Tests/Fixtures/Sites/default/config.yaml @@ -0,0 +1,32 @@ +base: / +languages: + - + title: English + enabled: true + base: / + typo3Language: default + locale: en_GB.UTF-8 + iso-639-1: en + websiteTitle: '' + navigationTitle: English + hreflang: en-GB + direction: '' + flag: gb + languageId: 0 + fallbackType: strict + fallbacks: '0' + - + title: Deutsch + enabled: true + base: /de + typo3Language: de + locale: de_DE.UTF-8 + iso-639-1: de + navigationTitle: Deutsch + hreflang: de-DE + direction: '' + flag: de + websiteTitle: '' + languageId: 1 +rootPageId: 1 +websiteTitle: 'Example Website' diff --git a/Tests/Fixtures/TwoFilesDatabase.php b/Tests/Fixtures/TwoFilesDatabase.php new file mode 100644 index 0000000..97823b0 --- /dev/null +++ b/Tests/Fixtures/TwoFilesDatabase.php @@ -0,0 +1,66 @@ + [ + [ + 'uid' => 1, + 'pid' => 0, + 'missing' => 0, + 'storage' => 1, + 'type' => 2, + 'metadata' => 0, + 'identifier' => '/Files/FirstResult.png', + 'identifier_hash' => '29b827d0daa29658d8a0d952dfd20f559bbe3bcf', + 'folder_hash' => '86d12d536195df2100a5ec04ab80c08f9bed3d31', + 'extension' => 'png', + 'mime_type' => 'image/png', + 'name' => 'FirstResult.png', + 'sha1' => 'b13f2bbf275d592534eab659c1430c2702ce31fc', + 'size' => '42383', + ], + [ + 'uid' => 2, + 'pid' => 0, + 'missing' => 0, + 'storage' => 1, + 'type' => 2, + 'metadata' => 0, + 'identifier' => '/Files/SecondResult.png', + 'identifier_hash' => '29b827d0daa29658d8a0d952dfd20f559bbe3bcf', + 'folder_hash' => '86d12d536195df2100a5ec04ab80c08f9bed3d31', + 'extension' => 'png', + 'mime_type' => 'image/png', + 'name' => 'SecondResult.png', + 'sha1' => 'b13f2bbf275d592534eab659c1430c2702ce31fc', + 'size' => '42383', + ], + ], + 'sys_file_collection' => [ + [ + 'uid' => 1, + 'pid' => 1, + 'title' => 'Example for Form', + 'files' => 1, + ], + ], + 'sys_file_reference' => [ + [ + 'uid' => 1, + 'pid' => 1, + 'uid_local' => 1, + 'uid_foreign' => 1, + 'tablenames' => 'sys_file_collection', + 'fieldname' => 'files', + 'sorting_foreign' => 2, + ], + [ + 'uid' => 2, + 'pid' => 1, + 'uid_local' => 2, + 'uid_foreign' => 1, + 'tablenames' => 'sys_file_collection', + 'fieldname' => 'files', + 'sorting_foreign' => 1, + ], + ], +]; diff --git a/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Example.form.yaml b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Example.form.yaml new file mode 100644 index 0000000..253ea75 --- /dev/null +++ b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Example.form.yaml @@ -0,0 +1,34 @@ +renderingOptions: + submitButtonLabel: Submit +type: Form +identifier: test +label: Test +prototypeName: standard +renderables: + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Next step' + type: Page + identifier: page-1 + label: First Step + renderables: + - + type: FileCollection + identifier: file-collection-1 + label: 'File Collection' + properties: + fileCollection: + uid: 1 + - + defaultValue: '' + type: Text + identifier: text-1 + label: 'Example text field' + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Next step' + type: SummaryPage + identifier: summarypage-1 + label: 'Summary step' diff --git a/Tests/Fixtures/form_file_collection_example/Configuration/Forms/ExampleCustomLabelAndValue.form.yaml b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/ExampleCustomLabelAndValue.form.yaml new file mode 100644 index 0000000..c0eba25 --- /dev/null +++ b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/ExampleCustomLabelAndValue.form.yaml @@ -0,0 +1,36 @@ +renderingOptions: + submitButtonLabel: Submit +type: Form +identifier: test +label: Test +prototypeName: standard +renderables: + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Next step' + type: Page + identifier: page-1 + label: First Step + renderables: + - + type: FileCollection + identifier: file-collection-1 + label: 'File Collection' + properties: + fileCollection: + uid: 1 + valueProperty: 'identifier_hash' + labelProperty: 'mime_type' + - + defaultValue: '' + type: Text + identifier: text-1 + label: 'Example text field' + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Next step' + type: SummaryPage + identifier: summarypage-1 + label: 'Summary step' diff --git a/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Setup.yaml b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Setup.yaml new file mode 100644 index 0000000..aa74a3a --- /dev/null +++ b/Tests/Fixtures/form_file_collection_example/Configuration/Forms/Setup.yaml @@ -0,0 +1,14 @@ +TYPO3: + CMS: + Form: + persistenceManager: + allowedExtensionPaths: + 10: EXT:form_file_collection_example/Configuration/Forms/ + + prototypes: + standard: + formElementsDefinition: + FileCollection: + renderingOptions: + # Allows to switch between different rendering like "Checkbox", "MultiCheckbox" or "RadioButton", etc. + templateName: 'MultiCheckbox' diff --git a/Tests/Fixtures/form_file_collection_example/Configuration/TypoScript/Form.typoscript b/Tests/Fixtures/form_file_collection_example/Configuration/TypoScript/Form.typoscript new file mode 100644 index 0000000..ee3e4b1 --- /dev/null +++ b/Tests/Fixtures/form_file_collection_example/Configuration/TypoScript/Form.typoscript @@ -0,0 +1,6 @@ + + +plugin.tx_form.settings.yamlConfigurations { + 80 = EXT:form_file_collection/Configuration/Form/Setup.yaml + 90 = EXT:form_file_collection_example/Configuration/Forms/Setup.yaml +} diff --git a/Tests/Fixtures/form_file_collection_example/composer.json b/Tests/Fixtures/form_file_collection_example/composer.json new file mode 100644 index 0000000..8bc426d --- /dev/null +++ b/Tests/Fixtures/form_file_collection_example/composer.json @@ -0,0 +1,16 @@ +{ + "name": "werkraummedia/form-file-collection-example", + "description": "Example usage of form file collection", + "type": "typo3-cms-extension", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "*", + "typo3/cms-form": "*", + "werkraummedia/form-file-collection": "*" + }, + "extra": { + "typo3/cms": { + "extension-key": "form_file_collection_example" + } + } +} diff --git a/Tests/Functional/FormIntegrationTest.php b/Tests/Functional/FormIntegrationTest.php new file mode 100644 index 0000000..56cf63d --- /dev/null +++ b/Tests/Functional/FormIntegrationTest.php @@ -0,0 +1,122 @@ + + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace WerkraumMedia\FormFileCollection\Tests\Functional; + +use Codappix\Typo3PhpDatasets\TestingFramework; +use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class FormIntegrationTest extends FunctionalTestCase +{ + use TestingFramework; + + public function setUp(): void + { + $this->coreExtensionsToLoad = [ + 'fluid_styled_content', + 'form', + ]; + $this->testExtensionsToLoad = [ + 'typo3conf/ext/form_file_collection', + 'typo3conf/ext/form_file_collection/Tests/Fixtures/form_file_collection_example', + ]; + $this->pathsToLinkInTestInstance = [ + 'typo3conf/ext/form_file_collection/Tests/Fixtures/Sites' => 'typo3conf/sites', + 'typo3conf/ext/form_file_collection/Tests/Fixtures/Fileadmin/Files' => 'fileadmin/Files', + ]; + + parent::setUp(); + + $this->importPHPDataSet(__DIR__ . '/../Fixtures/BasicDatabase.php'); + $this->setUpFrontendRootPage(1, [ + 'setup' => [ + 'EXT:form_file_collection/Tests/Fixtures/Rendering.typoscript', + 'EXT:form_file_collection_example/Configuration/TypoScript/Form.typoscript', + ], + ]); + } + + /** + * @test + */ + public function rendersFileFromSelectedCollection(): void + { + $this->importPHPDataSet(__DIR__ . '/../Fixtures/SingleFileDatabase.php'); + + $request = new InternalRequest(); + + $response = $this->executeFrontendRequest($request); + + $content = $response->getBody()->__toString(); + self::assertStringContainsString('name="tx_form_formframework[test-1][file-collection-1]"', $content); + + self::assertStringContainsString('FirstResult.png', $content); + self::assertStringContainsString('value="/Files/FirstResult.png"', $content); + self::assertStringContainsString('/Files/FirstResult.png', $content); + + self::assertStringNotContainsString('SecondResult.png', $content); + } + + /** + * @test + */ + public function providesMultipleFilesFromSelectedCollection(): void + { + $this->importPHPDataSet(__DIR__ . '/../Fixtures/TwoFilesDatabase.php'); + + $request = new InternalRequest(); + + $response = $this->executeFrontendRequest($request); + + $content = $response->getBody()->__toString(); + self::assertStringContainsString('name="tx_form_formframework[test-1][file-collection-1][]"', $content); + + self::assertStringContainsString('FirstResult.png', $content); + self::assertStringContainsString('value="/Files/FirstResult.png"', $content); + self::assertStringContainsString('/Files/FirstResult.png', $content); + + self::assertStringContainsString('SecondResult.png', $content); + self::assertStringContainsString('value="/Files/SecondResult.png"', $content); + self::assertStringContainsString('/Files/SecondResult.png', $content); + } + + /** + * @test + */ + public function rendersConfiguredLabel(): void + { + $this->importPHPDataSet(__DIR__ . '/../Fixtures/SingleFileDatabase.php'); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + + $response = $this->executeFrontendRequest($request); + + $content = $response->getBody()->__toString(); + self::assertStringContainsString('name="tx_form_formframework[test-2][file-collection-1][]"', $content); + + self::assertStringContainsString('value="29b827d0daa29658d8a0d952dfd20f559bbe3bcf"', $content); + self::assertStringContainsString('image/png', $content); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b9440cb --- /dev/null +++ b/composer.json @@ -0,0 +1,68 @@ +{ + "name": "werkraummedia/form-file-collection", + "description": "Add a form element to render file collection to EXT:form of TYPO3", + "type": "typo3-cms-extension", + "license": "GPL-2.0-or-later", + "homepage": "https://github.com/werkraum-media/form_file_collection", + "support": { + "docs": "https://docs.typo3.org/p/werkraummedia/form_file_collection/master/en-us/", + "email": "coding@daniel-siepmann.de", + "issues": "https://github.com/werkraum-media/form_file_collection/issues", + "source": "https://github.com/werkraum-media/form_file_collection" + }, + "authors": [ + { + "name": "Daniel Siepmann", + "email": "coding@daniel-siepmann.de", + "homepage": "https://daniel-siepmann.de/", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "WerkraumMedia\\FormFileCollection\\": "Classes/" + } + }, + "autoload-dev": { + "psr-4": { + "WerkraumMedia\\FormFileCollection\\Tests\\": "Tests/", + "WerkraumMedia\\FormFileCollectionExample\\": "Tests/Fixtures/form_file_collection_example/Classes/" + } + }, + "require": { + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "typo3/cms-backend": "^11.5 || ^12.4", + "typo3/cms-core": "^11.5 || ^12.4", + "typo3/cms-fluid-styled-content": "^11.5 || ^12.4", + "typo3/cms-form": "^11.5 || ^12.4", + "typo3/cms-frontend": "^11.5 || ^12.4" + }, + "require-dev": { + "codappix/typo3-php-datasets": "^1.3", + "friendsofphp/php-cs-fixer": "^3.11", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5", + "saschaegerer/phpstan-typo3": "^1.8", + "typo3/testing-framework": "^7.0" + }, + "config": { + "sort-packages": true, + "lock": false, + "allow-plugins": { + "typo3/cms-composer-installers": true, + "typo3/class-alias-loader": true, + "ocramius/package-versions": true, + "phpstan/extension-installer": true, + "cweagans/composer-patches": true + } + }, + "extra": { + "typo3/cms": { + "app-dir": ".Build", + "extension-key": "form_file_collection", + "web-dir": ".Build/web" + } + } +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0daf72e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + paths: + - Classes + - Tests + reportUnmatchedIgnoredErrors: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..bb9e07e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,35 @@ + + + + + + Tests/Functional/ + + + + + + Classes + + + + + + + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..c2d6126 --- /dev/null +++ b/shell.nix @@ -0,0 +1,91 @@ +{ + pkgs ? import { } +}: + +let + php = pkgs.php82.buildEnv { + extensions = { enabled, all }: enabled ++ (with all; [ + xdebug + ]); + + extraConfig = '' + xdebug.mode = debug + memory_limit = 4G + ''; + }; + inherit(php.packages) composer; + + projectInstall = pkgs.writeShellApplication { + name = "project-install"; + runtimeInputs = [ + php + composer + ]; + text = '' + rm -rf .Build/ vendor/ + composer install --prefer-dist --no-progress --working-dir="$PROJECT_ROOT" + ''; + }; + projectValidateComposer = pkgs.writeShellApplication { + name = "project-validate-composer"; + runtimeInputs = [ + php + composer + ]; + text = '' + composer validate + ''; + }; + projectValidateXml = pkgs.writeShellApplication { + name = "project-validate-xml"; + runtimeInputs = [ + pkgs.libxml2 + pkgs.wget + projectInstall + ]; + text = '' + project-install + xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist + ''; + }; + projectCodingGuideline = pkgs.writeShellApplication { + name = "project-coding-guideline"; + runtimeInputs = [ + php + projectInstall + ]; + text = '' + project-install + ./vendor/bin/php-cs-fixer fix --dry-run --diff + ''; + }; + projectCodingGuidelineFix = pkgs.writeShellApplication { + name = "project-coding-guideline-fix"; + runtimeInputs = [ + php + projectInstall + ]; + text = '' + project-install + ./vendor/bin/php-cs-fixer fix + ''; + }; + +in pkgs.mkShell { + name = "TYPO3 Extension FormFileCollection"; + buildInputs = [ + projectInstall + projectValidateComposer + projectValidateXml + projectCodingGuideline + projectCodingGuidelineFix + php + composer + ]; + + shellHook = '' + export PROJECT_ROOT="$(pwd)" + + export typo3DatabaseDriver=pdo_sqlite + ''; +}