Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
155 changes: 155 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -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'
25 changes: 25 additions & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Finder;
use TYPO3\CodingStandards\CsFixerConfig;

return CsFixerConfig::create()
->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'],
]);
13 changes: 9 additions & 4 deletions Classes/Domain/Renderer/Command.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

namespace Saccas\Mjml\Domain\Renderer;

use TYPO3\CMS\Core\Utility\CommandUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class Command implements RendererInterface
{
Expand All @@ -18,11 +19,11 @@ class Command implements RendererInterface
];

/**
* @param $config array{nodeBinaryPath: string, mjmlBinaryPath: string, mjmlBinary: string, mjmlParams: string}
* @param array{nodeBinaryPath: string, mjmlBinaryPath: string, mjmlBinary: string, mjmlParams: string} $config
*/
public function __construct(array $config = [])
public function __construct(array $config)
{
$this->config = array_merge($this->config, $config); // @phpstan-ignore-line
$this->config = array_merge($this->config, $config);
}

public function getHtmlFromMjml(string $mjml): string
Expand All @@ -44,6 +45,10 @@ public function getHtmlFromMjml(string $mjml): string

GeneralUtility::unlink_tempfile($temporaryMjmlFileWithPath);

if (is_null($result)) {
return '';
}

return implode('', $result);
}

Expand Down
1 change: 1 addition & 0 deletions Classes/Domain/Renderer/RendererInterface.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Saccas\Mjml\Domain\Renderer;

/**
Expand Down
6 changes: 3 additions & 3 deletions Classes/Mail/MjmlFluidEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@

class MjmlFluidEmail extends FluidEmail
{
public const string MJML_FORMAT_HTML = 'html';
public const string MJML_FORMAT_TXT = 'txt';
public const MJML_FORMAT_HTML = 'html';
public const MJML_FORMAT_TXT = 'txt';

public const array MJML_FORMATS = [
public const MJML_FORMATS = [
self::MJML_FORMAT_HTML,
self::MJML_FORMAT_TXT,
];
Expand Down
2 changes: 1 addition & 1 deletion Configuration/Services.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
resource: '../Classes/*'

configuration.mjml:
class: array
class: 'array'
factory:
- '@TYPO3\CMS\Core\Configuration\ExtensionConfiguration'
- 'get'
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MJML

https://mjml.io integration for **TYPO3 EXT:Form**
https://mjml.io integration for TYPO3

MJML is a markup language designed to reduce the pain of coding a responsive email. Its semantic syntax makes it easy and straightforward and its rich standard components library speeds up your development time and lightens your email codebase. MJML’s open-source engine generates high quality responsive HTML compliant with best practices. https://mjml.io/getting-started-onboard

Expand Down Expand Up @@ -49,3 +49,14 @@ To automate the installation of the npm packages, you could add the following li
]
}
```

## Changelog

### v3.0.0

* Support TYPO3 v13.
* Add basic CI with QA tooling.

### v2.0.0

Support TYPO3 v11.
43 changes: 43 additions & 0 deletions Tests/Functional/AbstractFunctionalTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Saccas\Mjml\Tests\Functional;

use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

abstract class AbstractFunctionalTestCase extends FunctionalTestCase
{
protected function setUp(): void
{
$this->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(
'/<!-- FILE: (.*)-->/Uis',
'',
$output
) ?? '';
}
}
28 changes: 28 additions & 0 deletions Tests/Functional/Domain/Renderer/CommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Saccas\Mjml\Tests\Functional\Domain\Renderer;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Saccas\Mjml\Domain\Renderer\Command;
use Saccas\Mjml\Tests\Functional\AbstractFunctionalTestCase;

#[CoversClass(Command::class)]
final class CommandTest extends AbstractFunctionalTestCase
{
#[Test]
public function htmlIsReturnedForMjml(): void
{
$subject = $this->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.'
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><title></title><!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]--><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style type="text/css">#outlook a { padding: 0; } body { margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } table, td { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; } img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; } p { display: block; margin: 13px 0; }</style><!--[if mso]> <xml> <o:OfficeDocumentSettings> <o:AllowPNG/> <o:PixelsPerInch>96</o:PixelsPerInch> </o:OfficeDocumentSettings> </xml> <![endif]--><!--[if lte mso 11]> <style type="text/css"> .mj-outlook-group-fix { width:100% !important; } </style> <![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) { .mj-column-per-50 { width: 50% !important; max-width: 50%; } .mj-column-per-100 { width: 100% !important; max-width: 100%; } }</style><style media="screen and (min-width:480px)">.moz-text-html .mj-column-per-50 { width: 50% !important; max-width: 50%; } .moz-text-html .mj-column-per-100 { width: 100% !important; max-width: 100%; }</style><style type="text/css">@media only screen and (max-width:480px) { table.mj-full-width-mobile { width: 100% !important; } td.mj-full-width-mobile { width: auto !important; } }</style></head><body style="word-spacing:normal;"><div><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:300px;" ><![endif]--><div class="mj-column-per-50 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"><tbody><tr><td style="width:112px;"><img height="auto" src="/assets/img/easy-and-quick.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="112"></td></tr></tbody></table></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#595959;">Easy and Quick</div></td></tr></tbody></table></div><!--[if mso | IE]></td><td class="" style="vertical-align:top;width:300px;" ><![endif]--><div class="mj-column-per-50 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"><tbody><tr><td style="width:135px;"><img height="auto" src="/assets/img/responsive.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="135"></td></tr></tbody></table></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#595959;">Responsive</div></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tr><td align="center" bgcolor="#F45E43" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#F45E43;" valign="middle"><p style="display:inline-block;background:#F45E43;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:15px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;">Discover</p></td></tr></table></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>
Loading