diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..fa6b21a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+Tests export-ignore
+.github export-ignore
+
+.gitattributes export-ignore
+.gitignore export-ignore
+
+phpstan-baseline.neon export-ignore
+phpstan.neon export-ignore
+phpunit.xml.dist export-ignore
+.php-cs-fixer.dist.php export-ignore
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..681aa21
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,155 @@
+name: 'CI'
+
+on:
+ push:
+ branches:
+ - 'main'
+ pull_request:
+
+jobs:
+ check-composer:
+ runs-on: 'ubuntu-latest'
+ steps:
+ - uses: 'actions/checkout@v3'
+
+ - name: 'Install PHP'
+ uses: 'shivammathur/setup-php@v2'
+ with:
+ php-version: '8.4'
+ coverage: 'none'
+ tools: 'composer:v2'
+ env:
+ COMPOSER_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+
+ - name: 'Validate composer.json'
+ run: 'composer validate'
+
+ php-linting:
+ runs-on: 'ubuntu-latest'
+ strategy:
+ matrix:
+ php-version:
+ - '8.2'
+ - '8.3'
+ - '8.4'
+ - '8.5'
+ steps:
+ - name: 'Checkout'
+ uses: 'actions/checkout@v3'
+
+ - name: 'Install PHP'
+ uses: 'shivammathur/setup-php@v2'
+ with:
+ php-version: '${{ matrix.php-version }}'
+ coverage: 'none'
+
+ - name: 'PHP lint'
+ run: |
+ find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l
+
+ coding-guideline:
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'check-composer'
+ steps:
+ - uses: 'actions/checkout@v3'
+
+ - name: 'Install PHP'
+ uses: 'shivammathur/setup-php@v2'
+ with:
+ php-version: '8.4'
+ coverage: 'none'
+ tools: 'composer:v2'
+ env:
+ COMPOSER_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+
+ - name: 'Install dependencies'
+ run: 'composer install --no-progress --no-interaction --optimize-autoloader'
+
+ - name: 'Coding Guideline'
+ run: './vendor/bin/php-cs-fixer fix --dry-run --diff'
+
+ tests:
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'check-composer'
+ strategy:
+ matrix:
+ include:
+ - php-version: '8.2'
+ typo3-version: '^13.4'
+ - php-version: '8.3'
+ typo3-version: '^13.4'
+ - php-version: '8.4'
+ typo3-version: '^13.4'
+ - php-version: '8.5'
+ typo3-version: '^13.4'
+ steps:
+ - uses: 'actions/checkout@v3'
+
+ - name: 'Install NodeJS'
+ uses: 'actions/setup-node@v4'
+
+ - name: 'Install node packages'
+ run: 'npm ci'
+
+ - name: 'Install PHP'
+ uses: 'shivammathur/setup-php@v2'
+ with:
+ php-version: '${{ matrix.php-version }}'
+ coverage: 'xdebug'
+ tools: 'composer:v2'
+ env:
+ COMPOSER_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+
+ - name: 'Install dependencies with expected TYPO3 version'
+ run: 'composer require --no-progress --no-interaction --optimize-autoloader "typo3/cms-core:${{ matrix.typo3-version }}"'
+
+ - name: 'PHPUnit Tests'
+ run: './vendor/bin/phpunit --testdox --display-all-issues --coverage-cobertura coverage/coverage.cobertura.xml'
+
+ - name: 'Code Coverage Report'
+ uses: 'irongut/CodeCoverageSummary@v1.3.0'
+ with:
+ filename: 'coverage/coverage.cobertura.xml'
+ badge: false
+ fail_below_min: true
+ format: 'text'
+ hide_branch_rate: true
+ hide_complexity: true
+ indicators: true
+ output: 'console'
+ thresholds: '40 100'
+
+ phpstan:
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'check-composer'
+ strategy:
+ matrix:
+ include:
+ - php-version: '8.2'
+ typo3-version: '^13.4'
+ - php-version: '8.3'
+ typo3-version: '^13.4'
+ - php-version: '8.4'
+ typo3-version: '^13.4'
+ - php-version: '8.5'
+ typo3-version: '^13.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'
+ env:
+ COMPOSER_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+
+ - name: 'Install dependencies with expected TYPO3 version'
+ run: 'composer require --no-progress --no-interaction --optimize-autoloader "typo3/cms-core:${{ matrix.typo3-version }}"'
+
+ - name: 'PHPStan'
+ run: './vendor/bin/phpstan'
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 0000000..80d6a05
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,25 @@
+setFinder(
+ (new Finder())
+ ->ignoreVCSIgnored(true)
+ ->in(realpath(__DIR__))
+ )
+ ->addRules([
+ 'fully_qualified_strict_types' => [
+ 'import_symbols' => true,
+ 'leading_backslash_in_global_namespace' => false,
+ ],
+ 'global_namespace_import' => false,
+ 'header_comment' => ['header' => ''],
+ 'single_line_comment_style' => ['comment_types' => ['hash']],
+ 'single_line_empty_body' => false,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'php_unit_test_annotation' => ['style' => 'annotation'],
+ ]);
diff --git a/Classes/Domain/Renderer/Command.php b/Classes/Domain/Renderer/Command.php
index e992382..6239f4f 100644
--- a/Classes/Domain/Renderer/Command.php
+++ b/Classes/Domain/Renderer/Command.php
@@ -1,9 +1,10 @@
config = array_merge($this->config, $config); // @phpstan-ignore-line
+ $this->config = array_merge($this->config, $config);
}
public function getHtmlFromMjml(string $mjml): string
@@ -44,6 +45,10 @@ public function getHtmlFromMjml(string $mjml): string
GeneralUtility::unlink_tempfile($temporaryMjmlFileWithPath);
+ if (is_null($result)) {
+ return '';
+ }
+
return implode('', $result);
}
diff --git a/Classes/Domain/Renderer/RendererInterface.php b/Classes/Domain/Renderer/RendererInterface.php
index 7be331a..84f7f70 100644
--- a/Classes/Domain/Renderer/RendererInterface.php
+++ b/Classes/Domain/Renderer/RendererInterface.php
@@ -1,4 +1,5 @@
coreExtensionsToLoad = [
+ 'typo3/cms-form',
+ ];
+
+ $this->testExtensionsToLoad = [
+ 'saccas/mjml',
+ ];
+
+ $this->configurationToUseInTestInstance = [
+ 'EXTENSIONS' => [
+ 'mjml' => [
+ 'nodeBinaryPath' => getenv('NODE_BIN_PATH') ?: 'node',
+ 'mjmlBinaryPath' => 'node_modules/mjml/bin/',
+ 'mjmlBinary' => 'mjml',
+ 'mjmlParams' => '-s --config.beautify true --config.minify true',
+ ],
+ ],
+ ];
+
+ parent::setUp();
+ }
+
+ protected function cleanUpGeneratedOutput(string $output): string
+ {
+ return preg_replace(
+ '//Uis',
+ '',
+ $output
+ ) ?? '';
+ }
+}
diff --git a/Tests/Functional/Domain/Renderer/CommandTest.php b/Tests/Functional/Domain/Renderer/CommandTest.php
new file mode 100644
index 0000000..220cad7
--- /dev/null
+++ b/Tests/Functional/Domain/Renderer/CommandTest.php
@@ -0,0 +1,28 @@
+get(Command::class);
+
+ $result = $subject->getHtmlFromMjml(
+ file_get_contents(__DIR__ . '/CommandTestFixture/Basic.mjml') ?: ''
+ );
+
+ self::assertStringEqualsFile(
+ __DIR__ . '/CommandTestFixture/Expected.html',
+ $this->cleanUpGeneratedOutput($result),
+ 'Command renderer did not return expected HTML.'
+ );
+ }
+}
diff --git a/Tests/Unit/Domain/Renderer/CommandTestFixture/Basic.mjml b/Tests/Functional/Domain/Renderer/CommandTestFixture/Basic.mjml
similarity index 100%
rename from Tests/Unit/Domain/Renderer/CommandTestFixture/Basic.mjml
rename to Tests/Functional/Domain/Renderer/CommandTestFixture/Basic.mjml
diff --git a/Tests/Functional/Domain/Renderer/CommandTestFixture/Expected.html b/Tests/Functional/Domain/Renderer/CommandTestFixture/Expected.html
new file mode 100644
index 0000000..e1d03dc
--- /dev/null
+++ b/Tests/Functional/Domain/Renderer/CommandTestFixture/Expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Tests/Functional/Mail/MjmlFluidEmailTest.php b/Tests/Functional/Mail/MjmlFluidEmailTest.php
new file mode 100644
index 0000000..6de727f
--- /dev/null
+++ b/Tests/Functional/Mail/MjmlFluidEmailTest.php
@@ -0,0 +1,45 @@
+setTemplateRootPaths([
+ 'EXT:mjml/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Templates/',
+ ]);
+
+ $subject = new MjmlFluidEmail($templatePaths);
+
+ $html = $subject->getHtmlBody();
+ self::assertIsString($html);
+ self::assertStringEqualsFile(
+ __DIR__ . '/MjmlFluidEmailTestFixture/Expected.html',
+ $this->cleanUpGeneratedOutput($html),
+ 'Command renderer did not return expected HTML.'
+ );
+
+ $text = $subject->getTextBody();
+ self::assertIsString($text);
+ self::assertStringEqualsFile(
+ __DIR__ . '/MjmlFluidEmailTestFixture/Expected.txt',
+ $text,
+ 'Rendering of view did not return expected plain text.'
+ );
+ }
+}
diff --git a/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.html b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.html
new file mode 100644
index 0000000..e1d03dc
--- /dev/null
+++ b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.txt b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.txt
new file mode 100644
index 0000000..2f808b8
--- /dev/null
+++ b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Expected.txt
@@ -0,0 +1,5 @@
+Easy and Quick
+
+Responsive
+
+Discover
\ No newline at end of file
diff --git a/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Templates/Default/Default.html b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Templates/Default/Default.html
new file mode 100644
index 0000000..e8b9e78
--- /dev/null
+++ b/Tests/Functional/Mail/MjmlFluidEmailTestFixture/Templates/Default/Default.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Easy and Quick
+
+
+
+ Responsive
+
+
+
+
+ Discover
+
+
+
+
+
diff --git a/Tests/Unit/AbstractUnitTestCase.php b/Tests/Unit/AbstractUnitTestCase.php
deleted file mode 100644
index 4a4948a..0000000
--- a/Tests/Unit/AbstractUnitTestCase.php
+++ /dev/null
@@ -1,22 +0,0 @@
-setCacheConfigurations([
- 'extbase_object' => [
- 'backend' => NullBackend::class,
- ],
- ]);
- }
-}
diff --git a/Tests/Unit/Domain/Renderer/CommandTest.php b/Tests/Unit/Domain/Renderer/CommandTest.php
deleted file mode 100644
index 28b1511..0000000
--- a/Tests/Unit/Domain/Renderer/CommandTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getMockBuilder(Package::class)
- // ->disableOriginalConstructor()
- // ->getMock();
- // $packageMock->expects($this->any())
- // ->method('getPackagePath')
- // ->willReturn(dirname(__FILE__, 5) . '/');
- // $packageManagerMock = $this->getMockBuilder(PackageManager::class)->getMock();
- // $packageManagerMock->expects($this->any())
- // ->method('isPackageActive')
- // ->with('mjml')
- // ->willReturn(true);
- // $packageManagerMock->expects($this->any())
- // ->method('getPackage')
- // ->with('mjml')
- // ->willReturn($packageMock);
- // ExtensionManagementUtility::setPackageManager($packageManagerMock);
- //
- // $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['mjml'] = serialize([
- // 'nodeBinaryPath' => 'node',
- // 'mjmlBinaryPath' => 'node_modules/mjml/bin/',
- // 'mjmlBinary' => 'mjml',
- // 'mjmlParams' => '-s --config.beautify true --config.minify true',
- // ]);
- //
- // $subject = $this->objectManager->get(Command::class);
- // $mjml = file_get_contents(__DIR__ . '/CommandTestFixture/Basic.mjml');
- // $html = $subject->getHtmlFromMjml($mjml);
- //
- // // remove comment rendered by the outputToConsole https://github.com/mjmlio/mjml/blob/50b08513b7a651c234829abfde254f106a62c859/packages/mjml-cli/src/commands/outputToConsole.js#L4
- // $html = preg_replace('//Uis', '', $html);
- //
- // $this->assertStringEqualsFile(
- // __DIR__ . '/CommandTestFixture/Expected.html',
- // $html,
- // 'Command renderer did not return expected HTML.'
- // );
- }
-}
diff --git a/Tests/Unit/Domain/Renderer/CommandTestFixture/Expected.html b/Tests/Unit/Domain/Renderer/CommandTestFixture/Expected.html
deleted file mode 100644
index 5bb42c1..0000000
--- a/Tests/Unit/Domain/Renderer/CommandTestFixture/Expected.html
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Tests/Unit/View/MjmlBasedViewTest.php b/Tests/Unit/View/MjmlBasedViewTest.php
deleted file mode 100644
index f0a73a0..0000000
--- a/Tests/Unit/View/MjmlBasedViewTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
- Easy and Quick
-
-
-
- Responsive
-
-
-
-
- Discover
-
-
-
-
-';
-
- /**
- * @test
- */
- public function viewCallsRendererAndReturnsRenderedHtml(): void
- {
- // TODO v13 Upgrade test
- // $expectedHtml = 'Simple HTML
';
- // $rendererMock = $this->getMockBuilder(RendererInterface::class)->getMock();
- // $rendererMock->expects($this->once())
- // ->method('getHtmlFromMjml')
- // ->with(static::EXAMPLE_MJML_TEMPLATE)
- // ->willReturn($expectedHtml);
- //
- // $subject = new MjmlBasedView(null, $rendererMock);
- // $subject->setTemplateSource(static::EXAMPLE_MJML_TEMPLATE);
- // $result = $subject->render();
- //
- // $this->assertSame(
- // $expectedHtml,
- // $result,
- // 'Rendering of view did not return expected HTML.'
- // );
- }
-}
diff --git a/composer.json b/composer.json
index 8064f52..076f827 100644
--- a/composer.json
+++ b/composer.json
@@ -3,6 +3,12 @@
"description": "Mjml view using mjml over npm",
"type": "typo3-cms-extension",
"homepage": "https://mjml.io",
+ "keywords": [
+ "TYPO3",
+ "extension",
+ "email",
+ "mjml"
+ ],
"license": ["GPL-2.0-or-later"],
"autoload": {
"psr-4": {
@@ -16,37 +22,34 @@
},
"require": {
"php": "^8.2",
+ "html2text/html2text": "^4.3",
"typo3/cms-core": "^13.4",
- "typo3/cms-form": "^13.4",
- "html2text/html2text": "^4.3"
- },
+ "typo3/cms-form": "^13.4"
+ },
"require-dev": {
- "squizlabs/php_codesniffer": "^3.2.0",
- "typo3/testing-framework": "^1.2.2 || ^6.16"
+ "phpstan/extension-installer": "^1.4",
+ "saschaegerer/phpstan-typo3": "^2.1",
+ "typo3/coding-standards": "^0.8.0",
+ "typo3/testing-framework": "^9.3"
},
"scripts": {
- "lint": [
- "! find Classes -type f -name \"*.php\" -exec php -d error_reporting=32767 -l {} \\; 2>&1 >&- | grep \"^\"",
- "! find Tests -type f -name \"*.php\" -exec php -d error_reporting=32767 -l {} \\; 2>&1 >&- | grep \"^\""
- ],
- "cgl": [
- "./vendor/bin/phpcs"
- ],
- "test": [
- "TYPO3_PATH_ROOT=web ./vendor/bin/phpunit"
- ],
"post-install-cmd": [
"npm install"
]
},
- "replace": {
- "typo3-ter/mjml": "self.version"
- },
"extra": {
"typo3/cms": {
"extension-key": "mjml",
- "cms-package-dir": "{$vendor-dir}/typo3/cms",
- "web-dir": "web"
+ "web-dir": ".Build/web"
+ }
+ },
+ "config": {
+ "sort-packages": true,
+ "lock": false,
+ "allow-plugins": {
+ "phpstan/extension-installer": true,
+ "typo3/class-alias-loader": true,
+ "typo3/cms-composer-installers": true
}
}
}
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000..86dbb16
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,37 @@
+parameters:
+ ignoreErrors:
+ -
+ message: '#^Cannot access offset ''MAIL'' on mixed\.$#'
+ identifier: offsetAccess.nonOffsetAccessible
+ count: 1
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
+
+ -
+ message: '#^Cannot access offset ''layoutRootPaths'' on mixed\.$#'
+ identifier: offsetAccess.nonOffsetAccessible
+ count: 2
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
+
+ -
+ message: '#^Cannot access offset ''partialRootPaths'' on mixed\.$#'
+ identifier: offsetAccess.nonOffsetAccessible
+ count: 2
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
+
+ -
+ message: '#^Cannot access offset ''templateRootPaths'' on mixed\.$#'
+ identifier: offsetAccess.nonOffsetAccessible
+ count: 2
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
+
+ -
+ message: '#^Parameter \#1 \$array of function array_replace_recursive expects array, mixed given\.$#'
+ identifier: argument.type
+ count: 3
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
+
+ -
+ message: '#^Parameter \#1 \$templateName of method TYPO3\\CMS\\Core\\Mail\\FluidEmail\:\:setTemplate\(\) expects string, mixed given\.$#'
+ identifier: argument.type
+ count: 1
+ path: Classes/Domain/Finishers/MjmlEmailFinisher.php
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..5d5d55f
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,8 @@
+includes:
+ - 'phpstan-baseline.neon'
+parameters:
+ level: 'max'
+ paths:
+ - 'Classes'
+ - 'Tests'
+ reportUnmatchedIgnoredErrors: true
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index f8dad0b..d9cc5f0 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,28 +1,31 @@
+
+ cacheDirectory=".phpunit.cache"
+ backupStaticProperties="false"
+ beStrictAboutCoverageMetadata="true"
+ requireCoverageMetadata="true">
-
- ./Tests/Unit
+
+ ./Tests/Functional/
-
-
+
+
./Classes
-
-
+
+
+
+
+
+