This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,80 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\AutomaticEmails\WooCommerce\Events\AbandonedCart;
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce as WooCommerceEmail;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionEntity;
use MailPoet\Entities\SendingQueueEntity;
class AbandonedCartContent {
/** @var AutomatedLatestContentBlock */
private $ALCBlock;
public function __construct(
AutomatedLatestContentBlock $ALCBlock
) {
$this->ALCBlock = $ALCBlock;
}
public function render(
NewsletterEntity $newsletter,
array $args,
bool $preview = false,
SendingQueueEntity $sendingQueue = null
): array {
if (
!in_array(
$newsletter->getType(),
[
NewsletterEntity::TYPE_AUTOMATIC,
NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL,
NewsletterEntity::TYPE_AUTOMATION,
],
true
)
) {
// Do not display the block if not an automatic email
return [];
}
$groupOption = $newsletter->getOptions()->filter(function (NewsletterOptionEntity $newsletterOption = null) {
if (!$newsletterOption) return false;
$optionField = $newsletterOption->getOptionField();
return $optionField && $optionField->getName() === 'group';
})->first();
$eventOption = $newsletter->getOptions()->filter(function (NewsletterOptionEntity $newsletterOption = null) {
if (!$newsletterOption) return false;
$optionField = $newsletterOption->getOptionField();
return $optionField && $optionField->getName() === 'event';
})->first();
if (
!$groupOption
|| $groupOption->getValue() !== WooCommerceEmail::SLUG
|| !$eventOption
|| $eventOption->getValue() !== AbandonedCart::SLUG
) {
// Do not display the block if not an AbandonedCart email
return [];
}
if ($preview) {
// Display latest products for preview (no 'posts' argument specified)
return $this->ALCBlock->render($newsletter, $args);
}
if (!$sendingQueue) {
// Do not display the block if we're not sending an email
return [];
}
$meta = $sendingQueue->getMeta();
if (empty($meta[AbandonedCart::TASK_META_NAME])) {
// Do not display the block if a cart is empty
return [];
}
$args['amount'] = 50;
$args['posts'] = $meta[AbandonedCart::TASK_META_NAME];
return $this->ALCBlock->render($newsletter, $args);
}
}
@@ -0,0 +1,75 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterPostEntity;
use MailPoet\Newsletter\AutomatedLatestContent;
use MailPoet\Newsletter\BlockPostQuery;
use MailPoet\Newsletter\NewsletterPostsRepository;
class AutomatedLatestContentBlock {
/**
* Cache for rendered posts in newsletter.
* Used to prevent duplicate post in case a newsletter contains 2 ALC blocks
* @var array
*/
public $renderedPostsInNewsletter;
/** @var AutomatedLatestContent */
private $ALC;
/** @var NewsletterPostsRepository */
private $newsletterPostsRepository;
public function __construct(
NewsletterPostsRepository $newsletterPostsRepository,
AutomatedLatestContent $ALC
) {
$this->renderedPostsInNewsletter = [];
$this->ALC = $ALC;
$this->newsletterPostsRepository = $newsletterPostsRepository;
}
public function render(NewsletterEntity $newsletter, $args) {
$newerThanTimestamp = false;
$newsletterId = false;
if ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION_HISTORY) {
$parent = $newsletter->getParent();
if ($parent instanceof NewsletterEntity) {
$newsletterId = $parent->getId();
$lastPost = $this->newsletterPostsRepository->findOneBy(['newsletter' => $parent], ['createdAt' => 'desc']);
if ($lastPost instanceof NewsletterPostEntity) {
$newerThanTimestamp = $lastPost->getCreatedAt();
}
}
}
$postsToExclude = $this->getRenderedPosts((int)$newsletterId);
$query = new BlockPostQuery([
'args' => $args,
'postsToExclude' => $postsToExclude,
'newsletterId' => $newsletterId,
'newerThanTimestamp' => $newerThanTimestamp,
'dynamic' => true,
]);
$aLCPosts = $this->ALC->getPosts($query);
foreach ($aLCPosts as $post) {
$postsToExclude[] = $post->ID;
}
$this->setRenderedPosts((int)$newsletterId, $postsToExclude);
return $this->ALC->transformPosts($args, $aLCPosts);
}
private function getRenderedPosts(int $newsletterId) {
return $this->renderedPostsInNewsletter[$newsletterId] ?? [];
}
private function setRenderedPosts(int $newsletterId, array $posts) {
return $this->renderedPostsInNewsletter[$newsletterId] = $posts;
}
}
@@ -0,0 +1,95 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Button {
public function render($element, $columnBaseWidth) {
$originalWidth = $this->getOriginalWidth($element, $columnBaseWidth);
$element['styles']['block']['width'] = $this->calculateWidth($element, $columnBaseWidth);
$styles = 'display:block;text-decoration:none;text-align:center;' . StylesHelper::getBlockStyles($element, $exclude = ['textAlign']);
$styles = EHelper::escapeHtmlStyleAttr($styles);
$template = '
<tr>
<td class="mailpoet_padded_vertical mailpoet_padded_side" valign="top">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
<tr>
<td class="mailpoet_button-container" style="text-align:' . $element['styles']['block']['textAlign'] . ';"><!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
href="' . EHelper::escapeHtmlLinkAttr($element['url']) . '"
style="height:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['lineHeight']) . ';
width:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['width']) . ';
v-text-anchor:middle;"
arcsize="' . round((int)$element['styles']['block']['borderRadius'] / ((int)$element['styles']['block']['lineHeight'] ?: 1) * 100) . '%"
strokeweight="' . EHelper::escapeHtmlAttr($element['styles']['block']['borderWidth']) . '"
strokecolor="' . EHelper::escapeHtmlAttr($element['styles']['block']['borderColor']) . '"
fillcolor="' . EHelper::escapeHtmlAttr($element['styles']['block']['backgroundColor']) . '">
<w:anchorlock/>
<center style="color:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontColor']) . ';
font-family:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontFamily']) . ';
font-size:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontSize']) . ';
font-weight:bold;">' . EHelper::escapeHtmlText($element['text']) . '
</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-- -->
<table
border="0"
cellspacing="0"
cellpadding="0"
role="presentation"
style="display:inline-block;border-collapse:separate;mso-table-lspace:0;mso-table-rspace:0;width:' . EHelper::escapeHtmlStyleAttr($originalWidth) . '"
width="' . EHelper::escapeHtmlStyleAttr($originalWidth) . '"
>
<tr>
<td class="mailpoet_table_button"
valign="middle"
role="presentation"
style="mso-table-lspace: 0;mso-table-rspace: 0;' . $styles . '"
>
<a class="mailpoet_button" style="
text-decoration: none;
display: block;
line-height: ' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['lineHeight']) . ';
color: ' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontColor']) . ';
" href="' . EHelper::escapeHtmlLinkAttr($element['url']) . '" target="_blank">' . EHelper::escapeHtmlText($element['text']) . '</a>
</td>
</tr>
</table>
<!--<![endif]-->
</td>
</tr>
</table>
</div>
</td>
</tr>';
return $template;
}
public function getOriginalWidth($element, $columnBaseWidth): string {
$columnWidth = $columnBaseWidth - (StylesHelper::$paddingWidth * 2);
$originalWidth = (int)$element['styles']['block']['width'];
$originalWidth = ($originalWidth > $columnWidth) ?
$columnWidth :
$originalWidth;
return $originalWidth . 'px';
}
public function calculateWidth($element, $columnBaseWidth): string {
$columnWidth = $columnBaseWidth - (StylesHelper::$paddingWidth * 2);
$borderWidth = (int)$element['styles']['block']['borderWidth'];
$buttonWidth = (int)$element['styles']['block']['width'];
$buttonWidth = ($buttonWidth > $columnWidth) ?
$columnWidth :
$buttonWidth;
$buttonWidth = $buttonWidth - (2 * $borderWidth) . 'px';
return $buttonWidth;
}
}
@@ -0,0 +1,90 @@
<?php declare(strict_types = 1);
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\NewsletterProcessingException;
use MailPoet\WooCommerce\Helper;
class Coupon {
const TYPE = 'coupon';
const CODE_PLACEHOLDER = 'XXXX-XXXXXXX-XXXX';
/*** @var Helper */
private $helper;
public function __construct(
Helper $helper
) {
$this->helper = $helper;
}
public function render($element, $columnBaseWidth) {
$couponCode = self::CODE_PLACEHOLDER;
if (!empty($element['couponId'])) {
try {
$couponCode = $this->helper->wcGetCouponCodeById((int)$element['couponId']);
} catch (\Exception $e) {
if (!$this->helper->isWooCommerceActive()) {
throw NewsletterProcessingException::create()->withMessage(__('WooCommerce is not active', 'mailpoet'));
} else {
throw NewsletterProcessingException::create()->withMessage($e->getMessage())->withCode($e->getCode());
}
}
if (empty($couponCode)) {
throw NewsletterProcessingException::create()->withMessage(__('Couldn\'t find the coupon. Please update the email if the coupon was removed.', 'mailpoet'));
}
}
$element['styles']['block']['width'] = $this->calculateWidth($element, $columnBaseWidth);
$styles = 'display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none;text-align:center;' . StylesHelper::getBlockStyles($element, $exclude = ['textAlign']);
$styles = EHelper::escapeHtmlStyleAttr($styles);
$template = '
<tr>
<td class="mailpoet_padded_vertical mailpoet_padded_side" valign="top">
<div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
<tr>
<td class="mailpoet_coupon-container" style="text-align:' . $element['styles']['block']['textAlign'] . ';"><!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
style="height:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['lineHeight']) . ';
width:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['width']) . ';
v-text-anchor:middle;"
arcsize="' . round((int)$element['styles']['block']['borderRadius'] / ((int)$element['styles']['block']['lineHeight'] ?: 1) * 100) . '%"
strokeweight="' . EHelper::escapeHtmlAttr($element['styles']['block']['borderWidth']) . '"
strokecolor="' . EHelper::escapeHtmlAttr($element['styles']['block']['borderColor']) . '"
fillcolor="' . EHelper::escapeHtmlAttr($element['styles']['block']['backgroundColor']) . '">
<w:anchorlock/>
<center style="color:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontColor']) . ';
font-family:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontFamily']) . ';
font-size:' . EHelper::escapeHtmlStyleAttr($element['styles']['block']['fontSize']) . ';
font-weight:bold;">' . EHelper::escapeHtmlText($couponCode) . '
</center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-- -->
<div class="mailpoet_coupon" style="' . $styles . '">' . EHelper::escapeHtmlText($couponCode) . '</div>
<!--<![endif]-->
</td>
</tr>
</table>
</div>
</td>
</tr>';
return $template;
}
public function calculateWidth($element, $columnBaseWidth): string {
$columnWidth = $columnBaseWidth - (StylesHelper::$paddingWidth * 2);
$borderWidth = (int)$element['styles']['block']['borderWidth'];
$width = (int)$element['styles']['block']['width'];
$width = ($width > $columnWidth) ?
$columnWidth :
$width;
return ($width - (2 * $borderWidth) . 'px');
}
}
@@ -0,0 +1,42 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Divider {
public function render($element) {
$backgroundColor = $element['styles']['block']['backgroundColor'];
$dividerCellStyle = "border-top-width: {$element['styles']['block']['borderWidth']};";
$dividerCellStyle .= "border-top-style: {$element['styles']['block']['borderStyle']};";
$dividerCellStyle .= "border-top-color: {$element['styles']['block']['borderColor']};";
$template = '
<tr>
<td class="mailpoet_divider" valign="top" ' .
(($element['styles']['block']['backgroundColor'] !== 'transparent') ?
'bgColor="' . EHelper::escapeHtmlAttr($backgroundColor) . '" style="background-color:' . EHelper::escapeHtmlStyleAttr($backgroundColor) . ';' :
'style="'
) .
sprintf(
'padding: %s %spx %s %spx;',
EHelper::escapeHtmlStyleAttr($element['styles']['block']['padding']),
StylesHelper::$paddingWidth,
EHelper::escapeHtmlStyleAttr($element['styles']['block']['padding']),
StylesHelper::$paddingWidth
) . '">
<table width="100%" border="0" cellpadding="0" cellspacing="0"
style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
<tr>
<td class="mailpoet_divider-cell" style="' . EHelper::escapeHtmlStyleAttr($dividerCellStyle) . '">
</td>
</tr>
</table>
</td>
</tr>';
return $template;
}
}
@@ -0,0 +1,64 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
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']);
$lineHeight = sprintf(
'%spx',
StylesHelper::$defaultLineHeight * (int)$element['styles']['text']['fontSize']
);
if (!is_string($element['text'])) {
throw new \RuntimeException('$element[\'text\'] should be a string.');
}
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($element['text']);
if (isset($element['styles']['link'])) {
$links = $DOM->query('a');
if ($links->count()) {
$css = new CSS();
foreach ($links as $link) {
$elementLinkStyles = StylesHelper::getStyles($element['styles'], 'link');
$link->style = $css->mergeInlineStyles($elementLinkStyles, $link->style);
}
}
}
$backgroundColor = $element['styles']['block']['backgroundColor'];
$backgroundColor = ($backgroundColor !== 'transparent') ?
'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');
$style = EHelper::escapeHtmlStyleAttr($style);
$template = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_footer" ' . $backgroundColor . ' style="' . $style . '">
' . $this->htmlSanitizer->sanitize($DOM->__toString()) . '
</td>
</tr>';
return $template;
}
}
@@ -0,0 +1,64 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
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']);
$lineHeight = sprintf(
'%spx',
StylesHelper::$defaultLineHeight * (int)$element['styles']['text']['fontSize']
);
if (!is_string($element['text'])) {
throw new \RuntimeException('$element[\'text\'] should be a string.');
}
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($element['text']);
if (isset($element['styles']['link'])) {
$links = $DOM->query('a');
if ($links->count()) {
$css = new CSS();
foreach ($links as $link) {
$elementLinkStyles = StylesHelper::getStyles($element['styles'], 'link');
$link->style = $css->mergeInlineStyles($elementLinkStyles, $link->style);
}
}
}
$backgroundColor = $element['styles']['block']['backgroundColor'];
$backgroundColor = ($backgroundColor !== 'transparent') ?
'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');
$style = EHelper::escapeHtmlStyleAttr($style);
$template = '
<tr>
<td class="mailpoet_header_footer_padded mailpoet_header" ' . $backgroundColor . ' style="' . $style . '">
' . $this->htmlSanitizer->sanitize($DOM->__toString()) . '
</td>
</tr>';
return $template;
}
}
@@ -0,0 +1,76 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\WP\Functions as WPFunctions;
class Image {
public function render($element, $columnBaseWidth) {
if (empty($element['src'])) {
return '';
}
if (substr($element['src'], 0, 1) == '/' && substr($element['src'], 1, 1) != '/') {
$element['src'] = WPFunctions::get()->getOption('siteurl') . $element['src'];
}
$element['width'] = str_replace('px', '', $element['width']);
$element['height'] = str_replace('px', '', $element['height']);
$originalWidth = 0;
if (is_numeric($element['width']) && is_numeric($element['height'])) {
$element['width'] = (int)$element['width'];
$element['height'] = (int)$element['height'];
$originalWidth = $element['width'];
$element = $this->adjustImageDimensions($element, $columnBaseWidth);
}
// If image was downsized because of column width set width to aways fill full column (e.g. on mobile)
$style = '';
if ($element['fullWidth'] === true && $originalWidth > $element['width']) {
$style = 'style="width:100%"';
}
$imageTemplate = '
<img src="' . EHelper::escapeHtmlLinkAttr($element['src']) . '" width="' . EHelper::escapeHtmlAttr($element['width']) . '" alt="' . EHelper::escapeHtmlAttr($element['alt']) . '"' . $style . '/>
';
if (!empty($element['link'])) {
$imageTemplate = '<a href="' . EHelper::escapeHtmlLinkAttr($element['link']) . '">' . trim($imageTemplate) . '</a>';
}
$align = 'center';
if (!empty($element['styles']['block']['textAlign']) && in_array($element['styles']['block']['textAlign'], ['left', 'right'])) {
$align = $element['styles']['block']['textAlign'];
}
$template = '
<tr>
<td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded_vertical mailpoet_padded_side' : '') . '" align="' . EHelper::escapeHtmlAttr($align) . '" valign="top">
' . trim($imageTemplate) . '
</td>
</tr>';
return $template;
}
public function adjustImageDimensions($element, $columnBaseWidth) {
$paddedWidth = StylesHelper::$paddingWidth * 2;
// scale image to fit column width
if ($element['width'] > $columnBaseWidth) {
$ratio = $element['width'] / $columnBaseWidth;
$element['width'] = $columnBaseWidth;
$element['height'] = (int)ceil($element['height'] / $ratio);
}
// resize image if the image is padded and wider than padded column width
if (
$element['fullWidth'] === false &&
$element['width'] > ($columnBaseWidth - $paddedWidth)
) {
$ratio = $element['width'] / ($columnBaseWidth - $paddedWidth);
$element['width'] = $columnBaseWidth - $paddedWidth;
$element['height'] = (int)ceil($element['height'] / $ratio);
}
return $element;
}
}
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\WP\Functions as WPFunctions;
class Placeholder {
/** @var WPFunctions */
private $wp;
public function __construct(
WPFunctions $wp
) {
$this->wp = $wp;
}
public function render($element): string {
$placeholder = $element['placeholder'];
$class = $element['class'] ?? '';
$style = $element['style'] ?? '';
return '
<tr>
<td class="' . $this->wp->escAttr($class) . '" style="' . $this->wp->escAttr($style) . '">
' . $this->wp->escHtml($placeholder) . '
</td>
</tr>';
}
}
@@ -0,0 +1,144 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Renderer {
/** @var AutomatedLatestContentBlock */
private $ALC;
/** @var Button */
private $button;
/** @var Divider */
private $divider;
/** @var Footer */
private $footer;
/** @var Header */
private $header;
/** @var Image */
private $image;
/** @var Social */
private $social;
/** @var Spacer */
private $spacer;
/** @var Text */
private $text;
/** @var Placeholder */
private $placeholder;
/** @var Coupon */
private $coupon;
public function __construct(
AutomatedLatestContentBlock $ALC,
Button $button,
Divider $divider,
Footer $footer,
Header $header,
Image $image,
Social $social,
Spacer $spacer,
Text $text,
Placeholder $placeholder,
Coupon $coupon
) {
$this->ALC = $ALC;
$this->button = $button;
$this->divider = $divider;
$this->footer = $footer;
$this->header = $header;
$this->image = $image;
$this->social = $social;
$this->spacer = $spacer;
$this->text = $text;
$this->placeholder = $placeholder;
$this->coupon = $coupon;
}
public function render(NewsletterEntity $newsletter, $data) {
if (is_null($data['blocks']) && isset($data['type'])) {
return null;
}
$columnCount = count($data['blocks']);
$columnsLayout = isset($data['columnLayout']) ? $data['columnLayout'] : null;
$columnWidths = ColumnsHelper::columnWidth($columnCount, $columnsLayout);
$columnContent = [];
foreach ($data['blocks'] as $index => $columnBlocks) {
$renderedBlockElement = $this->renderBlocksInColumn($newsletter, $columnBlocks, $columnWidths[$index]);
$columnContent[] = $renderedBlockElement;
}
return $columnContent;
}
private function renderBlocksInColumn(NewsletterEntity $newsletter, $block, $columnBaseWidth) {
$blockContent = '';
$_this = $this;
array_map(function($block) use (&$blockContent, $columnBaseWidth, $newsletter, $_this) {
$renderedBlockElement = $_this->createElementFromBlockType($newsletter, $block, $columnBaseWidth);
if (isset($block['blocks'])) {
$renderedBlockElement = $_this->renderBlocksInColumn($newsletter, $block, $columnBaseWidth);
// nested vertical column container is rendered as an array
if (is_array($renderedBlockElement)) {
$renderedBlockElement = implode('', $renderedBlockElement);
}
}
$blockContent .= $renderedBlockElement;
}, $block['blocks']);
return $blockContent;
}
public function createElementFromBlockType(NewsletterEntity $newsletter, $block, $columnBaseWidth) {
if ($block['type'] === 'automatedLatestContent') {
return $this->processAutomatedLatestContent($newsletter, $block, $columnBaseWidth);
}
$block = StylesHelper::applyTextAlignment($block);
switch ($block['type']) {
case 'button':
return $this->button->render($block, $columnBaseWidth);
case 'divider':
return $this->divider->render($block);
case 'footer':
return $this->footer->render($block);
case 'header':
return $this->header->render($block);
case 'image':
return $this->image->render($block, $columnBaseWidth);
case 'social':
return $this->social->render($block);
case 'spacer':
return $this->spacer->render($block);
case 'text':
return $this->text->render($block);
case 'placeholder':
return $this->placeholder->render($block);
case Coupon::TYPE:
return $this->coupon->render($block, $columnBaseWidth);
}
return "<!-- Skipped unsupported block type: {$block['type']} -->";
}
public function processAutomatedLatestContent(NewsletterEntity $newsletter, $args, $columnBaseWidth) {
$transformedPosts = [
'blocks' => $this->ALC->render($newsletter, $args),
];
$transformedPosts = StylesHelper::applyTextAlignment($transformedPosts);
return $this->renderBlocksInColumn($newsletter, $transformedPosts, $columnBaseWidth);
}
}
@@ -0,0 +1,41 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
class Social {
public function render($element) {
$iconsBlock = '';
if (is_array($element['icons'])) {
foreach ($element['icons'] as $index => $icon) {
if (empty($icon['image'])) {
continue;
}
$style = 'width:' . $icon['width'] . ';height:' . $icon['width'] . ';-ms-interpolation-mode:bicubic;border:0;display:inline;outline:none;';
$iconsBlock .= '<a href="' . EHelper::escapeHtmlLinkAttr($icon['link']) . '" style="text-decoration:none!important;"
><img
src="' . EHelper::escapeHtmlLinkAttr($icon['image']) . '"
width="' . (int)$icon['width'] . '"
height="' . (int)$icon['height'] . '"
style="' . EHelper::escapeHtmlStyleAttr($style) . '"
alt="' . EHelper::escapeHtmlAttr($icon['iconType']) . '"
></a>&nbsp;';
}
}
$alignment = isset($element['styles']['block']['textAlign']) ? $element['styles']['block']['textAlign'] : 'center';
if (!empty($iconsBlock)) {
$template = '
<tr>
<td class="mailpoet_padded_side mailpoet_padded_vertical" valign="top" align="' . EHelper::escapeHtmlAttr($alignment) . '">
' . $iconsBlock . '
</td>
</tr>';
return $template;
}
}
}
@@ -0,0 +1,22 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
class Spacer {
public function render($element) {
$height = (int)$element['styles']['block']['height'];
$backgroundColor = EHelper::escapeHtmlAttr($element['styles']['block']['backgroundColor']);
$template = '
<tr>
<td class="mailpoet_spacer" ' .
(($backgroundColor !== 'transparent') ? 'bgcolor="' . $backgroundColor . '" ' : '') .
'height="' . $height . '" valign="top"></td>
</tr>';
return $template;
}
}
@@ -0,0 +1,189 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Newsletter\Renderer\Blocks;
if (!defined('ABSPATH')) exit;
use MailPoet\Newsletter\Editor\PostContentManager;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\Util\pQuery\pQuery;
class Text {
public function render($element) {
$html = $element['text'];
// replace &nbsp; with spaces
$html = str_replace('&nbsp;', ' ', $html);
$html = str_replace('\xc2\xa0', ' ', $html);
$html = $this->convertBlockquotesToTables($html);
$html = $this->convertParagraphsToTables($html);
$html = $this->styleLists($html);
$html = $this->styleHeadings($html);
$html = $this->removeLastLineBreak($html);
$template = '
<tr>
<td class="mailpoet_text mailpoet_padded_vertical mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;">
' . $html . '
</td>
</tr>';
return $template;
}
public function convertBlockquotesToTables($html) {
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($html);
$blockquotes = $DOM->query('blockquote');
foreach ($blockquotes as $blockquote) {
$contents = [];
$paragraphs = $blockquote->query('p, h1, h2, h3, h4', 0);
foreach ($paragraphs as $index => $paragraph) {
if (preg_match('/h\d/', $paragraph->getTag())) {
$contents[] = $paragraph->getOuterText();
} else {
$contents[] = $paragraph->toString(true, true, 1);
}
if ($index + 1 < $paragraphs->count()) $contents[] = '<br />';
$paragraph->remove();
}
if (empty($contents)) continue;
$blockquote->setTag('table');
$blockquote->addClass('mailpoet_blockquote');
$blockquote->width = '100%';
$blockquote->spacing = 0;
$blockquote->border = 0;
$blockquote->cellpadding = 0;
$blockquote->html('
<tbody>
<tr>
<td width="2" bgcolor="#565656"></td>
<td width="10"></td>
<td valign="top">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr>
<td class="mailpoet_blockquote">
' . implode('', $contents) . '
</td>
</tr>
</table>
</td>
</tr>
</tbody>');
$blockquote = $this->insertLineBreak($blockquote);
}
return $DOM->__toString();
}
public function convertParagraphsToTables($html) {
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($html);
$paragraphs = $DOM->query('p');
if (!$paragraphs->count()) return $html;
foreach ($paragraphs as $paragraph) {
// process empty paragraphs
if (!trim($paragraph->html())) {
$nextElement = ($paragraph->getNextSibling()) ?
trim($paragraph->getNextSibling()->text()) :
false;
$previousElement = ($paragraph->getPreviousSibling()) ?
trim($paragraph->getPreviousSibling()->text()) :
false;
$previousElementTag = ($previousElement) ?
$paragraph->getPreviousSibling()->tag :
false;
// if previous or next paragraphs are empty OR previous paragraph
// is a heading, insert a break line
if (
!$nextElement ||
!$previousElement ||
(preg_match('/h\d+/', $previousElementTag))
) {
$paragraph = $this->insertLineBreak($paragraph);
}
$paragraph->remove();
continue;
}
$style = (string)$paragraph->style;
if (!preg_match('/text-align/i', $style)) {
$style = 'text-align: left;' . $style;
}
$contents = $paragraph->toString(true, true, 1);
$paragraph->setTag('table');
$paragraph->style = 'border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;';
$paragraph->width = '100%';
$paragraph->cellpadding = 0;
$nextElement = $paragraph->getNextSibling();
// unless this is the last element in column, add double line breaks
$lineBreaks = ($nextElement && !trim($nextElement->text())) ?
'<br /><br />' :
'';
// if this element is followed by a list, add single line break
$lineBreaks = ($nextElement && preg_match('/<li/i', $nextElement->getOuterText())) ?
'<br />' :
$lineBreaks;
if ($paragraph->hasClass(PostContentManager::WP_POST_CLASS)) {
$paragraph->removeClass(PostContentManager::WP_POST_CLASS);
// if this element is followed by a paragraph or heading, add double line breaks
$lineBreaks = ($nextElement && preg_match('/<(p|h[1-6]{1})/i', $nextElement->getOuterText())) ?
'<br /><br />' :
$lineBreaks;
}
$paragraph->html('
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . EHelper::escapeHtmlStyleAttr($style) . '">
' . $contents . $lineBreaks . '
</td>
</tr>');
}
return $DOM->__toString();
}
public function styleLists($html) {
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($html);
$lists = $DOM->query('ol, ul, li');
if (!$lists->count()) return $html;
foreach ($lists as $list) {
if ($list->tag === 'li') {
$list->setInnertext($list->toString(true, true, 1));
$list->class = 'mailpoet_paragraph';
} else {
$list->class = 'mailpoet_paragraph';
$list->style = StylesHelper::joinStyles($list->style, 'padding-top:0;padding-bottom:0;margin-top:10px;');
}
$list->style = StylesHelper::applyTextAlignment($list->style);
$list->style = StylesHelper::joinStyles($list->style, 'margin-bottom:10px;');
$list->style = EHelper::escapeHtmlStyleAttr($list->style);
}
return $DOM->__toString();
}
public function styleHeadings($html) {
$dOMParser = new pQuery();
$DOM = $dOMParser->parseStr($html);
$headings = $DOM->query('h1, h2, h3, h4');
if (!$headings->count()) return $html;
foreach ($headings as $heading) {
$heading->style = StylesHelper::applyTextAlignment($heading->style);
$heading->style = StylesHelper::joinStyles($heading->style, 'padding:0;font-style:normal;font-weight:normal;');
$heading->style = EHelper::escapeHtmlStyleAttr($heading->style);
}
return $DOM->__toString();
}
public function removeLastLineBreak($html) {
return preg_replace('/(^)?(<br[^>]*?\/?>)+$/i', '', $html);
}
public function insertLineBreak($element) {
$element->parent->insertChild(
[
'tag_name' => 'br',
'self_close' => true,
'attributes' => [],
],
$element->index() + 1
);
return $element;
}
}
@@ -0,0 +1 @@
<?php