Compare commits

...

5 Commits

Author SHA1 Message Date
40e7266890 Release 5.5.2 2024-12-23 16:59:11 +03:00
8ec779a9e0 Avoid unnecessary transformation when adding tracking image
[MAILPOET-6400]
2024-12-23 16:59:10 +03:00
e77b5bf309 Update mocks in Header and Footer renderers unit tests
[MAILPOET-6400]
2024-12-23 16:59:10 +03:00
f704b497e0 Fix deprecation warnings in unit tests
The missing property was causing multiple warnings in the test output.
Creation of dynamic property MailPoet\Test\Form\Block\RadioTest::$tester is deprecated
[MAILPOET-6400]
2024-12-23 16:59:10 +03:00
9dd35849a0 Improve content rendering in Header and Footer blocks
[MAILPOET-6400]
2024-12-23 16:59:10 +03:00
12 changed files with 201 additions and 25 deletions

View File

@ -1,5 +1,9 @@
== Changelog ==
= 5.5.2 - 2024-12-24 =
- Improved: minor changes and fixes.
= 5.5.1 - 2024-12-17 =
- Improved: added option to protect WordPress and WooCommerce registration forms with a captcha.

View File

@ -2,12 +2,25 @@
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\Util\pQuery\pQuery;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\CSS;
class Footer {
private NewsletterHtmlSanitizer $htmlSanitizer;
private WPFunctions $wp;
public function __construct(
NewsletterHtmlSanitizer $htmlSanitizer,
WPFunctions $wp
) {
$this->htmlSanitizer = $htmlSanitizer;
$this->wp = $wp;
}
public function render($element) {
$element['text'] = preg_replace('/\n/', '<br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
@ -32,7 +45,7 @@ class Footer {
}
$backgroundColor = $element['styles']['block']['backgroundColor'];
$backgroundColor = ($backgroundColor !== 'transparent') ?
'bgcolor="' . $backgroundColor . '"' :
'bgcolor="' . $this->wp->escAttr($backgroundColor) . '"' :
false;
if (!$backgroundColor) unset($element['styles']['block']['backgroundColor']);
$style = 'line-height: ' . $lineHeight . ';' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text');
@ -40,7 +53,7 @@ class Footer {
$template = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_footer" ' . $backgroundColor . ' style="' . $style . '">
' . str_replace('&', '&amp;', $DOM->html()) . '
' . $this->htmlSanitizer->sanitize($DOM->__toString()) . '
</td>
</tr>';
return $template;

View File

@ -2,12 +2,25 @@
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\Util\pQuery\pQuery;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\CSS;
class Header {
private NewsletterHtmlSanitizer $htmlSanitizer;
private WPFunctions $wp;
public function __construct(
NewsletterHtmlSanitizer $htmlSanitizer,
WPFunctions $wp
) {
$this->htmlSanitizer = $htmlSanitizer;
$this->wp = $wp;
}
public function render($element) {
$element['text'] = preg_replace('/\n/', '<br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
@ -32,7 +45,7 @@ class Header {
}
$backgroundColor = $element['styles']['block']['backgroundColor'];
$backgroundColor = ($backgroundColor !== 'transparent') ?
'bgcolor="' . $backgroundColor . '"' :
'bgcolor="' . $this->wp->escAttr($backgroundColor) . '"' :
false;
if (!$backgroundColor) unset($element['styles']['block']['backgroundColor']);
$style = 'line-height: ' . $lineHeight . ';' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text');
@ -40,7 +53,7 @@ class Header {
$template = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_header" ' . $backgroundColor . ' style="' . $style . '">
' . str_replace('&', '&amp;', $DOM->html()) . '
' . $this->htmlSanitizer->sanitize($DOM->__toString()) . '
</td>
</tr>';
return $template;

View File

@ -11,7 +11,7 @@ class OpenTracking {
public static function process($template) {
$DOM = new pQuery();
$DOM = $DOM->parseStr($template);
$template = $DOM->query('body');
$template = $DOM->select('body');
// url is a temporary data tag that will be further replaced with
// the proper track API URL during sending
$url = Links::DATA_TAG_OPEN;
@ -19,7 +19,7 @@ class OpenTracking {
'<img alt="" class="" src="%s"/>',
$url
);
$template->html($template->html() . $openTrackingImage);
self::appendToDomNodes($template, $openTrackingImage);
return $DOM->__toString();
}
@ -29,4 +29,16 @@ class OpenTracking {
});
return true;
}
private static function appendToDomNodes($template, $openTrackingImage): void {
// Preserve backward compatibility with pQuery::html()
// by processing an array of DomNodes
if (!empty($template)) {
$template = is_array($template) ? $template : [$template];
array_map(
fn($item) => $item->html($item->toString(true, true, 1) . $openTrackingImage),
$template,
);
}
}
}

View File

@ -2,7 +2,7 @@
/*
* Plugin Name: MailPoet
* Version: 5.5.1
* Version: 5.5.2
* Plugin URI: https://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -20,7 +20,7 @@
*/
$mailpoetPlugin = [
'version' => '5.5.1',
'version' => '5.5.2',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -3,7 +3,7 @@ Contributors: mailpoet, woocommerce, automattic
Tags: email marketing, post notification, woocommerce emails, email automation, newsletter
Requires at least: 6.6
Tested up to: 6.7
Stable tag: 5.5.1
Stable tag: 5.5.2
Requires PHP: 7.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -228,9 +228,7 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
== Changelog ==
= 5.5.1 - 2024-12-17 =
* Improved: added option to protect WordPress and WooCommerce registration forms with a captcha.
* Changed: removed 3rd party Beamer integration;
* Fixed: error when trying to use mailpoet_settings database table before it is available.
= 5.5.2 - 2024-12-24 =
* Improved: minor changes and fixes.
[See the changelog for all versions.](https://github.com/mailpoet/mailpoet/blob/trunk/mailpoet/CHANGELOG.md)

View File

@ -0,0 +1,67 @@
<?php declare(strict_types = 1);
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\WP\Functions as WPFunctions;
class FooterAndHeaderTest extends \MailPoetTest {
private Footer $footerRenderer;
private Header $headerRenderer;
private $block = [
'text' => '<p>Hello</p>',
'styles' => [
'text' => [
'fontSize' => 16,
],
'block' => [
'backgroundColor' => '#ffffff',
],
],
];
public function _before() {
parent::_before();
$this->footerRenderer = new Footer(
$this->diContainer->get(NewsletterHtmlSanitizer::class),
$this->diContainer->get(WPFunctions::class)
);
$this->headerRenderer = new Header(
$this->diContainer->get(NewsletterHtmlSanitizer::class),
$this->diContainer->get(WPFunctions::class)
);
}
public function testHeaderAndFooterSanitizeText(): void {
$this->checkItSanitizesText($this->footerRenderer);
$this->checkItSanitizesText($this->headerRenderer);
}
public function testHeaderAndFooterSanitizeStyles(): void {
$this->checkItSanitizesStyles($this->footerRenderer);
$this->checkItSanitizesStyles($this->headerRenderer);
}
public function checkItSanitizesText($renderer): void {
$block = $this->block;
// It removes tags that are not allowed
$block['text'] = '<p><img src=x onerror="alert(1)"><script>alert(2);</script></p>';
$result = $renderer->render($block);
$this->assertStringNotContainsString('<img', $result);
$this->assertStringNotContainsString('alert(1)', $result);
$this->assertStringNotContainsString('<script>', $result);
// Html entities should remain encoded
$block['text'] = '<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>';
$result = $renderer->render($block);
$this->assertStringContainsString('&lt;script&gt;alert(1)&lt;/script&gt;', $result);
}
private function checkItSanitizesStyles($renderer): void {
$block = $this->block;
$block['styles']['block']['backgroundColor'] = '"> <script>alert(1);</script>';
$result = $renderer->render($block);
$this->assertStringNotContainsString('<script>', $result);
$this->assertStringContainsString('&lt;script&gt;', $result);
}
}

View File

@ -0,0 +1,27 @@
<?php declare(strict_types = 1);
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
class OpenTrackingTest extends \MailPoetTest {
public function testItAddsTrackingImage(): void {
$template = '<html><body><p>text</p></body></html>';
$result = OpenTracking::process($template);
$this->assertStringContainsString('<img', $result);
$this->assertStringContainsString('src="' . Links::DATA_TAG_OPEN . '"', $result);
}
public function testItReturnsOriginalInputIfBodyTagIsMissing(): void {
$template = '<html><p>text</p></html>';
$result = OpenTracking::process($template);
$this->assertEquals($template, $result);
}
public function testItPreservesHTMLEntities(): void {
$template = '<html><body><p>text</p>&lt;img src="x" onerror="alert(1)"&gt;</body></html>';
$result = OpenTracking::process($template);
$this->assertStringContainsString('&lt;img src="x" onerror="alert(1)"&gt;', $result);
}
}

View File

@ -6,6 +6,7 @@ use Codeception\Util\Fixtures;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Features\FeaturesController;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Renderer\Blocks\Button;
use MailPoet\Newsletter\Renderer\Blocks\Divider;
@ -268,7 +269,11 @@ class RendererTest extends \MailPoetTest {
public function testItRendersHeader() {
$newsletter = (array)$this->newsletter->getBody();
$template = $newsletter['content']['blocks'][0]['blocks'][0]['blocks'][0];
$DOM = $this->dOMParser->parseStr((new Header)->render($template));
$headerRenderer = new Header(
$this->diContainer->get(NewsletterHtmlSanitizer::class),
$this->diContainer->get(WPFunctions::class)
);
$DOM = $this->dOMParser->parseStr($headerRenderer->render($template));
// element should be properly nested, and styles should be applied
verify($DOM('tr > td.mailpoet_header', 0)->html())->notEmpty();
verify($DOM('tr > td > a', 0)->html())->notEmpty();
@ -582,7 +587,11 @@ class RendererTest extends \MailPoetTest {
public function testItRendersFooter() {
$newsletter = (array)$this->newsletter->getBody();
$template = $newsletter['content']['blocks'][3]['blocks'][0]['blocks'][0];
$DOM = $this->dOMParser->parseStr((new Footer)->render($template));
$footerRenderer = new Footer(
$this->diContainer->get(NewsletterHtmlSanitizer::class),
$this->diContainer->get(WPFunctions::class)
);
$DOM = $this->dOMParser->parseStr($footerRenderer->render($template));
// element should be properly nested, and styles should be applied
verify($DOM('tr > td.mailpoet_footer', 0)->html())->notEmpty();
verify($DOM('tr > td > a', 0)->html())->notEmpty();

View File

@ -2,8 +2,11 @@
namespace MailPoet\Newsletter\Renderer\Blocks;
class FooterTest extends \MailPoetUnitTest {
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\WP\Functions as WPFunctions;
class FooterTest extends \MailPoetUnitTest {
private Footer $renderer;
private $block = [
'type' => 'footer',
'text' => '<p>Footer text. <a href="http://example.com">link</a></p>',
@ -24,8 +27,21 @@ class FooterTest extends \MailPoetUnitTest {
],
];
public function _before() {
parent::_before();
$wpMock = $this->createMock(WPFunctions::class);
$wpMock->method('escAttr')->willReturnCallback(function($attr) {
return $attr;
});
$sanitizerMock = $this->createMock(NewsletterHtmlSanitizer::class);
$sanitizerMock->method('sanitize')->willReturnCallback(function($html) {
return $html;
});
$this->renderer = new Footer($sanitizerMock, $wpMock);
}
public function testItRendersCorrectly() {
$output = (new Footer)->render($this->block);
$output = $this->renderer->render($this->block);
$expectedResult = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_footer" style="line-height: 19.2px;color: #222222;font-family: roboto, \'helvetica neue\', helvetica, arial, sans-serif;font-size: 12px;text-align: center;">
@ -37,7 +53,7 @@ class FooterTest extends \MailPoetUnitTest {
public function testItRendersWithBackgroundColor() {
$this->block['styles']['block']['backgroundColor'] = '#f0f0f0';
$output = (new Footer)->render($this->block);
$output = $this->renderer->render($this->block);
$expectedResult = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_footer" bgcolor="#f0f0f0" style="line-height: 19.2px;background-color: #f0f0f0;color: #222222;font-family: roboto, \'helvetica neue\', helvetica, arial, sans-serif;font-size: 12px;text-align: center;">
@ -49,13 +65,13 @@ class FooterTest extends \MailPoetUnitTest {
public function testItPrefersInlinedCssForLinks() {
$this->block['text'] = '<p>Footer text. <a href="http://example.com" style="color:#aaaaaa;">link</a></p>';
$output = (new Footer)->render($this->block);
$output = $this->renderer->render($this->block);
verify($output)->stringContainsString('<a href="http://example.com" style="color:#aaaaaa;text-decoration:none">link</a>');
}
public function testItRaisesExceptionIfTextIsNotString() {
$this->block['text'] = ['some', 'array'];
$this->expectException('RuntimeException');
(new Footer)->render($this->block);
$this->renderer->render($this->block);
}
}

View File

@ -2,8 +2,11 @@
namespace MailPoet\Newsletter\Renderer\Blocks;
class HeaderTest extends \MailPoetUnitTest {
use MailPoet\Newsletter\NewsletterHtmlSanitizer;
use MailPoet\WP\Functions as WPFunctions;
class HeaderTest extends \MailPoetUnitTest {
private Header $renderer;
private $block = [
'type' => 'header',
'text' => '<a href="[link:newsletter_view_in_browser_url]">View this in your browser.</a>',
@ -24,8 +27,21 @@ class HeaderTest extends \MailPoetUnitTest {
],
];
public function _before() {
parent::_before();
$wpMock = $this->createMock(WPFunctions::class);
$wpMock->method('escAttr')->willReturnCallback(function($attr) {
return $attr;
});
$sanitizerMock = $this->createMock(NewsletterHtmlSanitizer::class);
$sanitizerMock->method('sanitize')->willReturnCallback(function($html) {
return $html;
});
$this->renderer = new Header($sanitizerMock, $wpMock);
}
public function testItRendersCorrectly() {
$output = (new Header)->render($this->block);
$output = $this->renderer->render($this->block);
$expectedResult = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_header" style="line-height: 19.2px;color: #222222;font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;font-size: 12px;text-align: left;">
@ -37,7 +53,7 @@ class HeaderTest extends \MailPoetUnitTest {
public function testItRendersBackgroundColorCorrectly() {
$this->block['styles']['block']['backgroundColor'] = '#f0f0f0';
$output = (new Header)->render($this->block);
$output = $this->renderer->render($this->block);
$expectedResult = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_header" bgcolor="#f0f0f0" style="line-height: 19.2px;background-color: #f0f0f0;color: #222222;font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;font-size: 12px;text-align: left;">
@ -49,13 +65,13 @@ class HeaderTest extends \MailPoetUnitTest {
public function testItPrefersInlinedCssForLinks() {
$this->block['text'] = '<p>Header text. <a href="http://example.com" style="color:#aaaaaa;">link</a></p>';
$output = (new Footer)->render($this->block);
$output = $this->renderer->render($this->block);
verify($output)->stringContainsString('<a href="http://example.com" style="color:#aaaaaa;text-decoration:underline">link</a>');
}
public function testItRaisesExceptionIfTextIsNotString() {
$this->block['text'] = ['some', 'array'];
$this->expectException('RuntimeException');
(new Header)->render($this->block);
$this->renderer->render($this->block);
}
}

View File

@ -43,6 +43,7 @@ $console = new \Codeception\Lib\Console\Output([]);
abstract class MailPoetUnitTest extends \Codeception\TestCase\Test {
protected $runTestInSeparateProcess = false;
protected $preserveGlobalState = false;
protected $tester = null;
}
include '_fixtures.php';