PluginProbe
Linguise – AI Automatic Multilingual Translation / trunk
Linguise – AI Automatic Multilingual Translation vtrunk
2.2.64 2.2.63 2.2.62 2.2.61 2.2.60 2.2.59 2.2.58 2.2.57 2.2.56 2.2.55 2.2.54 2.2.53 2.2.52 2.2.51 2.2.50 2.2.49 2.2.47 2.2.48 2.2.46 2.2.45 2.2.44 2.2.43 2.2.42 1.9.7 1.9.8 All 255 releases
linguise / src / HTMLHelper.php

HTMLHelper.php in Linguise – AI Automatic Multilingual Translation trunk, at src/HTMLHelper.php

243 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Linguise\WordPress;
4
5 defined('ABSPATH') || die('');
6
7 /**
8 * A collection of HTML helper functions used by Linguise
9 *
10 * Mostly used to parse HTML content and protect HTML entities
11 * for processing by the Fragment handler system
12 */
13 class HTMLHelper
14 {
15 /**
16 * Marker used to protect the HTML entities
17 *
18 * @var array
19 */
20 protected static $marker_entity = [
21 'common' => 'linguise-internal-entity',
22 'named' => 'linguise-internal-entity1',
23 'numeric' => 'linguise-internal-entity2',
24 ];
25
26 /**
27 * Protect the HTML entities in the source code.
28 *
29 * Adapted from: https://github.com/ivopetkov/html5-dom-document-php/blob/master/src/HTML5DOMDocument.php
30 *
31 * @param string $source The source code to be protected
32 *
33 * @return string The protected source code
34 */
35 public static function protectEntity($source)
36 {
37 // Replace the entity with our own
38 $source = preg_replace('/&([a-zA-Z]*);/', self::$marker_entity['named'] . '-$1-end', $source);
39 $source = preg_replace('/&#([0-9]*);/', self::$marker_entity['numeric'] . '-$1-end', $source);
40
41 return $source;
42 }
43
44 /**
45 * Unprotect the HTML entities in the source code.
46 *
47 * @param string $html The HTML code to be unprotected
48 *
49 * @return string The unprotected HTML code
50 */
51 public static function unprotectEntity($html)
52 {
53 if (strpos($html, self::$marker_entity['common']) !== false) {
54 $html = preg_replace('/' . self::$marker_entity['named'] . '-(.*?)-end/', '&$1;', $html);
55 $html = preg_replace('/' . self::$marker_entity['numeric'] . '-(.*?)-end/', '&#$1;', $html);
56 }
57
58 return $html;
59 }
60
61
62 /**
63 * Protect the HTML string before processing with DOMDocument.
64 *
65 * It does:
66 * - Add CDATA around script tags content
67 * - Preserve html entities
68 *
69 * Adapted from: https://github.com/ivopetkov/html5-dom-document-php/blob/master/src/HTML5DOMDocument.php
70 *
71 * @param string $source The HTML source code to be protected
72 *
73 * @return string The protected HTML source code
74 */
75 private static function protectHTML($source)
76 {
77 // Add CDATA around script tags content
78 $matches = null;
79 preg_match_all('/<script(.*?)>/', $source, $matches);
80 if (isset($matches[0])) {
81 $matches[0] = array_unique($matches[0]);
82 foreach ($matches[0] as $match) {
83 if (substr($match, -2, 1) !== '/') { // check if ends with />
84 $source = str_replace($match, $match . '<![CDATA[-linguise-dom-internal-cdata', $source); // Add CDATA after the open tag
85 }
86 }
87 }
88
89 $source = str_replace('</script>', '-linguise-dom-internal-cdata]]></script>', $source); // Add CDATA before the end tag
90 $source = str_replace('<![CDATA[-linguise-dom-internal-cdata-linguise-dom-internal-cdata]]>', '', $source); // Clean empty script tags
91 $matches = null;
92 preg_match_all('/\<!\[CDATA\[-linguise-dom-internal-cdata.*?-linguise-dom-internal-cdata\]\]>/s', $source, $matches);
93 if (isset($matches[0])) {
94 $matches[0] = array_unique($matches[0]);
95 foreach ($matches[0] as $match) {
96 if (strpos($match, '</') !== false) { // check if contains </
97 $source = str_replace($match, str_replace('</', '<-linguise-dom-internal-cdata-endtagfix/', $match), $source);
98 }
99 }
100 }
101
102 // Preserve html entities
103 $source = self::protectEntity($source);
104
105 return $source;
106 }
107
108
109 /**
110 * Unclobber the CDATA internal
111 *
112 * NOTE: Only use this when processing a script content internal data not the whole HTML data.
113 *
114 * This is used to protect the CDATA internal data from being mangled by the DOMDocument.
115 *
116 * @param string $html_data The HTML data to be unclobbered
117 *
118 * @return string The unclobbered HTML data
119 */
120 public static function unclobberCdataInternal($html_data)
121 {
122 // Unclobber the CDATA internal
123 $html_data = str_replace('<![CDATA[-linguise-dom-internal-cdata', '', $html_data);
124 $html_data = str_replace('-linguise-dom-internal-cdata]]>', '', $html_data);
125 $html_data = str_replace('<-linguise-dom-internal-cdata-endtagfix/', '</', $html_data);
126
127 return $html_data;
128 }
129
130 /**
131 * Clobber back the CDATA internal
132 *
133 * NOTE: Only use this when processing a script content internal data not the whole HTML data.
134 *
135 * This is used to protect the CDATA internal data from being mangled by the DOMDocument.
136 *
137 * @param string $html_data The HTML data to be clobbered
138 *
139 * @return string The clobbered HTML data
140 */
141 public static function clobberCdataInternal($html_data)
142 {
143 // Append prefix and suffix
144 return '<![CDATA[-linguise-dom-internal-cdata' . $html_data . '-linguise-dom-internal-cdata]]>';
145 }
146
147 /**
148 * Load the HTML data into a DOMDocument object.
149 *
150 * @param string $html_data The HTML data to be loaded
151 *
152 * @return \DOMDocument|null The loaded HTML DOM object
153 */
154 public static function loadHTML($html_data)
155 {
156 // Check if DOMDocument is available or xml extension is loaded
157 if (!class_exists('\\DOMDocument') || !extension_loaded('xml')) {
158 // @codeCoverageIgnoreStart
159 return null;
160 // @codeCoverageIgnoreEnd
161 }
162 if (empty($html_data)) {
163 return null;
164 }
165
166 // Load HTML
167 $html_dom = new \DOMDocument();
168 @$html_dom->loadHTML(self::protectHTML($html_data), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
169
170 /**
171 * Avoid mangling the CSS and JS code with encoded HTML entities
172 *
173 * See: https://www.php.net/manual/en/domdocument.savehtml.php#119813
174 *
175 * While testing, we found this issue with css inner text got weirdly mangled
176 * following the issue above, I manage to correct this by adding proper content-type equiv
177 * which is really weird honestly but it manages to fix the issue.
178 */
179 $has_utf8 = false;
180 $meta_attrs = $html_dom->getElementsByTagName('meta');
181 foreach ($meta_attrs as $meta) {
182 if ($meta->hasAttribute('http-equiv') && strtolower($meta->getAttribute('http-equiv')) === 'content-type') {
183 // force UTF-8s
184 $meta->setAttribute('content', 'text/html; charset=UTF-8');
185 $has_utf8 = true;
186 break;
187 }
188 }
189
190 if (!$has_utf8) {
191 // We didn't found any meta tag with content-type equiv, add our own
192 $meta = $html_dom->createElement('meta');
193 $meta->setAttribute('http-equiv', 'Content-Type');
194 $meta->setAttribute('content', 'text/html; charset=UTF-8');
195 $head_doc = $html_dom->getElementsByTagName('head');
196 if ($head_doc->length > 0) {
197 // Add to head tag on the child as the first node
198 $head = $head_doc->item(0);
199 @$head->insertBefore($meta, $head->firstChild); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore any errors for now
200 }
201 }
202
203 return $html_dom;
204 }
205
206 /**
207 * Save the HTML data into a string.
208 *
209 * @param \DOMDocument $dom The DOMDocument object to be saved
210 *
211 * @return string The saved HTML data
212 */
213 public static function saveHTML($dom)
214 {
215 // Save HTML
216 $html_data = $dom->saveHTML();
217 if ($html_data === false) {
218 // @codeCoverageIgnoreStart
219 return '';
220 // @codeCoverageIgnoreEnd
221 }
222
223 // Unprotect HTML entities
224 $html_data = self::unprotectEntity($html_data);
225
226 // Unprotect HTML
227 $code_to_be_removed = [
228 'linguise-dom-internal-content',
229 '<![CDATA[-linguise-dom-internal-cdata',
230 '-linguise-dom-internal-cdata]]>',
231 '-linguise-dom-internal-cdata-endtagfix'
232 ];
233 foreach ($code_to_be_removed as $code) {
234 $html_data = str_replace($code, '', $html_data);
235 }
236
237 // Unmangle stuff like &amp;#xE5;
238 $html_data = preg_replace('/&amp;#x([0-9A-Fa-f]+);/', '&#x$1;', $html_data);
239
240 return $html_data;
241 }
242 }
243