index.php 0000644 00000000006 15073230107 0006356 0 ustar 00 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 = '
' . implode('
', $text) . '
';
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;
}
}
PostTransformer.php 0000644 00000012206 15073230107 0010424 0 ustar 00 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';
}
}
LayoutHelper.php 0000644 00000001463 15073230107 0007674 0 ustar 00 '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,
];
}
}
TitleListTransformer.php 0000644 00000002400 15073230107 0011407 0 ustar 00 args = $args;
}
public function transform($posts) {
$results = array_map(function($post) {
return $this->getPostTitle($post);
}, $posts);
return [
$this->wrap([
'type' => 'text',
'text' => '' . implode('', $results) . '
',
])];
}
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 = '' . $title . '';
}
return '' . $title . '';
}
}
PostListTransformer.php 0000644 00000001404 15073230107 0011256 0 ustar 00 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;
}
}
PostTransformerContentsExtractor.php 0000644 00000015753 15073230107 0014050 0 ustar 00 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(
'%s
',
$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 = '' . $title . '';
}
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,
];
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 = '' . strip_tags($product->get_price_html(), '') . '
';
}
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);
}
}
PostContentManager.php 0000644 00000011513 15073230107 0011027 0 ustar 00 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 = [
'', '', '', '', '', '',
'', '', '', '- ', '
', '',
];
if ($displayType === 'full') {
$tagsNotBeingStripped = array_merge($tagsNotBeingStripped, ['', '
', '', '', '', '
']);
}
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>/",
'',
$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('', $content);
if (count($excerpts) > 1) {
// 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>#',
'' . __('Click here to view media.', 'mailpoet') . '',
$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;
}
}
Transformer.php 0000644 00000001161 15073230107 0007554 0 ustar 00 transformer = $transformer;
}
public function transform($posts) {
return $this->transformer->transform($posts);
}
}
StructureTransformer.php 0000644 00000007664 15073230107 0011513 0 ustar 00 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;
}
}