init
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class LayoutHelper {
|
||||
public static function row($blocks, $styles = []) {
|
||||
if (empty($styles['backgroundColor'])) {
|
||||
$styles['backgroundColor'] = 'transparent';
|
||||
}
|
||||
return [
|
||||
'type' => 'container',
|
||||
'orientation' => 'horizontal',
|
||||
'styles' => ['block' => $styles],
|
||||
'blocks' => $blocks,
|
||||
];
|
||||
}
|
||||
|
||||
public static function col($blocks, $styles = []) {
|
||||
if (empty($styles['backgroundColor'])) {
|
||||
$styles['backgroundColor'] = 'transparent';
|
||||
}
|
||||
return [
|
||||
'type' => 'container',
|
||||
'orientation' => 'vertical',
|
||||
'styles' => ['block' => $styles],
|
||||
'blocks' => $blocks,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class MetaInformationManager {
|
||||
public function appendMetaInformation($content, $post, $args) {
|
||||
// Append author and categories above and below contents
|
||||
foreach (['above', 'below'] as $position) {
|
||||
$positionField = $position . 'Text';
|
||||
$text = [];
|
||||
|
||||
if (isset($args['showAuthor']) && $args['showAuthor'] === $positionField) {
|
||||
$text[] = self::getPostAuthor(
|
||||
$post->post_author, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$args['authorPrecededBy']
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($args['showCategories']) && $args['showCategories'] === $positionField) {
|
||||
$text[] = self::getPostCategories(
|
||||
$post->ID,
|
||||
$post->post_type, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$args['categoriesPrecededBy']
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($text)) {
|
||||
$text = '<p>' . implode('<br />', $text) . '</p>';
|
||||
if ($position === 'above') $content = $text . $content;
|
||||
else if ($position === 'below') $content .= $text;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private static function getPostCategories($postId, $postType, $precededBy) {
|
||||
$precededBy = trim($precededBy);
|
||||
|
||||
// Get categories
|
||||
$categories = WPFunctions::get()->wpGetPostTerms(
|
||||
$postId,
|
||||
['category'],
|
||||
['fields' => 'names']
|
||||
);
|
||||
if (!empty($categories)) {
|
||||
// check if the user specified a label to be displayed before the author's name
|
||||
if (strlen($precededBy) > 0) {
|
||||
$content = stripslashes($precededBy) . ' ';
|
||||
} else {
|
||||
$content = '';
|
||||
}
|
||||
|
||||
return $content . join(', ', $categories);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private static function getPostAuthor($authorId, $precededBy) {
|
||||
$authorName = WPFunctions::get()->getTheAuthorMeta('display_name', (int)$authorId);
|
||||
|
||||
$precededBy = trim($precededBy);
|
||||
if (strlen($precededBy) > 0) {
|
||||
$authorName = stripslashes($precededBy) . ' ' . $authorName;
|
||||
}
|
||||
|
||||
return $authorName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Util\pQuery\pQuery;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class PostContentManager {
|
||||
const WP_POST_CLASS = 'mailpoet_wp_post';
|
||||
|
||||
public $maxExcerptLength = 60;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $woocommerceHelper;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
WooCommerceHelper $woocommerceHelper = null
|
||||
) {
|
||||
$this->wp = new WPFunctions;
|
||||
$this->maxExcerptLength = $this->wp->applyFilters('mailpoet_newsletter_post_excerpt_length', $this->maxExcerptLength);
|
||||
$this->woocommerceHelper = $woocommerceHelper ?: new WooCommerceHelper($this->wp);
|
||||
}
|
||||
|
||||
public function getContent($post, $displayType) {
|
||||
if ($displayType === 'titleOnly') {
|
||||
return '';
|
||||
}
|
||||
if ($this->woocommerceHelper->isWooCommerceActive() && $this->wp->getPostType($post) === 'product') {
|
||||
$product = $this->woocommerceHelper->wcGetProduct($post->ID);
|
||||
if ($product) {
|
||||
return $this->getContentForProduct($product, $displayType);
|
||||
}
|
||||
}
|
||||
if ($displayType === 'excerpt') {
|
||||
if ($this->wp->hasExcerpt($post)) {
|
||||
return self::stripShortCodes($this->wp->getTheExcerpt($post));
|
||||
}
|
||||
return $this->generateExcerpt($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
return self::stripShortCodes($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
|
||||
public function filterContent($content, $displayType, $withPostClass = true) {
|
||||
$content = self::convertEmbeddedContent($content);
|
||||
|
||||
// convert h4 h5 h6 to h3
|
||||
$content = preg_replace('/<([\/])?h[456](.*?)>/', '<$1h3$2>', $content);
|
||||
|
||||
// convert currency signs
|
||||
$content = str_replace(
|
||||
['$', '€', '£', '¥'],
|
||||
['$', '€', '£', '¥'],
|
||||
$content
|
||||
);
|
||||
|
||||
// strip useless tags
|
||||
$tagsNotBeingStripped = [
|
||||
'<p>', '<em>', '<span>', '<b>', '<strong>', '<i>',
|
||||
'<a>', '<ul>', '<ol>', '<li>', '<br>', '<blockquote>',
|
||||
];
|
||||
if ($displayType === 'full') {
|
||||
$tagsNotBeingStripped = array_merge($tagsNotBeingStripped, ['<figure>', '<img>', '<h1>', '<h2>', '<h3>', '<hr>']);
|
||||
}
|
||||
|
||||
if (is_array($content)) {
|
||||
$content = implode(' ', $content);
|
||||
}
|
||||
|
||||
$content = strip_tags($content, implode('', $tagsNotBeingStripped));
|
||||
if ($withPostClass) {
|
||||
$dOMParser = new pQuery();
|
||||
$DOM = $dOMParser->parseStr(WPFunctions::get()->wpautop($content));
|
||||
$paragraphs = $DOM->query('p');
|
||||
foreach ($paragraphs as $paragraph) {
|
||||
// We replace the class attribute to avoid conflicts in the newsletter editor
|
||||
$paragraph->removeAttr('class');
|
||||
$paragraph->addClass(self::WP_POST_CLASS);
|
||||
}
|
||||
$content = $DOM->__toString();
|
||||
} else {
|
||||
$content = WPFunctions::get()->wpautop($content);
|
||||
}
|
||||
$content = trim($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getContentForProduct($product, $displayType) {
|
||||
if ($displayType === 'excerpt') {
|
||||
return $product->get_short_description();
|
||||
}
|
||||
return $product->get_description();
|
||||
}
|
||||
|
||||
private function generateExcerpt($content) {
|
||||
// remove image captions in gutenberg
|
||||
$content = preg_replace(
|
||||
"/<figcaption.*?>.*?<\/figcaption>/",
|
||||
'',
|
||||
$content
|
||||
);
|
||||
// remove image captions in classic posts
|
||||
$content = preg_replace(
|
||||
"/\[caption.*?\](.*?)\[\/caption\]/",
|
||||
'',
|
||||
$content
|
||||
);
|
||||
|
||||
$content = self::stripShortCodes($content);
|
||||
|
||||
// if excerpt is empty then try to find the "more" tag
|
||||
$excerpts = explode('<!--more-->', $content);
|
||||
if (count($excerpts) > 1) {
|
||||
// <!--more--> separator was present
|
||||
return $excerpts[0];
|
||||
} else {
|
||||
// Separator not present, try to shorten long posts
|
||||
return WPFunctions::get()->wpTrimWords($content, $this->maxExcerptLength, ' …');
|
||||
}
|
||||
}
|
||||
|
||||
private function stripShortCodes($content) {
|
||||
// remove captions
|
||||
$content = preg_replace(
|
||||
"/\[caption.*?\](.*<\/a>)(.*?)\[\/caption\]/",
|
||||
'$1',
|
||||
$content
|
||||
);
|
||||
|
||||
// remove other shortcodes
|
||||
$content = preg_replace('/\[[^\[\]]*\]/', '', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function convertEmbeddedContent($content = '') {
|
||||
// remove embedded video and replace with links
|
||||
$content = preg_replace(
|
||||
'#<iframe.*?src=\"(.+?)\".*><\/iframe>#',
|
||||
'<a href="$1">' . __('Click here to view media.', 'mailpoet') . '</a>',
|
||||
$content
|
||||
);
|
||||
|
||||
// replace youtube links
|
||||
$content = preg_replace(
|
||||
'#http://www.youtube.com/embed/([a-zA-Z0-9_-]*)#Ui',
|
||||
'http://www.youtube.com/watch?v=$1',
|
||||
$content
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class PostListTransformer {
|
||||
|
||||
private $args;
|
||||
private $transformer;
|
||||
|
||||
public function __construct(
|
||||
$args
|
||||
) {
|
||||
$this->args = $args;
|
||||
$this->transformer = new PostTransformer($args);
|
||||
}
|
||||
|
||||
public function transform($posts) {
|
||||
$results = [];
|
||||
$useDivider = filter_var($this->args['showDivider'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
foreach ($posts as $index => $post) {
|
||||
if ($useDivider && $index > 0) {
|
||||
$results[] = $this->transformer->getDivider();
|
||||
}
|
||||
|
||||
$results = array_merge($results, $this->transformer->transform($post));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class PostTransformer {
|
||||
/** @var PostTransformerContentsExtractor */
|
||||
private $extractor;
|
||||
|
||||
/** @var array */
|
||||
private $args;
|
||||
/** @var bool */
|
||||
private $withLayout;
|
||||
/** @var string */
|
||||
private $imagePosition;
|
||||
|
||||
public function __construct(
|
||||
$args,
|
||||
PostTransformerContentsExtractor $extractor = null
|
||||
) {
|
||||
$this->args = $args;
|
||||
$this->withLayout = isset($args['withLayout']) ? (bool)filter_var($args['withLayout'], FILTER_VALIDATE_BOOLEAN) : false;
|
||||
$this->imagePosition = 'left';
|
||||
if ($extractor === null) {
|
||||
$extractor = new PostTransformerContentsExtractor($args);
|
||||
}
|
||||
$this->extractor = $extractor;
|
||||
}
|
||||
|
||||
public function getDivider() {
|
||||
if (empty($this->withLayout)) {
|
||||
return $this->args['divider'];
|
||||
}
|
||||
return LayoutHelper::row([
|
||||
LayoutHelper::col([$this->args['divider']]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function transform($post) {
|
||||
if (empty($this->withLayout)) {
|
||||
return $this->getStructure($post);
|
||||
}
|
||||
return $this->getStructureWithLayout($post);
|
||||
}
|
||||
|
||||
private function getStructure($post) {
|
||||
$content = $this->extractor->getContent($post, true, $this->args['displayType']);
|
||||
$title = $this->extractor->getTitle($post);
|
||||
$featuredImage = $this->extractor->getFeaturedImage($post);
|
||||
$featuredImagePosition = $this->getFeaturedImagePosition($this->extractor->isProduct($post));
|
||||
|
||||
if (
|
||||
$featuredImage
|
||||
&& $featuredImagePosition === 'belowTitle'
|
||||
&& (
|
||||
$this->args['displayType'] !== 'titleOnly'
|
||||
|| $this->extractor->isProduct($post)
|
||||
)
|
||||
) {
|
||||
array_unshift($content, $title, $featuredImage);
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ($content[0]['type'] === 'text') {
|
||||
$content[0]['text'] = $title['text'] . $content[0]['text'];
|
||||
} else {
|
||||
array_unshift($content, $title);
|
||||
}
|
||||
|
||||
if ($featuredImage && $this->args['displayType'] !== 'titleOnly') {
|
||||
array_unshift($content, $featuredImage);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getStructureWithLayout($post) {
|
||||
$withPostClass = $this->args['displayType'] === 'full' || $this->args['displayType'] === 'excerpt';
|
||||
$content = $this->extractor->getContent($post, $withPostClass, $this->args['displayType']);
|
||||
$title = $this->extractor->getTitle($post);
|
||||
$featuredImage = $this->extractor->getFeaturedImage($post);
|
||||
$featuredImagePosition = $this->getFeaturedImagePosition($this->extractor->isProduct($post));
|
||||
|
||||
if (
|
||||
!$featuredImage
|
||||
|| $featuredImagePosition === 'none'
|
||||
|| (
|
||||
$this->args['displayType'] === 'titleOnly'
|
||||
&& !$this->extractor->isProduct($post)
|
||||
)
|
||||
) {
|
||||
array_unshift($content, $title);
|
||||
|
||||
return [
|
||||
LayoutHelper::row([
|
||||
LayoutHelper::col($content),
|
||||
]),
|
||||
];
|
||||
}
|
||||
$titlePosition = isset($this->args['titlePosition']) ? $this->args['titlePosition'] : '';
|
||||
|
||||
if ($featuredImagePosition === 'aboveTitle' || $featuredImagePosition === 'belowTitle') {
|
||||
$featuredImagePosition = 'centered';
|
||||
}
|
||||
|
||||
if ($featuredImagePosition === 'centered') {
|
||||
if ($titlePosition === 'aboveExcerpt') {
|
||||
array_unshift($content, $featuredImage, $title);
|
||||
} else {
|
||||
array_unshift($content, $title, $featuredImage);
|
||||
}
|
||||
return [
|
||||
LayoutHelper::row([
|
||||
LayoutHelper::col($content),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
if ($titlePosition === 'aboveExcerpt') {
|
||||
array_unshift($content, $title);
|
||||
}
|
||||
|
||||
if ($featuredImagePosition === 'alternate') {
|
||||
$featuredImagePosition = $this->nextImagePosition();
|
||||
}
|
||||
|
||||
$content = ($featuredImagePosition === 'left')
|
||||
? [
|
||||
LayoutHelper::col([$featuredImage]),
|
||||
LayoutHelper::col($content),
|
||||
]
|
||||
: [
|
||||
LayoutHelper::col($content),
|
||||
LayoutHelper::col([$featuredImage]),
|
||||
];
|
||||
|
||||
$result = [
|
||||
LayoutHelper::row($content),
|
||||
];
|
||||
|
||||
if ($titlePosition !== 'aboveExcerpt') {
|
||||
array_unshift(
|
||||
$result,
|
||||
LayoutHelper::row(
|
||||
[
|
||||
LayoutHelper::col([$title]),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function nextImagePosition() {
|
||||
$this->imagePosition = ($this->imagePosition === 'left') ? 'right' : 'left';
|
||||
return $this->imagePosition;
|
||||
}
|
||||
|
||||
private function getFeaturedImagePosition(bool $isProduct) {
|
||||
if ($this->args['displayType'] !== 'full') {
|
||||
return $this->args['featuredImagePosition'];
|
||||
}
|
||||
|
||||
// For products with display type 'full' use 'featuredImagePosition' if 'fullPostFeaturedImagePosition' not set.
|
||||
// This is because products always supported images, even for 'full' post display type.
|
||||
if ($isProduct && empty($this->args['fullPostFeaturedImagePosition'])) {
|
||||
return $this->args['featuredImagePosition'];
|
||||
}
|
||||
|
||||
// For posts with display type 'full' use 'fullPostFeaturedImagePosition'. This is for back compatibility
|
||||
// with posts that don't have featured image but contain some value for 'featuredImagePosition' in the DB.
|
||||
return $this->args['fullPostFeaturedImagePosition'] ?? 'none';
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class PostTransformerContentsExtractor {
|
||||
|
||||
private $args;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WooCommerceHelper */
|
||||
private $woocommerceHelper;
|
||||
|
||||
public function __construct(
|
||||
$args
|
||||
) {
|
||||
$this->args = $args;
|
||||
$this->wp = new WPFunctions();
|
||||
$this->woocommerceHelper = new WooCommerceHelper($this->wp);
|
||||
}
|
||||
|
||||
public function getContent($post, $withPostClass, $displayType) {
|
||||
$contentManager = new PostContentManager();
|
||||
$metaManager = new MetaInformationManager();
|
||||
|
||||
$content = $contentManager->getContent($post, $this->args['displayType']);
|
||||
$content = $metaManager->appendMetaInformation($content, $post, $this->args);
|
||||
$content = $contentManager->filterContent($content, $displayType, $withPostClass);
|
||||
|
||||
$structureTransformer = new StructureTransformer();
|
||||
$content = $structureTransformer->transform($content, $this->args['imageFullWidth'] === true);
|
||||
|
||||
if ($this->isProduct($post)) {
|
||||
$content = $this->addProductDataToContent($content, $post);
|
||||
}
|
||||
|
||||
$readMoreBtn = $this->getReadMoreButton($post);
|
||||
$blocksCount = count($content);
|
||||
if (!$readMoreBtn) {
|
||||
// Don't attach a button
|
||||
} else if ($readMoreBtn['type'] === 'text' && $blocksCount > 0 && $content[$blocksCount - 1]['type'] === 'text') {
|
||||
$content[$blocksCount - 1]['text'] .= $readMoreBtn['text'];
|
||||
} else {
|
||||
$content[] = $readMoreBtn;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getImageInfo($id) {
|
||||
/*
|
||||
* In some cases wp_get_attachment_image_src ignore the second parameter
|
||||
* and use global variable $content_width value instead.
|
||||
* By overriding it ourselves when ensure a constant behaviour regardless
|
||||
* of the user setup.
|
||||
*
|
||||
* https://mailpoet.atlassian.net/browse/MAILPOET-1365
|
||||
*/
|
||||
global $content_width; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps, default is NULL
|
||||
|
||||
$contentWidthCopy = $content_width; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$content_width = Env::NEWSLETTER_CONTENT_WIDTH; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$imageInfo = $this->wp->wpGetAttachmentImageSrc($id, 'mailpoet_newsletter_max');
|
||||
$content_width = $contentWidthCopy; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
return $imageInfo;
|
||||
}
|
||||
|
||||
public function getFeaturedImage($post) {
|
||||
$postId = $post->ID;
|
||||
$postTitle = $this->sanitizeTitle($post->post_title); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$imageFullWidth = (bool)filter_var($this->args['imageFullWidth'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (!has_post_thumbnail($postId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$thumbnailId = $this->wp->getPostThumbnailId($postId);
|
||||
$imageInfo = $this->getImageInfo($thumbnailId);
|
||||
|
||||
// get alt text
|
||||
$altText = trim(strip_tags(get_post_meta(
|
||||
$thumbnailId,
|
||||
'_wp_attachment_image_alt',
|
||||
true
|
||||
)));
|
||||
if (strlen($altText) === 0) {
|
||||
// if the alt text is empty then use the post title
|
||||
$altText = trim(strip_tags($postTitle));
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'image',
|
||||
'link' => $this->wp->getPermalink($postId),
|
||||
'src' => $imageInfo[0],
|
||||
'alt' => $altText,
|
||||
'fullWidth' => $imageFullWidth,
|
||||
'width' => $imageInfo[1],
|
||||
'height' => $imageInfo[2],
|
||||
'styles' => [
|
||||
'block' => [
|
||||
'textAlign' => 'center',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getReadMoreButton($post) {
|
||||
if ($this->args['readMoreType'] === 'none') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->args['readMoreType'] === 'button') {
|
||||
$button = $this->args['readMoreButton'];
|
||||
$button['url'] = $this->wp->getPermalink($post->ID);
|
||||
return $button;
|
||||
}
|
||||
|
||||
$readMoreText = sprintf(
|
||||
'<p><a href="%s">%s</a></p>',
|
||||
$this->wp->getPermalink($post->ID),
|
||||
$this->args['readMoreText']
|
||||
);
|
||||
|
||||
return [
|
||||
'type' => 'text',
|
||||
'text' => $readMoreText,
|
||||
];
|
||||
}
|
||||
|
||||
public function getTitle($post) {
|
||||
$title = $post->post_title; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
if (filter_var($this->args['titleIsLink'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
$title = '<a href="' . $this->wp->getPermalink($post->ID) . '">' . $title . '</a>';
|
||||
}
|
||||
|
||||
if (in_array($this->args['titleFormat'], ['h1', 'h2', 'h3'])) {
|
||||
$tag = $this->args['titleFormat'];
|
||||
} elseif ($this->args['titleFormat'] === 'ul') {
|
||||
$tag = 'li';
|
||||
} else {
|
||||
$tag = 'h1';
|
||||
}
|
||||
|
||||
$alignment = (in_array($this->args['titleAlignment'], ['left', 'right', 'center'])) ? $this->args['titleAlignment'] : 'left';
|
||||
|
||||
$title = '<' . $tag . ' data-post-id="' . $post->ID . '" style="text-align: ' . $alignment . ';">' . $title . '</' . $tag . '>';
|
||||
|
||||
// The allowed HTML is based on all the possible ways we might construct a $title above
|
||||
$commonAttributes = [
|
||||
'data-post-id' => [],
|
||||
'style' => [],
|
||||
];
|
||||
|
||||
$allowedTitleHtml = [
|
||||
'a' => [
|
||||
'href' => [],
|
||||
],
|
||||
'li' => $commonAttributes,
|
||||
'h1' => $commonAttributes,
|
||||
'h2' => $commonAttributes,
|
||||
'h3' => $commonAttributes,
|
||||
'b' => [],
|
||||
'i' => [],
|
||||
'strong' => [],
|
||||
'em' => [],
|
||||
'small' => [],
|
||||
];
|
||||
|
||||
return [
|
||||
'type' => 'text',
|
||||
'text' => wp_kses($title, $allowedTitleHtml),
|
||||
];
|
||||
}
|
||||
|
||||
private function getPrice($post) {
|
||||
$price = null;
|
||||
$product = null;
|
||||
if ($this->woocommerceHelper->isWooCommerceActive()) {
|
||||
$product = $this->woocommerceHelper->wcGetProduct($post->ID);
|
||||
}
|
||||
if ($product) {
|
||||
$price = '<h2>' . strip_tags($product->get_price_html(), '<span><del>') . '</h2>';
|
||||
}
|
||||
return $price;
|
||||
}
|
||||
|
||||
private function addProductDataToContent($content, $post) {
|
||||
if (!isset($this->args['pricePosition']) || $this->args['pricePosition'] === 'hidden') {
|
||||
return $content;
|
||||
}
|
||||
$price = $this->getPrice($post);
|
||||
$blocksCount = count($content);
|
||||
if ($blocksCount > 0 && $content[$blocksCount - 1]['type'] === 'text') {
|
||||
if ($this->args['pricePosition'] === 'below') {
|
||||
$content[$blocksCount - 1]['text'] = $content[$blocksCount - 1]['text'] . $price;
|
||||
} else {
|
||||
$content[$blocksCount - 1]['text'] = $price . $content[$blocksCount - 1]['text'];
|
||||
}
|
||||
} else {
|
||||
$content[] = [
|
||||
'type' => 'text',
|
||||
'text' => $price,
|
||||
];
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function isProduct($post) {
|
||||
return $post->post_type === 'product'; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces double quote character with a unicode
|
||||
* alternative to avoid problems when inlining CSS.
|
||||
* [MAILPOET-1937]
|
||||
*
|
||||
* @param string $title
|
||||
* @return string
|
||||
*/
|
||||
private function sanitizeTitle($title) {
|
||||
return str_replace('"', '"', $title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Util\DOM as DOMUtil;
|
||||
use MailPoet\Util\pQuery\pQuery;
|
||||
use MailPoetVendor\pQuery\DomNode;
|
||||
|
||||
class StructureTransformer {
|
||||
public function transform($content, $imageFullWidth) {
|
||||
$root = pQuery::parseStr($content);
|
||||
|
||||
$this->hoistImagesToRoot($root);
|
||||
$structure = $this->transformTagsToBlocks($root, $imageFullWidth);
|
||||
$structure = $this->mergeNeighboringBlocks($structure);
|
||||
return $structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hoists images to root level, preserves order by splitting neighboring
|
||||
* elements and inserts tags as children of top ancestor
|
||||
*/
|
||||
protected function hoistImagesToRoot(DomNode $root) {
|
||||
foreach ($root->query('img') as $item) {
|
||||
$topAncestor = DOMUtil::findTopAncestor($item);
|
||||
$offset = $topAncestor->index();
|
||||
|
||||
if ($item->hasParent('a') || $item->hasParent('figure')) {
|
||||
$item = $item->parent;
|
||||
}
|
||||
|
||||
DOMUtil::splitOn($item->getRoot(), $item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms HTML tags into their respective JSON objects,
|
||||
* turns other root children into text blocks
|
||||
*/
|
||||
private function transformTagsToBlocks(DomNode $root, $imageFullWidth) {
|
||||
$children = $this->filterOutFiguresWithoutImages($root->children);
|
||||
return array_map(function($item) use ($imageFullWidth) {
|
||||
if ($this->isImageElement($item)) {
|
||||
$image = $item->tag === 'img' ? $item : $item->query('img')[0];
|
||||
$width = $image->getAttribute('width');
|
||||
$height = $image->getAttribute('height');
|
||||
return [
|
||||
'type' => 'image',
|
||||
'link' => $item->getAttribute('href') ?: '',
|
||||
'src' => $image->getAttribute('src'),
|
||||
'alt' => $image->getAttribute('alt'),
|
||||
'fullWidth' => $imageFullWidth,
|
||||
'width' => $width === null ? 'auto' : $width,
|
||||
'height' => $height === null ? 'auto' : $height,
|
||||
'styles' => [
|
||||
'block' => [
|
||||
'textAlign' => $this->getImageAlignment($image),
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'type' => 'text',
|
||||
'text' => $item->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
}, $children);
|
||||
}
|
||||
|
||||
private function filterOutFiguresWithoutImages(array $items) {
|
||||
$items = array_filter($items, function (DomNode $item) {
|
||||
if ($item->tag === 'figure' && $item->query('img')->count() === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return array_values($items);
|
||||
}
|
||||
|
||||
private function isImageElement(DomNode $item) {
|
||||
return $item->tag === 'img' || (in_array($item->tag, ['a', 'figure'], true) && $item->query('img')->count() > 0);
|
||||
}
|
||||
|
||||
private function getImageAlignment(DomNode $image) {
|
||||
$alignItem = $image->hasParent('figure') ? $image->parent : $image;
|
||||
if ($alignItem->hasClass('aligncenter')) {
|
||||
$align = 'center';
|
||||
} elseif ($alignItem->hasClass('alignleft')) {
|
||||
$align = 'left';
|
||||
} elseif ($alignItem->hasClass('alignright')) {
|
||||
$align = 'right';
|
||||
} else {
|
||||
$align = 'left';
|
||||
}
|
||||
return $align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges neighboring blocks when possible.
|
||||
* E.g. 2 adjacent text blocks may be combined into one.
|
||||
*/
|
||||
private function mergeNeighboringBlocks(array $structure) {
|
||||
$updatedStructure = [];
|
||||
$textAccumulator = '';
|
||||
foreach ($structure as $item) {
|
||||
if ($item['type'] === 'text') {
|
||||
$textAccumulator .= $item['text'];
|
||||
}
|
||||
if ($item['type'] !== 'text') {
|
||||
if (!empty($textAccumulator)) {
|
||||
$updatedStructure[] = [
|
||||
'type' => 'text',
|
||||
'text' => trim($textAccumulator),
|
||||
];
|
||||
$textAccumulator = '';
|
||||
}
|
||||
$updatedStructure[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($textAccumulator)) {
|
||||
$updatedStructure[] = [
|
||||
'type' => 'text',
|
||||
'text' => trim($textAccumulator),
|
||||
];
|
||||
}
|
||||
|
||||
return $updatedStructure;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class TitleListTransformer {
|
||||
|
||||
private $args;
|
||||
|
||||
public function __construct(
|
||||
$args
|
||||
) {
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function transform($posts) {
|
||||
$results = array_map(function($post) {
|
||||
return $this->getPostTitle($post);
|
||||
}, $posts);
|
||||
|
||||
return [
|
||||
$this->wrap([
|
||||
'type' => 'text',
|
||||
'text' => '<ul>' . implode('', $results) . '</ul>',
|
||||
])];
|
||||
}
|
||||
|
||||
private function wrap($block) {
|
||||
return LayoutHelper::row([
|
||||
LayoutHelper::col([$block]),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getPostTitle($post) {
|
||||
$title = $post->post_title; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$alignment = $this->args['titleAlignment'];
|
||||
$alignment = (in_array($alignment, ['left', 'right', 'center'])) ? $alignment : 'left';
|
||||
|
||||
if ($this->args['titleIsLink']) {
|
||||
$title = '<a data-post-id="' . $post->ID . '" href="' . WPFunctions::get()->getPermalink($post->ID) . '">' . $title . '</a>';
|
||||
}
|
||||
|
||||
return '<li style="text-align: ' . $alignment . ';">' . $title . '</li>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Newsletter\Editor;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class Transformer {
|
||||
|
||||
private $transformer;
|
||||
|
||||
public function __construct(
|
||||
$args
|
||||
) {
|
||||
$titleListOnly = $args['displayType'] === 'titleOnly' && $args['titleFormat'] === 'ul';
|
||||
|
||||
if ($titleListOnly) $transformer = new TitleListTransformer($args);
|
||||
else $transformer = new PostListTransformer($args);
|
||||
$this->transformer = $transformer;
|
||||
}
|
||||
|
||||
public function transform($posts) {
|
||||
return $this->transformer->transform($posts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user