| 1 |
<?php |
| 2 |
|
| 3 |
namespace Linguise\WordPress; |
| 4 |
|
| 5 |
use Linguise\WordPress\FragmentBase; |
| 6 |
use Linguise\WordPress\Helper; |
| 7 |
use Linguise\WordPress\HTMLHelper; |
| 8 |
use Linguise\Vendor\Linguise\Script\Core\Debug; |
| 9 |
|
| 10 |
defined('ABSPATH') || die(''); |
| 11 |
|
| 12 |
require_once __DIR__ . DIRECTORY_SEPARATOR . 'FragmentOverrideHandler.php'; |
| 13 |
require_once __DIR__ . DIRECTORY_SEPARATOR . 'FragmentAutoHandler.php'; |
| 14 |
require_once __DIR__ . DIRECTORY_SEPARATOR . 'FragmentI18nHandler.php'; |
| 15 |
|
| 16 |
/** |
| 17 |
* Class FragmentHandler |
| 18 |
*/ |
| 19 |
class FragmentHandler extends FragmentBase |
| 20 |
{ |
| 21 |
/** |
| 22 |
* Regex/matcher for our custom HTML fragment |
| 23 |
* |
| 24 |
* @var string |
| 25 |
*/ |
| 26 |
protected static $frag_html_match = '/<(div|a|linguise-main|img) class="linguise-fragment" data-fragment-name="([^"]*)" data-fragment-param="([^"]*)" data-fragment-key="([^"]*)" data-fragment-format="((?:link|html|html-main|text|media-img|media-imgset)(?:-skip)?)" data-fragment-mode="(auto|override|skip|i18n|attribute)"(?: data-fragment-extra-id="([^"]*)")?(?: (?:href|src|srcset)="([^"]*)")?(?: linguise-ignore="1")?>(.*?)<\/\1>/si'; |
| 27 |
/** |
| 28 |
* Regex/matcher for our custom HTML fragment |
| 29 |
* |
| 30 |
* This version support self-closing tag, e.g. <img> |
| 31 |
* |
| 32 |
* @var string |
| 33 |
*/ |
| 34 |
protected static $frag_html_match_self_close = '/<(img) class="linguise-fragment" data-fragment-name="([^"]*)" data-fragment-param="([^"]*)" data-fragment-key="([^"]*)" data-fragment-format="((?:link|html|html-main|text|media-img|media-imgset)(?:-skip)?)" data-fragment-mode="(auto|override|skip|i18n|attribute)"(?: data-fragment-extra-id="([^"]*)")?(?: (?:href|src|srcset)="([^"]*)")?(?: linguise-ignore="1")?\s*\/?>/si'; |
| 35 |
|
| 36 |
/** |
| 37 |
* Collect the fragment from the JSON data. |
| 38 |
* |
| 39 |
* @param string $key The key of the fragment |
| 40 |
* @param mixed $value The value of the fragment |
| 41 |
* @param array $collected_fragments The array of collected fragments |
| 42 |
* @param string $current_key The current key of the fragment |
| 43 |
* @param boolean $strict If strict mode, only "allow"-ed fragments will be collected |
| 44 |
* @param integer $array_index The index of the array, if it's an array |
| 45 |
* |
| 46 |
* @return array - The array of collected fragments |
| 47 |
*/ |
| 48 |
private static function collectFragment($key, $value, $collected_fragments, $current_key, $strict = false, $array_index = null) |
| 49 |
{ |
| 50 |
$use_key = self::wrapKey($key); |
| 51 |
if ($array_index !== null) { |
| 52 |
$use_key .= '.' . $array_index; |
| 53 |
} |
| 54 |
|
| 55 |
// Fix FragmentAttribute become invalid if value is array object |
| 56 |
if ($current_key !== null && $current_key !== '') { |
| 57 |
if ($key === $use_key) { |
| 58 |
$use_key = '.' . $use_key; |
| 59 |
} |
| 60 |
|
| 61 |
$use_key = $current_key . $use_key; |
| 62 |
} |
| 63 |
|
| 64 |
if (Helper::isActualObject($value)) { |
| 65 |
$collected_fragments = self::collectFragmentFromJson($value, $strict, $collected_fragments, $use_key); |
| 66 |
} elseif (is_array($value)) { |
| 67 |
for ($arr_i = 0; $arr_i < count($value); $arr_i++) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed |
| 68 |
$inner_value = $value[$arr_i]; |
| 69 |
$collected_fragments = self::collectFragment('', $inner_value, $collected_fragments, $use_key, $strict, $arr_i); |
| 70 |
} |
| 71 |
} elseif (is_string($value)) { |
| 72 |
// By default, we assume "text" for now. |
| 73 |
$allowed_filters = self::isKeyAllowed($key, $use_key); |
| 74 |
if ($strict && ($allowed_filters !== true && !is_string($allowed_filters))) { |
| 75 |
return $collected_fragments; |
| 76 |
} |
| 77 |
if (!is_null($allowed_filters) && $allowed_filters === false) { |
| 78 |
// If it's not null and it's false, then we don't need to check further |
| 79 |
return $collected_fragments; |
| 80 |
} |
| 81 |
$tl_string = self::isTranslatableString($value); |
| 82 |
$tl_link = self::isStringLink($value); |
| 83 |
$tl_dom = self::isHTMLElement($value); |
| 84 |
|
| 85 |
$format = 'text'; |
| 86 |
if ($tl_link) { |
| 87 |
$format = 'link'; |
| 88 |
if (self::isImageLink($value)) { |
| 89 |
$format = 'media-img'; |
| 90 |
} |
| 91 |
} elseif (is_string($tl_dom)) { |
| 92 |
$format = $tl_dom; |
| 93 |
} |
| 94 |
if (is_string($allowed_filters)) { |
| 95 |
$format = $allowed_filters; |
| 96 |
} |
| 97 |
if ($tl_string || $tl_link || is_string($tl_dom) || $allowed_filters) { |
| 98 |
// Extra check |
| 99 |
if (is_string($value) && !empty($value)) { |
| 100 |
$collected_fragments[] = [ |
| 101 |
'key' => $use_key, |
| 102 |
'value' => $value, |
| 103 |
'format' => $format, |
| 104 |
]; |
| 105 |
} |
| 106 |
} |
| 107 |
} |
| 108 |
|
| 109 |
return $collected_fragments; |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* A recursive function that iterates through the json data and collects the fragments |
| 114 |
* |
| 115 |
* @param array $json_data The JSON data to be iterated |
| 116 |
* @param boolean $strict Default to false, if True this will only collect "allow"-ed fragments |
| 117 |
* @param array $collected_fragments Default to empty array, when it's being called inside the function it will be appended for the parent key |
| 118 |
* @param string $current_key Default to empty string, when it's being called inside the function it will be appended for the parent key |
| 119 |
* |
| 120 |
* @return array |
| 121 |
*/ |
| 122 |
public static function collectFragmentFromJson($json_data, $strict = false, $collected_fragments = null, $current_key = null) |
| 123 |
{ |
| 124 |
// set default if null |
| 125 |
if ($collected_fragments === null) { |
| 126 |
$collected_fragments = []; |
| 127 |
} |
| 128 |
if ($current_key === null) { |
| 129 |
$current_key = ''; |
| 130 |
} |
| 131 |
if (empty($json_data) || !is_array($json_data)) { |
| 132 |
return $collected_fragments; |
| 133 |
} |
| 134 |
foreach ($json_data as $key => $value) { |
| 135 |
$collected_fragments = self::collectFragment($key, $value, $collected_fragments, $current_key, $strict); |
| 136 |
} |
| 137 |
return $collected_fragments; |
| 138 |
} |
| 139 |
|
| 140 |
/** |
| 141 |
* Protect sprintf templates in the text by wrapping them in a span tag |
| 142 |
* |
| 143 |
* @param string $text The text to be protected |
| 144 |
* |
| 145 |
* @return string The protected text |
| 146 |
*/ |
| 147 |
private static function protectSprintfTemplates($text) |
| 148 |
{ |
| 149 |
$pattern = '/(%(?:\d+\$)?[+-]?(?:\d+)?(?:\.\d+)?[bcdeEfFgGosuxX])/'; |
| 150 |
$replacement = '<span-ling translate="no">$1</span-ling>'; |
| 151 |
$result = preg_replace($pattern, $replacement, $text); |
| 152 |
if (!empty($result)) { |
| 153 |
return $result; |
| 154 |
} |
| 155 |
return $text; |
| 156 |
} |
| 157 |
|
| 158 |
/** |
| 159 |
* Restore sprintf templates in the text by unwrapping them from the span tag |
| 160 |
* |
| 161 |
* @param string $text The text to be restored |
| 162 |
* |
| 163 |
* @return string The restored text |
| 164 |
*/ |
| 165 |
private static function restoreSprintfTemplates($text) |
| 166 |
{ |
| 167 |
$pattern = '/<span-ling translate="no">(%(?:\d+\$)?[+-]?(?:\d+)?(?:\.\d+)?[bcdeEfFgGosuxX])<\/span-ling>/'; |
| 168 |
$result = preg_replace($pattern, '$1', $text); |
| 169 |
if (!empty($result)) { |
| 170 |
return $result; |
| 171 |
} |
| 172 |
return $text; |
| 173 |
} |
| 174 |
|
| 175 |
/** |
| 176 |
* Convert the array fragments created previously into a HTML |
| 177 |
* data that will be injected into the page. |
| 178 |
* |
| 179 |
* @param string $fragment_name The name of the fragment, e.g. 'woocommerce' |
| 180 |
* @param string $fragment_param The param of the fragment, e.g. 'params' used in the actual variables |
| 181 |
* @param array $json_fragments The array of fragments, generated with collectFragmentFromJson |
| 182 |
* |
| 183 |
* @return string - The HTML data that will be injected into the page |
| 184 |
*/ |
| 185 |
public static function intoHTMLFragments($fragment_name, $fragment_param, $json_fragments) |
| 186 |
{ |
| 187 |
$html = ''; |
| 188 |
foreach ($json_fragments['fragments'] as $fragment) { |
| 189 |
$tag = 'div'; |
| 190 |
if ($fragment['format'] === 'link') { |
| 191 |
$tag = 'a'; |
| 192 |
} |
| 193 |
$frag_value = $fragment['value']; |
| 194 |
if ($fragment['format'] === 'html') { |
| 195 |
// check if html has div, if yes change it to divlinguise |
| 196 |
$frag_value = preg_replace('/<div(.*?)>(.*?)<\/div>$/si', '<linguise-div$1>$2</linguise-div>', $frag_value, 1); |
| 197 |
} |
| 198 |
if ($fragment['format'] === 'html-main') { |
| 199 |
$tag = 'linguise-main'; |
| 200 |
} |
| 201 |
if ($fragment['format'] === 'media-img' || $fragment['format'] === 'media-imgset') { |
| 202 |
$tag = 'img'; |
| 203 |
} |
| 204 |
$frag_format = $fragment['format']; |
| 205 |
if (isset($fragment['skip']) && $fragment['skip']) { |
| 206 |
$frag_format .= '-skip'; |
| 207 |
} |
| 208 |
|
| 209 |
$html .= '<' . $tag . ' class="linguise-fragment" data-fragment-name="' . $fragment_name . '" data-fragment-param="' . $fragment_param . '" data-fragment-key="'; |
| 210 |
$html .= $fragment['key'] . '" data-fragment-format="' . $frag_format . '" data-fragment-mode="' . $json_fragments['mode'] . '"'; |
| 211 |
if ($json_fragments['mode'] === 'attribute' && isset($json_fragments['attribute'])) { |
| 212 |
$html .= ' data-fragment-extra-id="' . $json_fragments['attribute'] . '"'; |
| 213 |
} |
| 214 |
if ($json_fragments['mode'] === 'i18n' && isset($fragment['index'])) { |
| 215 |
$html .= ' data-fragment-extra-id="' . $fragment['index'] . '"'; |
| 216 |
} |
| 217 |
if ($fragment['format'] === 'link') { |
| 218 |
$html .= ' href="' . $fragment['value'] . '">'; |
| 219 |
} elseif ($fragment['format'] === 'media-img' || $fragment['format'] === 'media-imgset') { |
| 220 |
$tag_select = $fragment['format'] === 'media-imgset' ? 'srcset' : 'src'; |
| 221 |
$html .= ' ' . $tag_select . '="' . $fragment['value'] . '">'; |
| 222 |
} else { |
| 223 |
$html .= '>' . self::protectSprintfTemplates($frag_value); |
| 224 |
} |
| 225 |
$html .= '</' . $tag . '>' . "\n"; |
| 226 |
} |
| 227 |
|
| 228 |
return trim($html) . "\n"; |
| 229 |
} |
| 230 |
|
| 231 |
/** |
| 232 |
* Convert back the translated fragments into the original JSON data |
| 233 |
* |
| 234 |
* @param string $html_fragments The HTML fragments that was injected into the page |
| 235 |
* |
| 236 |
* @return array - The array of fragments, generated with collectFragmentFromJson |
| 237 |
*/ |
| 238 |
public static function intoJSONFragments($html_fragments) |
| 239 |
{ |
| 240 |
$fragments = []; |
| 241 |
// Let's just use regex to get anything with "linguise-fragment" class |
| 242 |
preg_match_all(self::$frag_html_match, $html_fragments, $std_matches, PREG_SET_ORDER, 0); |
| 243 |
preg_match_all(self::$frag_html_match_self_close, $html_fragments, $self_close_matches, PREG_SET_ORDER, 0); |
| 244 |
$matches = array_merge($std_matches, $self_close_matches); |
| 245 |
foreach ($matches as $match) { |
| 246 |
$fragment_name = $match[2]; |
| 247 |
$fragment_param = $match[3]; |
| 248 |
$fragment_key = $match[4]; |
| 249 |
$fragment_format = $match[5]; |
| 250 |
$fragment_mode = $match[6]; |
| 251 |
|
| 252 |
$fragment_value = isset($match[9]) ? $match[9] : ''; |
| 253 |
|
| 254 |
$is_skipped = false; |
| 255 |
if (substr($fragment_format, -5) === '-skip') { |
| 256 |
$is_skipped = true; |
| 257 |
$fragment_format = substr($fragment_format, 0, -5); |
| 258 |
} |
| 259 |
|
| 260 |
if ($fragment_format === 'link' || $fragment_format === 'media-img' || $fragment_format === 'media-imgset') { |
| 261 |
$fragment_value = $match[8]; |
| 262 |
} |
| 263 |
|
| 264 |
if ($fragment_format === 'html') { |
| 265 |
// parse back the linguise-dev |
| 266 |
$fragment_value = preg_replace('/<linguise-div(.*?)>(.*?)<\/linguise-div>$/si', '<div$1>$2</div>', $fragment_value, 1); |
| 267 |
} elseif ($fragment_format === 'html-main') { |
| 268 |
// parse back the linguise-main |
| 269 |
$fragment_value = preg_replace('/<linguise-main>(.*?)<\/linguise-main>$/si', '$1', $fragment_value, 1); |
| 270 |
} |
| 271 |
|
| 272 |
// Strip any data-editor="linguise" wrap |
| 273 |
$result_strip = preg_replace('/^\s*<span data-editor="linguise" data-linguise-hash="(?:.*?)">(.*?)<\/span>$/', '$1', $fragment_value, 1); |
| 274 |
if (!empty($result_strip)) { |
| 275 |
$fragment_value = $result_strip; |
| 276 |
} |
| 277 |
|
| 278 |
$fragment_value = self::restoreSprintfTemplates($fragment_value); |
| 279 |
|
| 280 |
if (!isset($fragments[$fragment_name])) { |
| 281 |
$fragments[$fragment_name] = []; |
| 282 |
} |
| 283 |
if (!isset($fragments[$fragment_name][$fragment_param])) { |
| 284 |
$temp_fragment_sets = [ |
| 285 |
'mode' => $fragment_mode, |
| 286 |
'fragments' => [], |
| 287 |
]; |
| 288 |
|
| 289 |
if ($fragment_mode === 'attribute' && isset($match[7]) && !empty($match[7])) { |
| 290 |
$temp_fragment_sets['attribute'] = $match[7]; |
| 291 |
} |
| 292 |
|
| 293 |
$fragments[$fragment_name][$fragment_param] = $temp_fragment_sets; |
| 294 |
} |
| 295 |
|
| 296 |
// The returned data is encoded HTML entities for non-ASCII characters |
| 297 |
// Decode it back to UTF-8 for link and text, for HTML since it would be rendered |
| 298 |
// the browser will decode it back to UTF-8 automatically |
| 299 |
if ($fragment_format !== 'html' && $fragment_format !== 'html-main') { |
| 300 |
$fragment_value = html_entity_decode($fragment_value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); |
| 301 |
} |
| 302 |
|
| 303 |
$fragment_index = null; |
| 304 |
if ($fragment_mode === 'i18n' && isset($match[7]) && is_numeric($match[7])) { |
| 305 |
$fragment_index = (int)$match[7]; |
| 306 |
} |
| 307 |
|
| 308 |
// make it into list for each fragment name |
| 309 |
$fragments[$fragment_name][$fragment_param]['fragments'][] = [ |
| 310 |
'key' => $fragment_key, |
| 311 |
'value' => $fragment_value, |
| 312 |
'format' => $fragment_format, |
| 313 |
'match' => $match[0], |
| 314 |
'index' => $fragment_index, |
| 315 |
'skip' => $is_skipped, // If `skip` is true, then don't replace this fragment (but remove it) |
| 316 |
]; |
| 317 |
} |
| 318 |
|
| 319 |
Debug::log('FragmentHandler -> Dump fragments: ' . json_encode($fragments, JSON_PRETTY_PRINT)); |
| 320 |
|
| 321 |
return $fragments; |
| 322 |
} |
| 323 |
|
| 324 |
/** |
| 325 |
* === HTML Injector Related === |
| 326 |
*/ |
| 327 |
|
| 328 |
/** |
| 329 |
* Try to match the script with the override list. |
| 330 |
* |
| 331 |
* @param \DOMNode|\DOMElement $script The script to be matched |
| 332 |
* @param string $html_data The raw HTML data to be checked |
| 333 |
* |
| 334 |
* @return array|null |
| 335 |
*/ |
| 336 |
private static function tryMatchWithOverride($script, $html_data) |
| 337 |
{ |
| 338 |
return FragmentOverrideHandler::tryMatchWithOverride($script, $html_data); |
| 339 |
} |
| 340 |
|
| 341 |
/** |
| 342 |
* Parse the translation block from the script tag. |
| 343 |
* |
| 344 |
* @param \DOMNode|\DOMElement $script The script to be matched |
| 345 |
* |
| 346 |
* @return array|null |
| 347 |
*/ |
| 348 |
public static function tryMatchTranslationBlock($script) |
| 349 |
{ |
| 350 |
return FragmentI18nHandler::tryMatchTranslationBlock($script); |
| 351 |
} |
| 352 |
|
| 353 |
/** |
| 354 |
* Parse the HTML input or data into the fragments. |
| 355 |
* XXX: Maybe normal regex and not DOMDocument if possible. |
| 356 |
* |
| 357 |
* @param string $html_data The HTML data to be parsed |
| 358 |
* |
| 359 |
* @return array |
| 360 |
*/ |
| 361 |
public static function findWPFragments(&$html_data) |
| 362 |
{ |
| 363 |
$html_dom = HTMLHelper::loadHTML($html_data); |
| 364 |
if (empty($html_dom)) { |
| 365 |
return []; |
| 366 |
} |
| 367 |
|
| 368 |
$scripts = $html_dom->getElementsByTagName('script'); |
| 369 |
|
| 370 |
$all_fragments = []; |
| 371 |
foreach ($scripts as $script) { |
| 372 |
$attr_id = $script->getAttribute('id'); |
| 373 |
$match_res = preg_match('/^(.*)-js-extra$/', $attr_id, $attr_match); |
| 374 |
if ($match_res === false || $match_res === 0) { |
| 375 |
$overridden_temp = self::tryMatchWithOverride($script, $html_data); |
| 376 |
if (is_array($overridden_temp)) { |
| 377 |
foreach ($overridden_temp as $overridden_temp_item) { |
| 378 |
$all_fragments[$overridden_temp_item['name']][$overridden_temp_item['name']] = [ |
| 379 |
'mode' => 'override', |
| 380 |
'fragments' => $overridden_temp_item['fragments'], |
| 381 |
]; |
| 382 |
} |
| 383 |
|
| 384 |
continue; |
| 385 |
} |
| 386 |
|
| 387 |
// Try matching -js-translations |
| 388 |
$trans_match_res = preg_match('/^(.*)-js-translations$/', $attr_id, $trans_attr_match); |
| 389 |
if ($trans_match_res !== false && $trans_match_res !== 0) { |
| 390 |
$translation_block = self::tryMatchTranslationBlock($script); |
| 391 |
$param_name = $trans_attr_match[1]; |
| 392 |
if (is_array($translation_block)) { |
| 393 |
$all_fragments[$param_name][$translation_block['name']] = [ |
| 394 |
'mode' => 'i18n', |
| 395 |
'fragments' => $translation_block['fragments'], |
| 396 |
]; |
| 397 |
} |
| 398 |
} |
| 399 |
continue; |
| 400 |
} |
| 401 |
|
| 402 |
$frag_id = $attr_match[1]; |
| 403 |
|
| 404 |
$script_content = HTMLHelper::unclobberCdataInternal($script->textContent); |
| 405 |
|
| 406 |
$match_res = preg_match('/var ' . preg_quote(str_replace('-', '_', $frag_id), '/') . '_params = (.*);/', $script_content, $json_matches); |
| 407 |
if ($match_res === false || $match_res === 0) { |
| 408 |
$unmatched_res = preg_match_all('/var (.+)_params = (.*);/', $script_content, $json_multi_matches, PREG_SET_ORDER, 0); |
| 409 |
if ($unmatched_res === false || $unmatched_res === 0) { |
| 410 |
$overridden_temp = self::tryMatchWithOverride($script, $html_data); |
| 411 |
if (is_array($overridden_temp)) { |
| 412 |
foreach ($overridden_temp as $overridden_temp_item) { |
| 413 |
$all_fragments[$frag_id][$overridden_temp_item['name']] = [ |
| 414 |
'mode' => 'override', |
| 415 |
'fragments' => $overridden_temp_item['fragments'], |
| 416 |
]; |
| 417 |
} |
| 418 |
} |
| 419 |
continue; |
| 420 |
} |
| 421 |
|
| 422 |
// Since this is might have multiple matches, we need to check each of them |
| 423 |
foreach ($json_multi_matches as $json_multi_match) { |
| 424 |
$json_par_key = $json_multi_match[1]; |
| 425 |
$json_data = $json_multi_match[2]; |
| 426 |
$collected_temp = self::collectFragmentFromJson(json_decode($json_data, true)); |
| 427 |
if (!empty($collected_temp)) { |
| 428 |
$all_fragments[$frag_id][$json_par_key] = [ |
| 429 |
'mode' => 'auto', |
| 430 |
'fragments' => $collected_temp, |
| 431 |
]; |
| 432 |
} |
| 433 |
} |
| 434 |
|
| 435 |
continue; |
| 436 |
} |
| 437 |
|
| 438 |
$json_data = $json_matches[1]; |
| 439 |
$collected_temp = self::collectFragmentFromJson(json_decode($json_data, true)); |
| 440 |
if (!empty($collected_temp)) { |
| 441 |
$all_fragments[$frag_id][str_replace('-', '_', $frag_id)] = [ |
| 442 |
'mode' => 'auto', |
| 443 |
'fragments' => $collected_temp, |
| 444 |
]; |
| 445 |
} |
| 446 |
} |
| 447 |
|
| 448 |
// Run filters |
| 449 |
$filtered_fragments = $all_fragments; |
| 450 |
$filtered_fragments = apply_filters('linguise_after_fragment_collection', $filtered_fragments, $html_data, $html_dom); |
| 451 |
if (is_array($filtered_fragments)) { |
| 452 |
$all_fragments = $filtered_fragments; |
| 453 |
} |
| 454 |
|
| 455 |
Debug::log('FragmentHandler -> Collected: ' . json_encode($all_fragments, JSON_PRETTY_PRINT)); |
| 456 |
|
| 457 |
return $all_fragments; |
| 458 |
} |
| 459 |
|
| 460 |
/** |
| 461 |
* Apply the translated fragments for the override. |
| 462 |
* |
| 463 |
* @param string $html_data The HTML data to be injected |
| 464 |
* @param string $fragment_name The name of the fragment, e.g. 'woocommerce' |
| 465 |
* @param string $fragment_param The param of the fragment, e.g. 'woocommerce$$attr-1' |
| 466 |
* @param array $fragment_info The array of fragments to be injected, from intoJSONFragments |
| 467 |
* |
| 468 |
* @return string |
| 469 |
*/ |
| 470 |
public static function applyTranslatedFragmentsForOverride($html_data, $fragment_name, $fragment_param, $fragment_info) |
| 471 |
{ |
| 472 |
return FragmentOverrideHandler::applyTranslatedFragmentsForOverride($html_data, $fragment_name, $fragment_param, $fragment_info); |
| 473 |
} |
| 474 |
|
| 475 |
/** |
| 476 |
* Apply the translated fragments for the auto mode. |
| 477 |
* |
| 478 |
* @param array $json_data The JSON data to be injected |
| 479 |
* @param array $fragments The array of fragments to be injected, from intoJSONFragments |
| 480 |
* |
| 481 |
* @return array |
| 482 |
*/ |
| 483 |
public static function applyTranslatedFragmentsForAuto($json_data, $fragments) |
| 484 |
{ |
| 485 |
return FragmentAutoHandler::applyTranslatedFragmentsForAuto($json_data, $fragments); |
| 486 |
} |
| 487 |
|
| 488 |
/** |
| 489 |
* Apply the translated fragments for the i18n mode. |
| 490 |
* |
| 491 |
* @param string $html_data The HTML data to be injected |
| 492 |
* @param string $param_name The name of the fragment, e.g. 'woocommerce' |
| 493 |
* @param string $param_key The param of the fragment, e.g. 'messages' |
| 494 |
* @param array $fragments The array of fragments to be injected, from intoJSON |
| 495 |
* |
| 496 |
* @return string |
| 497 |
*/ |
| 498 |
public static function applyTranslatedFragmentsForI18n($html_data, $param_name, $param_key, $fragments) |
| 499 |
{ |
| 500 |
return FragmentI18nHandler::applyTranslatedFragmentsForI18n($html_data, $param_name, $param_key, $fragments); |
| 501 |
} |
| 502 |
|
| 503 |
/** |
| 504 |
* Inject the fragments into the HTML data. |
| 505 |
* |
| 506 |
* @param string $html_data The HTML data to be injected |
| 507 |
* @param array $fragments The array of fragments to be injected, from intoJSONFragments |
| 508 |
* |
| 509 |
* @return string |
| 510 |
*/ |
| 511 |
public static function injectTranslatedWPFragments($html_data, $fragments) |
| 512 |
{ |
| 513 |
Debug::log('FragmentHandler -> Injecting: ' . json_encode($fragments, JSON_PRETTY_PRINT)); |
| 514 |
|
| 515 |
// Remove the `linguise-fragment` marker elements for every group we're about to process here, |
| 516 |
// directly in the DOM. This is done once, up front, so it's unaffected by the string-based |
| 517 |
// substitutions below (see FragmentBase::removeFragmentMarkers() for why this can't be a |
| 518 |
// literal str_replace() against the raw captured HTML). |
| 519 |
$html_dom = null; |
| 520 |
foreach ($fragments as $fragment_name => $fragment_jsons) { |
| 521 |
foreach ($fragment_jsons as $fragment_param => $fragment_list) { |
| 522 |
if (!in_array($fragment_list['mode'], ['auto', 'override', 'skip', 'i18n'])) { |
| 523 |
continue; |
| 524 |
} |
| 525 |
|
| 526 |
if ($html_dom === null) { |
| 527 |
$html_dom = HTMLHelper::loadHTML($html_data); |
| 528 |
if (empty($html_dom)) { |
| 529 |
break 2; |
| 530 |
} |
| 531 |
} |
| 532 |
|
| 533 |
self::removeFragmentMarkers($html_dom, $fragment_name, $fragment_param); |
| 534 |
} |
| 535 |
} |
| 536 |
if (!empty($html_dom)) { |
| 537 |
$html_data = HTMLHelper::saveHTML($html_dom); |
| 538 |
} |
| 539 |
|
| 540 |
foreach ($fragments as $fragment_name => $fragment_jsons) { |
| 541 |
foreach ($fragment_jsons as $fragment_param => $fragment_list) { |
| 542 |
$mode = $fragment_list['mode']; |
| 543 |
|
| 544 |
if (!in_array($mode, ['auto', 'override', 'skip', 'i18n'])) { |
| 545 |
continue; |
| 546 |
} |
| 547 |
|
| 548 |
if ($mode === 'skip') { |
| 549 |
continue; |
| 550 |
} |
| 551 |
|
| 552 |
if ($mode === 'override') { |
| 553 |
$html_data = self::applyTranslatedFragmentsForOverride($html_data, $fragment_param, $fragment_name, $fragment_list); |
| 554 |
continue; |
| 555 |
} |
| 556 |
|
| 557 |
if ($mode === 'i18n') { |
| 558 |
// i18n mode for translation blocks |
| 559 |
$html_data = self::applyTranslatedFragmentsForI18n($html_data, $fragment_name, $fragment_param, $fragment_list['fragments']); |
| 560 |
continue; |
| 561 |
} |
| 562 |
|
| 563 |
$id = preg_quote($fragment_name . '-js-extra', '/'); |
| 564 |
$variable = preg_quote($fragment_param . '_params', '/'); |
| 565 |
|
| 566 |
$match_ptrn = '/<script (type=[\'"][\w\/]+[\'"] )?id=[\'"]' . $id . '[\'"]>(.*?)var ' . $variable . ' = \{(.*?)\};(.*?)<\/script>/s'; |
| 567 |
|
| 568 |
preg_match($match_ptrn, $html_data, $html_matches); |
| 569 |
if (empty($html_matches)) { |
| 570 |
continue; |
| 571 |
} |
| 572 |
|
| 573 |
$replaced_json = self::applyTranslatedFragmentsForAuto(json_decode('{' . $html_matches[3] . '}', true), $fragment_list['fragments']); |
| 574 |
|
| 575 |
$replaced_json = apply_filters('linguise_after_apply_translated_fragments_auto', $replaced_json, $fragment_name); |
| 576 |
|
| 577 |
if ($replaced_json === false) { |
| 578 |
throw new \LogicException('FragmentHandler -> Injection -> ' . $fragment_name . '/' . $fragment_param . ' -> JSON data is empty!'); |
| 579 |
} |
| 580 |
|
| 581 |
$subst_ptrn = '<script $1id="' . $fragment_name . '-js-extra">$2var ' . $fragment_param . '_params = ' . json_encode($replaced_json) . ';$4</script>'; |
| 582 |
|
| 583 |
$replacement = preg_replace($match_ptrn, $subst_ptrn, $html_data, 1, $count); |
| 584 |
if ($count) { |
| 585 |
$html_data = $replacement; |
| 586 |
} |
| 587 |
} |
| 588 |
} |
| 589 |
|
| 590 |
$mod_html_data = apply_filters('linguise_after_fragment_translation', $html_data, $fragments); |
| 591 |
if (!empty($mod_html_data)) { |
| 592 |
$html_data = $mod_html_data; |
| 593 |
} |
| 594 |
return $html_data; |
| 595 |
} |
| 596 |
} |
| 597 |
|