PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / vendor-prefixed / pelago / emogrifier / src / HtmlProcessor / AbstractHtmlProcessor.php
mailpoet / vendor-prefixed / pelago / emogrifier / src / HtmlProcessor Last commit date
AbstractHtmlProcessor.php 2 years ago CssToAttributeConverter.php 2 years ago HtmlNormalizer.php 2 years ago HtmlPruner.php 2 years ago index.php 2 years ago
AbstractHtmlProcessor.php
196 lines
1 <?php
2 declare (strict_types=1);
3 namespace MailPoetVendor\Pelago\Emogrifier\HtmlProcessor;
4 if (!defined('ABSPATH')) exit;
5 abstract class AbstractHtmlProcessor
6 {
7 protected const DEFAULT_DOCUMENT_TYPE = '<!DOCTYPE html>';
8 protected const CONTENT_TYPE_META_TAG = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
9 protected const PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER = '(?:command|embed|keygen|source|track|wbr)';
10 protected const TAGNAME_ALLOWED_BEFORE_BODY_MATCHER = '(?:html|head|base|command|link|meta|noscript|script|style|template|title)';
11 protected const HTML_COMMENT_PATTERN = '/<!--[^-]*+(?:-(?!->)[^-]*+)*+(?:-->|$)/';
12 protected const HTML_TEMPLATE_ELEMENT_PATTERN = '%<template[\\s>][^<]*+(?:<(?!/template>)[^<]*+)*+(?:</template>|$)%i';
13 protected $domDocument = null;
14 private $xPath = null;
15 private function __construct()
16 {
17 }
18 public static function fromHtml(string $unprocessedHtml) : self
19 {
20 if ($unprocessedHtml === '') {
21 throw new \InvalidArgumentException('The provided HTML must not be empty.', 1515763647);
22 }
23 $instance = new static();
24 $instance->setHtml($unprocessedHtml);
25 return $instance;
26 }
27 public static function fromDomDocument(\DOMDocument $document) : self
28 {
29 $instance = new static();
30 $instance->setDomDocument($document);
31 return $instance;
32 }
33 private function setHtml(string $html) : void
34 {
35 $this->createUnifiedDomDocument($html);
36 }
37 public function getDomDocument() : \DOMDocument
38 {
39 if (!$this->domDocument instanceof \DOMDocument) {
40 $message = self::class . '::setDomDocument() has not yet been called on ' . static::class;
41 throw new \UnexpectedValueException($message, 1570472239);
42 }
43 return $this->domDocument;
44 }
45 private function setDomDocument(\DOMDocument $domDocument) : void
46 {
47 $this->domDocument = $domDocument;
48 $this->xPath = new \DOMXPath($this->domDocument);
49 }
50 protected function getXPath() : \DOMXPath
51 {
52 if (!$this->xPath instanceof \DOMXPath) {
53 $message = self::class . '::setDomDocument() has not yet been called on ' . static::class;
54 throw new \UnexpectedValueException($message, 1617819086);
55 }
56 return $this->xPath;
57 }
58 public function render() : string
59 {
60 $htmlWithPossibleErroneousClosingTags = $this->getDomDocument()->saveHTML();
61 return $this->removeSelfClosingTagsClosingTags($htmlWithPossibleErroneousClosingTags);
62 }
63 public function renderBodyContent() : string
64 {
65 $htmlWithPossibleErroneousClosingTags = $this->getDomDocument()->saveHTML($this->getBodyElement());
66 $bodyNodeHtml = $this->removeSelfClosingTagsClosingTags($htmlWithPossibleErroneousClosingTags);
67 return \preg_replace('%</?+body(?:\\s[^>]*+)?+>%', '', $bodyNodeHtml);
68 }
69 private function removeSelfClosingTagsClosingTags(string $html) : string
70 {
71 return \preg_replace('%</' . self::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '>%', '', $html);
72 }
73 private function getBodyElement() : \DOMElement
74 {
75 $node = $this->getDomDocument()->getElementsByTagName('body')->item(0);
76 if (!$node instanceof \DOMElement) {
77 throw new \RuntimeException('There is no body element.', 1617922607);
78 }
79 return $node;
80 }
81 private function createUnifiedDomDocument(string $html) : void
82 {
83 $this->createRawDomDocument($html);
84 $this->ensureExistenceOfBodyElement();
85 }
86 private function createRawDomDocument(string $html) : void
87 {
88 $domDocument = new \DOMDocument();
89 $domDocument->strictErrorChecking = \false;
90 $domDocument->formatOutput = \false;
91 $libXmlState = \libxml_use_internal_errors(\true);
92 $domDocument->loadHTML($this->prepareHtmlForDomConversion($html));
93 \libxml_clear_errors();
94 \libxml_use_internal_errors($libXmlState);
95 $this->setDomDocument($domDocument);
96 }
97 private function prepareHtmlForDomConversion(string $html) : string
98 {
99 $htmlWithSelfClosingSlashes = $this->ensurePhpUnrecognizedSelfClosingTagsAreXml($html);
100 $htmlWithDocumentType = $this->ensureDocumentType($htmlWithSelfClosingSlashes);
101 return $this->addContentTypeMetaTag($htmlWithDocumentType);
102 }
103 private function ensureDocumentType(string $html) : string
104 {
105 $hasDocumentType = \stripos($html, '<!DOCTYPE') !== \false;
106 if ($hasDocumentType) {
107 return $this->normalizeDocumentType($html);
108 }
109 return self::DEFAULT_DOCUMENT_TYPE . $html;
110 }
111 private function normalizeDocumentType(string $html) : string
112 {
113 // Limit to replacing the first occurrence: as an optimization; and in case an example exists as unescaped text.
114 return \preg_replace('/<!DOCTYPE\\s++html(?=[\\s>])/i', '<!DOCTYPE html', $html, 1);
115 }
116 private function addContentTypeMetaTag(string $html) : string
117 {
118 if ($this->hasContentTypeMetaTagInHead($html)) {
119 return $html;
120 }
121 // We are trying to insert the meta tag to the right spot in the DOM.
122 // If we just prepended it to the HTML, we would lose attributes set to the HTML tag.
123 $hasHeadTag = \preg_match('/<head[\\s>]/i', $html);
124 $hasHtmlTag = \stripos($html, '<html') !== \false;
125 if ($hasHeadTag) {
126 $reworkedHtml = \preg_replace('/<head(?=[\\s>])([^>]*+)>/i', '<head$1>' . self::CONTENT_TYPE_META_TAG, $html);
127 } elseif ($hasHtmlTag) {
128 $reworkedHtml = \preg_replace('/<html(.*?)>/is', '<html$1><head>' . self::CONTENT_TYPE_META_TAG . '</head>', $html);
129 } else {
130 $reworkedHtml = self::CONTENT_TYPE_META_TAG . $html;
131 }
132 return $reworkedHtml;
133 }
134 private function hasContentTypeMetaTagInHead(string $html) : bool
135 {
136 \preg_match('%^.*?(?=<meta(?=\\s)[^>]*\\shttp-equiv=(["\']?+)Content-Type\\g{-1}[\\s/>])%is', $html, $matches);
137 if (isset($matches[0])) {
138 $htmlBefore = $matches[0];
139 try {
140 $hasContentTypeMetaTagInHead = !$this->hasEndOfHeadElement($htmlBefore);
141 } catch (\RuntimeException $exception) {
142 // If something unexpected occurs, assume the `Content-Type` that was found is valid.
143 \trigger_error($exception->getMessage());
144 $hasContentTypeMetaTagInHead = \true;
145 }
146 } else {
147 $hasContentTypeMetaTagInHead = \false;
148 }
149 return $hasContentTypeMetaTagInHead;
150 }
151 private function hasEndOfHeadElement(string $html) : bool
152 {
153 $headEndTagMatchCount = \preg_match('%<(?!' . self::TAGNAME_ALLOWED_BEFORE_BODY_MATCHER . '[\\s/>])\\w|</head>%i', $html);
154 if (\is_int($headEndTagMatchCount) && $headEndTagMatchCount > 0) {
155 // An exception to the implicit end of the `<head>` is any content within a `<template>` element, as well in
156 // comments. As an optimization, this is only checked for if a potential `<head>` end tag is found.
157 $htmlWithoutCommentsOrTemplates = $this->removeHtmlTemplateElements($this->removeHtmlComments($html));
158 $hasEndOfHeadElement = $htmlWithoutCommentsOrTemplates === $html || $this->hasEndOfHeadElement($htmlWithoutCommentsOrTemplates);
159 } else {
160 $hasEndOfHeadElement = \false;
161 }
162 return $hasEndOfHeadElement;
163 }
164 private function removeHtmlComments(string $html) : string
165 {
166 $result = \preg_replace(self::HTML_COMMENT_PATTERN, '', $html);
167 if (!\is_string($result)) {
168 throw new \RuntimeException('Internal PCRE error', 1616521475);
169 }
170 return $result;
171 }
172 private function removeHtmlTemplateElements(string $html) : string
173 {
174 $result = \preg_replace(self::HTML_TEMPLATE_ELEMENT_PATTERN, '', $html);
175 if (!\is_string($result)) {
176 throw new \RuntimeException('Internal PCRE error', 1616519652);
177 }
178 return $result;
179 }
180 private function ensurePhpUnrecognizedSelfClosingTagsAreXml(string $html) : string
181 {
182 return \preg_replace('%<' . self::PHP_UNRECOGNIZED_VOID_TAGNAME_MATCHER . '\\b[^>]*+(?<!/)(?=>)%', '$0/', $html);
183 }
184 private function ensureExistenceOfBodyElement() : void
185 {
186 if ($this->getDomDocument()->getElementsByTagName('body')->item(0) instanceof \DOMElement) {
187 return;
188 }
189 $htmlElement = $this->getDomDocument()->getElementsByTagName('html')->item(0);
190 if (!$htmlElement instanceof \DOMElement) {
191 throw new \UnexpectedValueException('There is no HTML element although there should be one.', 1569930853);
192 }
193 $htmlElement->appendChild($this->getDomDocument()->createElement('body'));
194 }
195 }
196