<?php declare(strict_types = 1);
namespace MailPoet\EmailEditor\Integrations\MailPoet;
if (!defined('ABSPATH')) exit;
use MailPoet\WP\Functions as WPFunctions;
class BlockEmailContentDetector {
private const DYNAMIC_RENDERABLE_BLOCKS = [
'woocommerce/coupon-code' => true,
'woocommerce/product-collection' => true,
'woocommerce/product-image' => true,
'woocommerce/product-price' => true,
'woocommerce/product-button' => true,
];
private const MEDIA_OR_LINK_BLOCKS = [
'core/audio' => true,
'core/button' => true,
'core/cover' => true,
'core/file' => true,
'core/gallery' => true,
'core/image' => true,
'core/media-text' => true,
'core/video' => true,
];
private WPFunctions $wp;
public function __construct(
WPFunctions $wp
) {
$this->wp = $wp;
}
/** @param \WP_Post|string $postOrContent */
public function hasMeaningfulContent($postOrContent): bool {
if ($postOrContent instanceof \WP_Post) {
$content = (string)$postOrContent->post_content; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
} elseif (is_string($postOrContent)) {
$content = $postOrContent;
} else {
return false;
}
if (trim($content) === '') {
return false;
}
if ($this->contentContainsPersonalizationToken($content)) {
return true;
}
$blocks = $this->wp->parseBlocks($content);
if ($this->blocksHaveMeaningfulContent($blocks)) {
return true;
}
return $this->htmlHasVisibleText($content);
}
private function blocksHaveMeaningfulContent(array $blocks, bool $insideProductTemplate = false): bool {
foreach ($blocks as $block) {
if (!is_array($block)) {
continue;
}
if ($this->blockHasMeaningfulContent($block, $insideProductTemplate)) {
return true;
}
}
return false;
}
private function blockHasMeaningfulContent(array $block, bool $insideProductTemplate): bool {
$blockName = isset($block['blockName']) && is_string($block['blockName']) ? $block['blockName'] : null;
$attrs = $this->getBlockAttrs($block);
if ($this->isKnownRenderableDynamicBlock($blockName, $attrs, $insideProductTemplate)) {
return true;
}
if ($this->blockContainsRenderableMediaOrLink($blockName, $block, $attrs)) {
return true;
}
$blockContent = $this->getBlockContent($block);
if ($this->contentContainsPersonalizationToken($blockContent) || $this->htmlHasVisibleText($blockContent)) {
return true;
}
$innerBlocks = isset($block['innerBlocks']) && is_array($block['innerBlocks']) ? $block['innerBlocks'] : [];
return $this->blocksHaveMeaningfulContent($innerBlocks, $insideProductTemplate || $blockName === 'woocommerce/product-template');
}
private function isKnownRenderableDynamicBlock(?string $blockName, array $attrs, bool $insideProductTemplate): bool {
if ($blockName === null) {
return false;
}
if (isset(self::DYNAMIC_RENDERABLE_BLOCKS[$blockName])) {
return true;
}
if (!in_array($blockName, ['core/post-title', 'core/post-content'], true)) {
return false;
}
$woocommerceNamespace = isset($attrs['__woocommerceNamespace']) && is_string($attrs['__woocommerceNamespace'])
? $attrs['__woocommerceNamespace']
: '';
return $insideProductTemplate || strpos($woocommerceNamespace, 'woocommerce/product-collection') === 0;
}
private function blockContainsRenderableMediaOrLink(?string $blockName, array $block, array $attrs): bool {
if ($blockName !== null && isset(self::MEDIA_OR_LINK_BLOCKS[$blockName]) && $this->attrsContainRenderableValue($attrs)) {
return true;
}
$content = strtolower($this->getBlockContent($block));
foreach (['<img', '<picture', '<source', '<video', '<audio'] as $tag) {
if (strpos($content, $tag) !== false) {
return true;
}
}
return strpos($content, '<a ') !== false && strpos($content, 'href=') !== false;
}
private function attrsContainRenderableValue(array $attrs): bool {
foreach (['id', 'url', 'href', 'src', 'mediaId', 'mediaUrl'] as $key) {
if (!empty($attrs[$key])) {
return true;
}
}
foreach ($attrs as $value) {
if (is_string($value) && $this->contentContainsPersonalizationToken($value)) {
return true;
}
if (is_array($value) && $this->attrsContainRenderableValue($value)) {
return true;
}
}
return false;
}
private function htmlHasVisibleText(string $content): bool {
// Remove HTML comments so Gutenberg delimiters do not count as visible content.
$contentWithoutComments = preg_replace('/<!--.*?-->/s', '', $content);
if (!is_string($contentWithoutComments)) {
$contentWithoutComments = $content;
}
return trim($this->wp->wpStripAllTags($contentWithoutComments, true)) !== '';
}
private function contentContainsPersonalizationToken(string $content): bool {
return strpos($content, '[mailpoet/') !== false || strpos($content, '[woocommerce/') !== false;
}
private function getBlockAttrs(array $block): array {
return isset($block['attrs']) && is_array($block['attrs']) ? $block['attrs'] : [];
}
private function getBlockContent(array $block): string {
$content = isset($block['innerHTML']) && is_string($block['innerHTML']) ? $block['innerHTML'] : '';
$innerContent = isset($block['innerContent']) && is_array($block['innerContent']) ? $block['innerContent'] : [];
foreach ($innerContent as $contentPart) {
if (is_string($contentPart)) {
$content .= $contentPart;
}
}
return $content;
}
}