Exception:Array
(
[message] => Class "DOMDocument" not found
[file] => /home/www/vvveb/www/system/vtpl/vtpl.php
[line_no] => 201
[line] => $this->document = new DOMDocument(); // <==
[lines] => Array
(
[0] =>
[1] => $this->selector = $selector;
[2] => $this->componentId = $componentId;
[3] => $this->componentContent = $componentContent;
[4] =>
[5] => //libxml_disable_entity_loader();
[6] =>
[7] => $this->document = new DOMDocument(); // <==
[8] => $this->legacy = true;
[9] =>
[10] => $this->configureDomDocument($this->document);
[11] => }
[12] =>
[13] => /**
)
[trace] => #0 /home/www/vvveb/www/system/core/view.php(168): Vtpl->__construct()
#1 /home/www/vvveb/www/system/core/view.php(190): Vvveb\System\Core\View->__construct()
#2 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(64): Vvveb\System\Core\View::getInstance()
#3 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(80): InsertScriptsPlugin->app()
#4 /home/www/vvveb/www/plugins/insert-scripts/plugin.php(85): InsertScriptsPlugin->__construct()
#5 /home/www/vvveb/www/system/extensions/plugins.php(354): include('...')
#6 /home/www/vvveb/www/system/core/startup.php(512): Vvveb\System\Extensions\Plugins::loadPlugins()
#7 /home/www/vvveb/www/index.php(170): Vvveb\System\Core\start()
#8 /home/www/vvveb/www/public/index.php(28): include('...')
#9 {main}
[code] => Array
(
[0] =>
[2] => /**
[3] => * Vvveb
[4] => *
[5] => * Copyright (C) 2022 Ziadin Givan
[6] => *
[7] => * This program is free software: you can redistribute it and/or modify
[8] => * it under the terms of the GNU Affero General Public License as
[9] => * published by the Free Software Foundation, either version 3 of the
[10] => * License, or (at your option) any later version.
[11] => *
[12] => * This program is distributed in the hope that it will be useful,
[13] => * but WITHOUT ANY WARRANTY; without even the implied warranty of
[14] => * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
[15] => * GNU Affero General Public License for more details.
[16] => *
[17] => * You should have received a copy of the GNU Affero General Public License
[18] => * along with this program. If not, see .
[19] => *
[20] => */
[21] =>
[22] => //set debug to true to show the vtpl console log
[23] => defined('VTPL_DEBUG') || define('VTPL_DEBUG', false);
[24] =>
[25] => defined('VTPL_HTML_MINIFY') || define('VTPL_HTML_MINIFY', false);
[26] => define('VTPL_JS_MINIFY', false);
[27] => define('VTPL_PHP_MINIFY', false);
[28] => define('VTPL_CLEAN_COMP_OPT', false);
[29] =>
[30] => define('VTPL_DEBUG_SHOW_XPATH', true);
[31] => define('VTPL_DONT_ALLOW_PHP', true); //don't set to false unless ABSOLUTELY NECESSARY!
[32] =>
[33] => // PHP placeholder constants - used to embed PHP inside the DOM without triggering parsing.
[34] => // These are replaced with real php tags in cleanHtml().
[35] => define('VTPL_PHP_CDATA_OPEN', '<_script language="php"> define('VTPL_PHP_CDATA_CLOSE', ']]>');
[37] => define('VTPL_PHP_OPEN', '<_script language="php">');
[38] => define('VTPL_PHP_CLOSE', '');
[39] =>
[40] => include 'macros.php';
[41] =>
[42] => include 'debug.php';
[43] =>
[44] => /**
[45] => * Vtpl template engine — compiles `.tpl` selector commands against plain-HTML documents.
[46] => *
[47] => * See each property declaration below for individual types.
[48] => */
[49] => #[\AllowDynamicProperties]
[50] => class Vtpl {
[51] => /** @var string */
[52] => private string $template = '';
[53] =>
[54] => /** @var array */
[55] => private array $templatePath = [];
[56] =>
[57] => /** @var string */
[58] => private string $htmlPath = '';
[59] =>
[60] => /** @var string */
[61] => private string $relativePath = '';
[62] =>
[63] => /** @var DOMDocument */
[64] => private Dom\HTMLDocument|DOMDocument $document;
[65] =>
[66] => /** @var bool */
[67] => private bool $legacy = true;
[68] =>
[69] => /** @var ?DOMXPath */
[70] => private ?DOMXPath $xpath = null;
[71] =>
[72] => /** @var string */
[73] => private string $extension = 'tpl';
[74] =>
[75] => /** @var string */
[76] => private string $translationFunction = '\Vvveb\__';
[77] =>
[78] => /** @var bool */
[79] => private bool $removeComments = true;
[80] =>
[81] => /** @var bool */
[82] => private bool $removeVattrs = false;
[83] =>
[84] => /** @var bool */
[85] => private bool $minifyHtml = VTPL_HTML_MINIFY;
[86] =>
[87] => /** @var string */
[88] => private string $documentType = 'html';
[89] =>
[90] => /** @var bool */
[91] => private bool $checkSyntax = true;
[92] =>
[93] => /** @var ?string */
[94] => private ?string $selector = null;
[95] =>
[96] => /** @var array|null */
[97] => private ?array $selectors = null;
[98] =>
[99] => /** @var array|null */
[100] => private ?array $replaceConstants = null;
[101] =>
[102] => /** @var ?int */
[103] => private ?int $componentId = null;
[104] =>
[105] => /** @var ?string */
[106] => private ?string $componentContent = null;
[107] =>
[108] => /** @var array|null */
[109] => private ?array $froms = null;
[110] =>
[111] => /** @var array */
[112] => private array $constants = [];
[113] =>
[114] => /** @var array */
[115] => private array $variables = [];
[116] =>
[117] => /** @var array */
[118] => private array $strings = [];
[119] =>
[120] => /** @var array */
[121] => private array $scripts = [];
[122] =>
[123] => /** @var VtplDebug */
[124] => private VtplDebug $debug;
[125] =>
[126] => /** @var array */
[127] => private array $phpCode = [];
[128] =>
[129] => /** @var array */
[130] => private array $modifiers = ['outerHTML', 'text', 'before', 'after', 'append', 'prepend', 'deleteAllButFirst', 'deleteAllButFirstChild', 'delete', 'if_exists', 'hide', 'addClass', 'removeClass'];
[131] =>
[132] => /** @var array */
[133] => private array $variableFilters =
[134] => [
[135] => 'capitalize' => 'ucfirst($$0)',
[136] => 'html_to_text' => 'Vvveb\htmlToText($$0)',
[137] => 'cdata' => 'CDATA_START . ( $$0 ). CDATA_END',
[138] => 'friendly_date' => 'Vvveb\friendlyDate($$0)',
[139] => 'friendly_number' => 'Vvveb\parseQuantity($$0)',
[140] => 'human_readable' => 'Vvveb\humanReadable($$0)',
[141] => 'date' => 'date($$1,is_int($$0) ? $$0 : strtotime($$0))',
[142] => 'truncate' => 'substr($$0, 0, $$1)',
[143] => 'truncate_words' => 'Vvveb\truncateWords($$0,$$1)',
[144] => 'replace' => 'str_replace($$1, $$2, $$0)',
[145] => 'uppercase' => 'strtoupper($$0)',
[146] => 'lowercase' => 'strtolower($$0)',
[147] => 'append' => '$$1 . $$0',
[148] => 'prepend' => '$$0 . $$1',
[149] => 'strip_html' => 'strip_tags($$0)',
[150] => 'strip_newlines' => 'str_replace("\n",\' \', $$0)',
[151] => 'mod' => ['tag', 'if (@++$_modc_@@__VTPL_rand()__@@ % (int)$$1 === (int)$$2) {', 'if (@++$_modc_@@__VTPL_rand()__@@ % (int)$$1 === (int)$$2) {'],
[152] => 'mod_class' => ['class', ''],
[153] => 'conditional_class' => ['class', ''],
[154] => 'iteration_class' => ['class', ''],
[155] => 'number_format' => 'number_format($$0, $$1, $$2, $$3)',
[156] => 'only_decimals' => 'substr($$0, (($_strpos = strrpos($$0, \'.\')) !== false)?$_strpos + 1:-100, ($_strpos !== false)?10:false)',
[157] => 'without_decimals' => 'substr($$0, 0, strrpos($$0, \'.\'))',
[158] => ];
[159] =>
[160] => /** @var int */
[161] => private int $attributesIndex = 0;
[162] =>
[163] => /** @var int */
[164] => private int $newAttributesIndex = 0;
[165] =>
[166] => /** @var array */
[167] => private array $attributes = [];
[168] =>
[169] => /** @var array */
[170] => private array $newAttributes = [];
[171] =>
[172] => /** @var bool */
[173] => private bool $externalElements = false;
[174] =>
[175] => /** @var bool */
[176] => public bool $escapeInline = false;
[177] =>
[178] => /** @var array */
[179] => private array $prefix = [];
[180] =>
[181] => /**
[182] => * @param ?string $selector
[183] => * @param ?int $componentId
[184] => * @param ?string $componentContent
[185] => */
[186] => public function __construct(?string $selector = null, ?int $componentId = null, ?string $componentContent = null) {
[187] => $this->templatePath = [];
[188] =>
[189] => $this->debug = new VtplDebug();
[190] =>
[191] => if (VTPL_DEBUG) {
[192] => $this->debug->enable(true);
[193] => }
[194] =>
[195] => $this->selector = $selector;
[196] => $this->componentId = $componentId;
[197] => $this->componentContent = $componentContent;
[198] =>
[199] => //libxml_disable_entity_loader();
[200] =>
[201] => $this->document = new DOMDocument(); // <==
[202] => $this->legacy = true;
[203] =>
[204] => $this->configureDomDocument($this->document);
[205] => }
[206] =>
[207] => /**
[208] => * Apply standard configuration options to a DOMDocument instance.
[209] => *
[210] => * @param DOMDocument $doc
[211] => *
[212] => * @return void
[213] => */
[214] => private function configureDomDocument(Dom\HTMLDocument|DOMDocument $doc): void {
[215] => if ($this->legacy) {
[216] => $doc->preserveWhiteSpace = true;
[217] => $doc->recover = true;
[218] => $doc->strictErrorChecking = false;
[219] => $doc->substituteEntities = false;
[220] => $doc->formatOutput = false;
[221] => $doc->resolveExternals = false;
[222] => $doc->validateOnParse = false;
[223] => $doc->xmlStandalone = true;
[224] => }
[225] => }
[226] =>
[227] => /**
[228] => * @param bool $flag
[229] => *
[230] => * @return void
[231] => */
[232] => public function removeVattrs(bool $flag = true): void {
[233] => $this->removeVattrs = $flag;
[234] => }
[235] =>
[236] => /**
[237] => * @param bool $flag
[238] => *
[239] => * @return void
[240] => */
[241] => public function setMinifyHtml(bool $flag = true): void {
[242] => $this->minifyHtml = $flag;
[243] => }
[244] =>
[245] => /**
[246] => * @return string
[247] => */
[248] => public function getDocumentType(): string {
[249] => return $this->documentType;
[250] => }
[251] =>
[252] => /**
[253] => * @return Dom\Document|DOMDocument
[254] => */
[255] => public function getDocument(): Dom\Document|DOMDocument {
[256] => return $this->document;
[257] => }
[258] =>
[259] => /**
[260] => * @param ?string $selector
[261] => * @param false|string $command
[262] => *
[263] => * @return void
[264] => */
[265] => public function addCommand(?string $selector, false|string $command = false): void {
[266] => if ($selector) {
[267] => $this->template .= "\n $selector";
[268] =>
[269] => if ($command) {
[270] => //if not php code then quote string
[271] => if (!str_starts_with($command, '')) {
[272] => $command = "'$command'";
[273] => }
[274] =>
[275] => $this->template .= " = $command\n";
[276] => } else {
[277] => $this->template .= "\n";
[278] => }
[279] => }
[280] => }
[281] =>
[282] => /**
[283] => * @param string $path
[284] => *
[285] => * @return void
[286] => */
[287] => public function setHtmlPath(string $path): void {
[288] => $this->htmlPath = $path;
[289] => }
[290] =>
[291] => /**
[292] => * @param string $path
[293] => *
[294] => * @return void
[295] => */
[296] => public function setRelativePath(string $path): void {
[297] => $this->relativePath = $path;
[298] => }
[299] =>
[300] => /**
[301] => * @param string $path
[302] => *
[303] => * @return void
[304] => */
[305] => public function addTemplatePath(string $path): void {
[306] => if ($path) {
[307] => $this->templatePath[] = $path;
[308] => }
[309] => }
[310] =>
[311] => /**
[312] => * @param string $templateFile
[313] => * @param bool $before
[314] => *
[315] => * @return void
[316] => */
[317] => public function loadTemplateFile(string $templateFile, bool $before = false): void {
[318] => if (file_exists($templateFile)) {
[319] => if ($before) {
[320] => $this->template = file_get_contents($templateFile) . "\n\n" . $this->template;
[321] => } else {
[322] => $this->template .= "\n\n" . file_get_contents($templateFile);
[323] => }
[324] => }
[325] => }
[326] =>
[327] => /**
[328] => * @param string $templateFile
[329] => * @param false|string $extra
[330] => *
[331] => * @return ?bool
[332] => */
[333] => public function loadTemplateFileFromPath(string $templateFile, false|string $extra = false): ?bool {
[334] => $path = '';
[335] =>
[336] => foreach ($this->templatePath as $path) {
[337] => $this->debug->log('LOAD', $path . $templateFile);
[338] =>
[339] => if (!file_exists($path . $templateFile)) {
[340] => $this->debug->log('LOAD', '!EXISTS' . $path . $templateFile);
[341] =>
[342] => continue;
[343] => }
[344] =>
[345] => //load from the first matching path only (paths are priority ordered,
[346] => //same semantics as the import loader); loading from every path would
[347] => //duplicate content when a theme overrides a base template
[348] => $this->template .= file_get_contents($path . $templateFile);
[349] =>
[350] => break;
[351] => }
[352] =>
[353] => if ($extra) {
[354] => $this->template .= $extra;
[355] => }
[356] =>
[357] => if (!$this->template) {
[358] => $this->debug->log('LOAD', 'EMPTY' . $path . $templateFile);
[359] =>
[360] => return false;
[361] => }
[362] =>
[363] => return true;
[364] => }
[365] =>
[366] => /**
[367] => * @return void
[368] => */
[369] => private function processPhpcode(): void {
[370] => // Placeholders, replace variables, php code etc with placeholders.
[371] => //$this->template = preg_replace('@\/\/[^\n\r]+?(?:\*\)|[\n\r])@','', $this->template);
[372] => preg_match_all('/(?/s', $this->template, $phpCode);
[373] =>
[374] => $patternsPhp = [];
[375] => $placeholdersPhp = [];
[376] =>
[377] => for ($i = 0; $i < count($phpCode[1]); $i++) {
[378] => $patternsPhp[] = '/' . preg_quote($phpCode[0][$i], '/') . '/';
[379] => $placeholdersPhp[] = "replace_php_code-{$i}";
[380] => // double backslashes must be escaped if we want to use them in the replacement argument
[381] => $phpCode[0][$i] = str_replace('\\\\', '\\\\\\\\', $phpCode[1][$i]);
[382] => }
[383] =>
[384] => if ($placeholdersPhp) {
[385] => $this->template = preg_replace($patternsPhp, $placeholdersPhp, $this->template);
[386] => }
[387] =>
[388] => $this->phpCode = $phpCode[0];
[389] => }
[390] =>
[391] => /**
[392] => * @return void
[393] => */
[394] => private function processFroms(): void {
[395] => // Froms - from(index.html|#element)
[396] => preg_match_all('/from\(([^\|]+)\|(.+)\)/', $this->template, $froms);
[397] => $patternsFroms = [];
[398] => $placeholdersFroms = [];
[399] =>
[400] => for ($i = 0; $i < count($froms[1]); $i++) {
[401] => $patternsFroms[] = '/' . preg_quote($froms[0][$i], '/') . '/';
[402] => $placeholdersFroms[] = "replace_from-{$i}\n";
[403] => // double backslashes must be escaped if we want to use them in the replacement argument
[404] => $froms[0][$i] = str_replace('\\\\', '\\\\\\\\', $froms[1][$i]);
[405] => }
[406] =>
[407] => if ($froms[0]) {
[408] => $this->template = preg_replace($patternsFroms, $placeholdersFroms, $this->template);
[409] => }
[410] =>
[411] => $this->froms = $froms;
[412] => }
[413] =>
[414] => /**
[415] => * @return void
[416] => */
[417] => private function processStrings(): void {
[418] => // strings
[419] => //single quote
[420] => preg_match_all("/=\\s+'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'(?!\\])/s", $this->template, $stringsSingle);
[421] => //doube quote
[422] => preg_match_all('/=\s+"[^"\\\r\n]*(?:\.[^"\\\r\n]*)*"(?!\])/s', $this->template, $strings);
[423] =>
[424] => $strings = array_values(array_unique($strings[0]));
[425] => $stringsSingle = array_values(array_unique($stringsSingle[0]));
[426] => $strings = array_merge((array)$strings, (array)$stringsSingle);
[427] => $patternsStrings = [];
[428] => $placeholdersStrings = [];
[429] =>
[430] => for ($i = 0; $i < count($strings); $i++) {
[431] => $string = trim($strings[$i], '= ');
[432] => $patternsStrings[] = '/' . preg_quote($string, '/') . '/';
[433] => $placeholdersStrings[] = "replace_string-{$i}";
[434] => // double backslashes must be escaped if we want to use them in the replacement argument
[435] => $strings[$i] = str_replace('\\\\', '\\\\\\\\', $string);
[436] => }
[437] =>
[438] => if ($strings) {
[439] => $this->template = preg_replace($patternsStrings, $placeholdersStrings, $this->template);
[440] => }
[441] =>
[442] => $this->strings = $strings;
[443] => }
[444] =>
[445] => /**
[446] => * @return void
[447] => */
[448] => private function processVariables(): void {
[449] => preg_match_all('/(?template, $variables);
[450] =>
[451] => $patternsVariables = [];
[452] => $placeholdersVariables = [];
[453] => $variables[1] = array_unique($variables[1]);
[454] => usort($variables[1], fn ($a, $b) => strlen($b) <=> strlen($a));
[455] =>
[456] => for ($i = 0; $i < count($variables[1]); $i++) {
[457] => $patternsVariables[] = '/' . preg_quote($variables[1][$i], '/') . '/';
[458] => $placeholdersVariables[] = "replace_variable-{$i}";
[459] => // double backslashes must be escaped if we want to use them in the replacement argument
[460] => }
[461] =>
[462] => $this->template = preg_replace($patternsVariables, $placeholdersVariables, $this->template);
[463] =>
[464] => $this->variables = $variables[1];
[465] => }
[466] =>
[467] => /**
[468] => * Process the raw template commands: expand imports, extract php/variable/
[469] => * from/string placeholders into lookup tables, then parse selector lines.
[470] => */
[471] => private function processTemplateFile(): void {
[472] => if (!$this->template) {
[473] => return;
[474] => }
[475] =>
[476] => $this->expandImports();
[477] => $this->processPhpcode();
[478] => $this->processVariables();
[479] => $this->processFroms();
[480] => $this->processStrings();
[481] =>
[482] => $this->parseSelectorLines();
[483] => }
[484] =>
[485] => /**
[486] => * Expand import(file.tpl) directives by inlining the referenced files,
[487] => * repeating until no imports remain (imports may nest).
[488] => */
[489] => private function expandImports(): void {
[490] => $foundImports = true;
[491] => //expand imports
[492] => while ($foundImports) {
[493] => $foundImports = preg_match_all("/import\\(([^\\&%'`\\@{}~!#\\(\\)&\\^\\+,=\\[\\]]*?\\.{$this->extension})\\,?(.+)?\\);?/", $this->template, $imports);
[494] =>
[495] => for ($i = 0; $i < count($imports[0]); $i++) {
[496] => $content = '';
[497] => $importContent = '';
[498] =>
[499] => foreach ($this->templatePath as $path) {
[500] => $import = $imports[1][$i];
[501] =>
[502] => if (str_starts_with($import, '/plugins/')) {
[503] => $importFile = DIR_PLUGINS . substr($import, 9);
[504] => } else {
[505] => if ($import[0] === '/') {
[506] => $importFile = DIR_ROOT . $import;
[507] => } else {
[508] => $importFile = $path . $import;
[509] => }
[510] => }
[511] =>
[512] => if (str_contains($importFile, '*')) {
[513] => $files = glob($importFile);
[514] => } else {
[515] => $files = [$importFile];
[516] => }
[517] =>
[518] => $found = false;
[519] =>
[520] => foreach ($files as $importFile) {
[521] => if (file_exists($importFile)) {
[522] => $parameter = trim($imports[2][$i]);
[523] => $this->debug->log('LOAD', $importFile);
[524] =>
[525] => if (!empty($parameter)) {
[526] => //if php array then parameter is a template variables list
[527] => if ($parameter[0] === '{'/* || $parameter[0] == '['*/) {
[528] => $importContent = file_get_contents($importFile);
[529] =>
[530] => //process template
[531] => $templateParams = json_decode($parameter, true);
[532] =>
[533] => foreach ($templateParams as $name => $value) {
[534] => $importContent = str_replace('{{' . $name . '}}', $value, $importContent);
[535] => }
[536] => } else {
[537] => //parameter is a css selector to check if the element exists and conditionally include template
[538] => $elements = $this->query($parameter);
[539] => if ($elements && $elements->length) {
[540] => //found, load template below
[541] => $importContent = file_get_contents($importFile);
[542] => } else {
[543] => //not found, replace import with nothing
[544] => $this->template = str_replace($imports[0][$i], '', $this->template);
[545] =>
[546] => continue;
[547] => }
[548] => }
[549] => } else {
[550] => $importContent = file_get_contents($importFile);
[551] => }
[552] =>
[553] => //remove comments
[554] => $importContent = preg_replace("/(? $content .= $importContent . "\n";
[556] =>
[557] => $found = true;
[558] => } else {
[559] => $this->debug->log('VTPL_IMPORT_FILE_NOT_EXIST', $importFile);
[560] => }
[561] => }
[562] =>
[563] => if ($found) {
[564] => break;
[565] => }
[566] => }
[567] =>
[568] => $this->template = str_replace($imports[0][$i], $content, $this->template);
[569] => }
[570] => }
[571] => }
[572] =>
[573] => /**
[574] => * Normalize template whitespace/comments and split the remaining command
[575] => * text into selector lines ("selector = value" pairs or bare selectors).
[576] => */
[577] => private function parseSelectorLines(): void {
[578] => //remove comments
[579] => $this->template = preg_replace("/(?template);
[580] => $this->template = preg_replace('/\n+/', "\n", $this->template);
[581] => $this->template = preg_replace('/(?<=\=)\s*\n/', '', $this->template);
[582] =>
[583] => $this->template = str_replace("\n\n", "\n", trim($this->template));
[584] => $lines = explode("\n", $this->template);
[585] =>
[586] => foreach ($lines as $line) {
[587] => $matches = [];
[588] => //check if "=" exists for pair
[589] => if (preg_match('/(.*?)(=)\s*(replace_.*|true|false);?/s', $line, $matches) && $matches[1]) {
[590] => $this->selectors[] = [trim($matches[1]), trim($matches[3])];
[591] => } else {
[592] => //single command, no pair
[593] => $line = trim($line);
[594] =>
[595] => if ($line) {
[596] => $this->selectors[] = [$line];
[597] => }
[598] => }
[599] => }
[600] => }
[601] =>
[602] => /**
[603] => * Convert a CSS-selector into an xPath-query.
[604] => *
[605] => * @param string $selector The CSS-selector
[606] => *
[607] => * @return string
[608] => */
[609] => public function cssToXpath(string $selector): string {
[610] => $selector = (string)$selector;
[611] =>
[612] => //protect quoted literals so later rules (brackets/class/id) can't mangle
[613] => //attribute values like name="settings[frontend]" — placeholders carry no
[614] => //special css characters and are restored after the rule table runs
[615] => $quotedLiterals = [];
[616] => $selector = preg_replace_callback('/"[^"]*"/', function(array $m) use (&$quotedLiterals): string {
[617] => $key = '__QT' . count($quotedLiterals) . '__';
[618] => $quotedLiterals[$key] = substr($m[0], 1, -1);
[619] =>
[620] => return '"' . $key . '"';
[621] => }, $selector);
[622] =>
[623] => //convert , to | union operator to allow multiple queries
[624] => $selector = str_replace(',', '|', $selector);
[625] =>
[626] => $cssSelector = [
[627] => // E > F: Matches any F element that is a child of an element E
[628] => '/\s*>\s*/', // /
[629] =>
[630] => // E + F: Matches any F element immediately preceded by an element
[631] => '/\s+\+\s+/', // /following-sibling::*[1]/self::
[632] =>
[633] => // E F: Matches any F element that is a descendant of an E element
[634] => '/([\w\*="\[\]#._-])\s+([\w\*="\[\]#._-])/', // \1//\2' //'/([a-zA-Z\*="\[\]#._-])\s+([a-zA-Z\*#._-])/',
[635] =>
[636] => // E:first-child: Matches element E when E is the first child of its parent
[637] => '/([a-z#\.]\w*):first-child/', // *[1]/self::\1
[638] =>
[639] => // E:nth-child() Matches the nth child element
[640] => '/([a-z#\.]\w*):nth-child\((\d+)\)/', // *[\2]/self::\1
[641] =>
[642] => // E:first: Matches the first element from the set
[643] => '/([a-z#\.]\w*):first/', // \1[1]
[644] =>
[645] => // E:nth(2): Matches the nth element from the set
[646] => '/([a-z#\.]\w*):nth\((\d+)\)/', // \1[\2]
[647] =>
[648] => // E[foo=warning]: Matches any E element whose unquoted "foo" attribute value equals "warning"
[649] => // bracketed value first (name=settings[enabled] — invalid css but common in templates),
[650] => // so the plain unquoted rule can't stop at the inner ']'
[651] => '/([a-z]\w*|\*)\[([a-z][\w\-_]*)\=([\w\-\.]+)\[([\w\-_]+)\]\]/', // \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3[\4]", " " ) ) ]
[652] => '/\[([a-z][\w\-_]*)\=([\w\-\.]+)\[([\w\-_]+)\]\]/', // *[ contains( concat( " ", @\1, " " ), concat( " ", "\2[\3]", " " ) ) ]
[653] => '/([a-z]\w*|\*)\[([a-z][\w\-_]*)\=([^\s\]"]+)\]/', // \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]
[654] =>
[655] => // E[foo="warning"]: Matches any E element whose "foo" attribute value is exactly equal to "warning"
[656] => '/([a-z]\w*)\[([a-z][\w\-_]*)\="([^"]*)"\]/', // \1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]
[657] =>
[658] => // E[foo]: Matches any E element with the "foo" attribute set (whatever the value)
[659] => '/(?
[661] => // E[!foo]: Matches any E element without the "foo" attribute set
[662] => '/([a-z]\w*)\[!([a-z][\w\-_]*)\]/', // \1 [ not(@\2) ]
[663] =>
[664] => // [foo=warning]: Matches any element whose unquoted "foo" attribute value equals "warning"
[665] => '/\[([a-z][\w\-_]*)\=([^\s\]"]+)\]/', // *[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]
[666] =>
[667] => // [foo="warning"]: Matches any element whose "foo" attribute value is exactly equal to "warning"
[668] => '/\[([a-z][\w\-_]*)\=\"(.*)\"\]/', // *[@\1 = "\2"]
[669] =>
[670] => // element[foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning"
[671] => '/([a-z]\w*|\*)\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/', // \1[ contains( @\2, "\3" ) ]
[672] =>
[673] => // [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning" and has other attributes
[674] => '/(?<=\])\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/', // [contains(@\1,"\2")]
[675] =>
[676] => // [foo*="warning"]: Matches any element whose "foo" attribute value contains the string "warning"
[677] => '/\[([a-z][\w\-_]*)\*\=\"([^"]+)\"\]/', // [ contains( @\1, "\2" ) ]
[678] =>
[679] => // [foo^="warning"]: Matches any element whose "foo" attribute value begins with the string "warning"
[680] => '/\[([a-z][\w_\-]*)\^\=\"([^"]+)\"\]/', // [starts-with(@\1,"\2")]
[681] =>
[682] => // [foo$="warning"]: Matches any element whose "foo" attribute value ends with the string "warning"
[683] => '/\[([a-z][\w_\-]*)\$\=\"([^"]+)\"\]/', // [ends-with(@\1,"\2")]
[684] =>
[685] => // [foo][baz]: Matches any element with the "foo" attribute set (whatever the value)
[686] => '/(?<=\])(?
[688] => // [foo]: Matches any element with the "foo" attribute set (whatever the value)
[689] => '/(?
[691] => // [!foo]: Matches any element without the "foo" attribute set (whatever the value)
[692] => '/\[!([a-z][\w_\-]*)\]/', // *[ not(@\1) ]
[693] =>
[694] => // element[foo*]: Matches any element that starts with "foo" attribute (whatever the value)
[695] => '/(\w+)\[([a-z][\w\-]*)\*\]/', // \1 [ @*[starts-with(name(), "\2")] ]
[696] =>
[697] => // [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and has other attributes
[698] => '/(?<=\])\[([a-z][\w\-]*)\*\]/', // [ @*[starts-with(name(), "\1")] ]
[699] =>
[700] => // [foo*]: Matches any element that starts with "foo" attribute (whatever the value) and is a single attribute
[701] => '/(?
[703] => // div.warn*: HTML only. The same as DIV[class*="warning"]
[704] => '/([a-z]\w*|\*)\.([a-z][\w\-_]*)\*/', // \1[ contains( concat( " ", @class, " " ), concat( " ", "\2") ) ]
[705] =>
[706] => // div.warning: HTML only. The same as DIV[class~="warning"]
[707] => '/([a-z]\w*|\*)\.([a-z][\w\-_]*)+/', // \1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]
[708] =>
[709] => // .warn*: HTML only. The same as [class*="warning"]
[710] => '/\.([a-z][\w\-\_]*)\*/', // *[ contains( concat( " ", @class, " " ), concat( " ", "\1") ) ]
[711] =>
[712] => // .warning: HTML only. The same as [class~="warning"] and has other attributes
[713] => '/(?<=\])\.([a-z][\w\-\_]*)+/', // *[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]
[714] =>
[715] => // .warning: HTML only. The same as [class~="warning"]
[716] => '/\.([a-z][\w\-\_]*)+/', // *[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]
[717] =>
[718] => // E#myid: Matches any E element with id-attribute equal to "myid"
[719] => '/([a-z]\w*)\#([a-z][\w\-_]*)/', // \1[ @id = "\2" ]
[720] =>
[721] => // #myid: Matches any E element with id-attribute equal to "myid"
[722] => '/\#([a-z][\w\-_]*)/', // *[ @id = "\1" ]
[723] =>
[724] => // negation pseudo selector
[725] => '/\:not\(([^\)]+?)\)/', // :not(\1)
[726] =>
[727] => // empty pseudo selector
[728] => '/\:empty/', //:empty
[729] => ];
[730] =>
[731] => $xpathQuery = [
[732] => '/', //element > child
[733] => '/following-sibling::*[1]/self::', // element + precedent
[734] => '\1//\2', //element descendent
[735] => '*[1]/self::\1', //element:first-child
[736] => '*[\2]/self::\1', //element:nth-child(2)
[737] => '\1[1]', //element:first
[738] => '\1[\2]', //element:nth(2)
[739] => '\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3[\4]", " " ) ) ]', //element[attribute=bwarningacketed unquoted]
[740] => '*[ contains( concat( " ", @\1, " " ), concat( " ", "\2[\3]", " " ) ) ]', //[attribute=bracketed unquoted]
[741] => '\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]', //element[attribute=string unquoted]
[742] => '\1[ contains( concat( " ", @\2, " " ), concat( " ", "\3", " " ) ) ]', //element[attribute="string"]
[743] => '\1 [ @\2 ]', //element[attribute]
[744] => '\1 [ not(@\2) ]', //element[!attribute]
[745] => '*[ contains( concat( " ", @\1, " " ), concat( " ", "\2", " " ) ) ]', //[foo=warning unquoted]
[746] => '*[@\1 = "\2"]', //[foo="warning"]
[747] => '\1[ contains( @\2, "\3" ) ]', //element[foo*="warning"]
[748] => '[contains(@\1,"\2")]', //[foo*="warning"]
[749] => '[ contains( @\1, "\2" ) ]', //[foo*="warning"]
[750] => '[starts-with(@\1,"\2")]', //[foo^="warning"]
[751] => '[ends-with(@\1,"\2")]', //[foo$="warning"]
[752] => '[ @\1 ]', //[attribute][attribute]
[753] => '*[ @\1 ]', //[attribute]
[754] => '[ not(@\1) ]', //[!attribute]
[755] => '\1 [ @*[starts-with(name(), "\2")] ]', //element[attr*]
[756] => '[ @*[starts-with(name(), "\1")] ]', //[attr*] - attribute with other attributes
[757] => '*[ @*[starts-with(name(), "\1")] ]', //[attr*] - single attribute
[758] => '\1[ contains( concat( " ", @class, " " ), concat( " ", "\2") ) ]', //element[class*="string"]
[759] => '\1[ contains( concat( " ", @class, " " ), concat( " ", "\2", " " ) ) ]', //element[class~="string"]
[760] => '*[ contains( concat( " ", @class, " " ), concat( " ", "\1") ) ]', //[class*="string"]
[761] => '[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]', //element[class~="string"]
[762] => '*[ contains( concat( " ", @class, " " ), concat( " ", "\1", " " ) ) ]', //element[class~="string"]
[763] => '\1[ @id = "\2" ]', //element#id
[764] => '*[ @id = "\1" ]', //#id
[765] => //'(\1 except \2)', //negation pseudo selector
[766] => '[not(self::\1)]', //negation pseudo selector
[767] => '[not(text())]', //empty pseudo selector
[768] => ];
[769] =>
[770] => $result = (string)'//' . preg_replace($cssSelector, $xpathQuery, $selector);
[771] => $result = strtr($result, $quotedLiterals);
[772] => $this->debug->log('CSS_XPATH_TRANSFORM', $result);
[773] =>
[774] => return $result;
[775] => }
[776] =>
[777] => /**
[778] => * @param string $selector
[779] => *
[780] => * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false
[781] => */
[782] => public function query(string $selector): Dom\NodeList|DOMNodeList|false {
[783] => if ($this->legacy) {
[784] => $xpath = $this->cssToXpath($selector);
[785] => $result = $this->xpath->query($xpath);
[786] => } else {
[787] => //missing css support for
[788] => //[data-v-menu-item-*] => [attribute-name^="data-v-menu-item-"]
[789] => $result = $this->document->querySelectorAll($selector);
[790] => }
[791] =>
[792] => return $result;
[793] => }
[794] =>
[795] => /**
[796] => * @param string $modifier
[797] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false|null $elements
[798] => * @param array|string $val
[799] => *
[800] => * @return void
[801] => */
[802] => private function processElements(string $modifier, null|Dom\NodeList|DOMNodeList|false $elements, array|string $val): void {
[803] => //no match (or invalid xpath expression) - nothing to modify
[804] => if (!$elements || !$elements->length) {
[805] => return;
[806] => }
[807] =>
[808] => switch ($modifier) {
[809] => case 'deleteAllButFirstChild':
[810] => $this->deleteAllButFirstChild($elements);
[811] =>
[812] => break;
[813] =>
[814] => case 'deleteAllButFirst':
[815] => $this->deleteAllButFirst($elements);
[816] =>
[817] => break;
[818] =>
[819] => case 'outerHTML':
[820] => $this->outerHTML($elements, $val);
[821] =>
[822] => break;
[823] =>
[824] => case 'text':
[825] => case 'innerText':
[826] => $this->innerText($elements, $val);
[827] =>
[828] => break;
[829] =>
[830] => case 'before':
[831] => $this->insertBefore($elements, $val);
[832] =>
[833] => break;
[834] =>
[835] => case 'after':
[836] => $this->insertAfter($elements, $val);
[837] =>
[838] => break;
[839] =>
[840] => case 'append':
[841] => $this->append($elements, $val);
[842] =>
[843] => break;
[844] =>
[845] => case 'prepend':
[846] => $this->prepend($elements, $val);
[847] =>
[848] => break;
[849] =>
[850] => case 'delete':
[851] => $this->delete($elements);
[852] =>
[853] => break;
[854] =>
[855] => case 'if_exists':
[856] => $this->ifExists($elements, $val);
[857] =>
[858] => break;
[859] =>
[860] => case 'hide':
[861] => $this->hide($elements, $val);
[862] =>
[863] => break;
[864] =>
[865] => case 'addClass':
[866] => $this->addClass($elements, $val);
[867] =>
[868] => break;
[869] =>
[870] => case 'removeClass':
[871] => $this->removeClass($elements, $val);
[872] =>
[873] => break;
[874] =>
[875] => case 'addNewAttribute':
[876] => $this->addNewAttribute($elements, $val);
[877] =>
[878] => break;
[879] =>
[880] => case 'removeAttribute':
[881] => $this->removeAttribute($elements, $val);
[882] =>
[883] => break;
[884] =>
[885] => case '':
[886] => $this->innerHTML($elements, $val);
[887] =>
[888] => break;
[889] =>
[890] => default:
[891] => $this->setAttribute($elements, $modifier, $val);
[892] => }
[893] => }
[894] =>
[895] => /**
[896] => * @return void
[897] => */
[898] => /**
[899] => * Expands comma-separated selector lines ("a, b|mod = value") into one
[900] => * selector entry per selector, preserving the modifier and value.
[901] => */
[902] => private function expandCommaSelectors(): void {
[903] => $count = count($this->selectors);
[904] => for ($i = 0; $i < $count; $i++) {
[905] => $data = $this->selectors[$i];
[906] => //$data[0] = selector
[907] => if (str_contains($data[0], ',')) {
[908] => $selectorComponents = explode('|', $data[0]);
[909] => $selector = $selectorComponents[0];
[910] => $modifier = (isset($selectorComponents[1])) ? trim($selectorComponents[1]) : '';
[911] =>
[912] => $selectors = explode(',', $selector);
[913] => $newSelectors = [];
[914] => foreach ($selectors as $selector) {
[915] => $newSelectors[] = [trim($selector) . ($modifier ? '|' . $modifier : ''), $data[1] ?? ''];
[916] => }
[917] =>
[918] => $newCount = count($newSelectors) - 1;
[919] =>
[920] => array_splice($this->selectors, $i, 1, $newSelectors);
[921] => $count += $newCount;
[922] => $i += $newCount;
[923] => }
[924] => }
[925] => }
[926] =>
[927] => private function processTemplate(): void {
[928] => if (isset($this->selectors)) {
[929] => //check for multiple selectors
[930] =>
[931] => $this->expandCommaSelectors();
[932] =>
[933] => foreach ($this->selectors as &$data) {
[934] => $selector = $data[0];
[935] => $selectorComponents = explode('|', $selector);
[936] => $selector = $selectorComponents[0];
[937] => $modifier = (isset($selectorComponents[1])) ? trim($selectorComponents[1]) : '';
[938] => $value = (isset($data[1])) ? $data[1] : '';
[939] => $this->externalElements = false;
[940] =>
[941] => //enable disable debugging
[942] => if (!$selector) {
[943] => continue;
[944] => }
[945] => $isConstant = false;
[946] =>
[947] => if (str_contains($selector, '@@_CONSTANT_')) {
[948] => $isConstant = true;
[949] => }
[950] =>
[951] => $prefix = &$this->prefix;
[952] => $isPrefix = false;
[953] =>
[954] => /**
[955] => get all set prefix and save values
[956] => @my-prefix = #selector .class[attribute]
[957] => */
[958] => $selector = preg_replace_callback(
[959] => '/^@([a-zA-Z][a-zA-Z0-9_-]+)(?![a-zA-Z0-9_-])\s+=\s+(.+)$/',
[960] => function($matches) use (&$prefix, &$isPrefix) {
[961] => $name = $matches[1];
[962] => $value = $matches[2];
[963] =>
[964] => $prefix[$name] = $value;
[965] => $isPrefix = true;
[966] =>
[967] => return trim($matches[0]);
[968] => },
[969] => $selector
[970] => );
[971] =>
[972] => //if the line is only for set prefix then skip further processing
[973] => if ($isPrefix) {
[974] => continue;
[975] => }
[976] => //replace all prefix with actual values
[977] => $selector = preg_replace_callback(
[978] => '/@([a-zA-Z][a-zA-Z0-9_-]+)(?![a-zA-Z0-9_-])/',
[979] => function($matches) use (&$prefix) {
[980] => $name = $matches[1];
[981] =>
[982] => if (isset($prefix[$name])) {
[983] => return $prefix[$name];
[984] => }
[985] =>
[986] => return trim($matches[0]);
[987] => },
[988] => $selector
[989] => );
[990] =>
[991] => $this->debug->log('SELECTOR', $selector);
[992] =>
[993] => $val = $value;
[994] =>
[995] => if ($selector === 'debug') {
[996] => $this->debug->enable(($value === 'true') ? true : false);
[997] => } else {
[998] => $valueElements = explode('-', $value);
[999] =>
[1000] => switch ($valueElements[0]) {
[1001] => case 'replace_string':
[1002] => $val = $value;
[1003] => $index = (int)$valueElements[1];
[1004] =>
[1005] => if (isset($this->strings[$index])) {
[1006] => $val = trim($this->strings[$index], '"\'');
[1007] => $this->debug->log('SELECTOR_STRING', $this->strings[$index]);
[1008] => } else {
[1009] => $this->debug->log('SELECTOR_STRING', "$index not found for $value");
[1010] => }
[1011] =>
[1012] => break;
[1013] =>
[1014] => case 'replace_php_code':
[1015] => $phpCode = $this->phpCode[(int)$valueElements[1]] ?? '';
[1016] => /*
[1017] => if (VTPL_PHP_MINIFY === true) {
[1018] => $phpCode = $this->minifyPhp($phpCode);
[1019] => }
[1020] => */
[1021] =>
[1022] => if ($modifier && !in_array($modifier, $this->modifiers)) {
[1023] => $val = '<_script language="php">minifyPhp($phpCode) . ']]>';
[1024] => } else {
[1025] => if ($modifier === 'if_exists' || $modifier === 'hide') {
[1026] => //these modifiers take a boolean expression — strip a
[1027] => //statement wrapper (echo/print + trailing ;) so ifExists()
[1028] => //never wraps statements in isset()
[1029] => $expr = trim($phpCode);
[1030] => $expr = preg_replace('/^\s*(?:echo|print)\s+/s', '', $expr) ?? $expr;
[1031] => $expr = preg_replace('/\s*;\s*$/s', '', $expr) ?? $expr;
[1032] => $val = str_contains($expr, ';') ? "({$phpCode})" : "({$expr})";
[1033] => } else {
[1034] => $val = VTPL_PHP_CDATA_OPEN . $this->minifyPhp($phpCode) . VTPL_PHP_CDATA_CLOSE;
[1035] => }
[1036] => }
[1037] => $this->debug->log('SELECTOR_PHP', $phpCode);
[1038] =>
[1039] => break;
[1040] =>
[1041] => case 'replace_variable':
[1042] => $variable = $this->variables[(int)$valueElements[1]] ?? '';
[1043] => //variable chains keep the classic isset() guard; composite
[1044] => //expressions ($x ?? '', ternaries, calls …) are evaluated
[1045] => //directly — isset() is invalid on expressions (php fatal)
[1046] => $isChain = (bool)preg_match('/^\$[a-zA-Z_][a-zA-Z0-9_]*(?:\s*(?:->\s*\w+|\[(?:\'[^\']*\'|"[^"]*"|[^\[\]]*)\]))*$/s', trim($variable));
[1047] => $evalExpr = VTPL_PHP_CDATA_OPEN . '$_vtpl_v = (' . $variable . '); if ($_vtpl_v !== null && $_vtpl_v !== \'\') echo htmlspecialchars((string) $_vtpl_v);' . VTPL_PHP_CDATA_CLOSE;
[1048] => if ($modifier) {
[1049] => if ($modifier === 'if_exists' || $modifier === 'hide') {
[1050] => $val = $variable;
[1051] => } elseif (!in_array($modifier, $this->modifiers) || $modifier === 'addClass') {
[1052] => $val = $isChain
[1053] => ? VTPL_PHP_CDATA_OPEN . 'if (isset(' . $variable . ')) echo htmlspecialchars(' . $variable . ');' . VTPL_PHP_CDATA_CLOSE
[1054] => : $evalExpr;
[1055] => }
[1056] => } else {
[1057] => if ($isChain) {
[1058] => $echo = $isConstant ? 'echo ' . $variable : 'echo htmlspecialchars(' . $variable . ')';
[1059] => $val = VTPL_PHP_CDATA_OPEN . 'if (isset(' . $variable . ')) ' . $echo . ';' . VTPL_PHP_CDATA_CLOSE;
[1060] => } else {
[1061] => $val = $evalExpr;
[1062] => }
[1063] => }
[1064] =>
[1065] => $this->debug->log('SELECTOR_VARIABLE', $variable);
[1066] =>
[1067] => break;
[1068] =>
[1069] => case 'replace_from':
[1070] => $this->externalElements = true;
[1071] => $val = $valueElements;
[1072] =>
[1073] => break;
[1074] => }
[1075] =>
[1076] => if ($isConstant) {
[1077] => $this->constants[$selector] = $val;
[1078] =>
[1079] => continue;
[1080] => }
[1081] =>
[1082] => $elements = $this->query($selector);
[1083] =>
[1084] => if (!$elements) {
[1085] => $this->debug->log(0, ' [empty]');
[1086] => } else {
[1087] => if ($elements->length === 0) {
[1088] => $this->debug->log(0, ' [0 elements]');
[1089] => } else {
[1090] => $this->debug->log(0, " [{$elements->length} elements]");
[1091] => }
[1092] => }
[1093] =>
[1094] => $this->debug->log('SELECTOR_VARIABLE', $selector . ' - ' . $modifier);
[1095] =>
[1096] => $this->processElements($modifier, $elements, $val);
[1097] => }
[1098] => }
[1099] => }
[1100] => }
[1101] =>
[1102] => /**
[1103] => * Replace $$N placeholders in a filter command string with option values.
[1104] => * Non-zero indices are quoted as string literals.
[1105] => *
[1106] => * @param string $command
[1107] => * @param array $options
[1108] => *
[1109] => * @return string
[1110] => */
[1111] => private function replaceFilterVars(string $command, array &$options): string {
[1112] => return preg_replace_callback(
[1113] => '@\$\$(\d+)@',
[1114] => function($matches) use (&$options) {
[1115] => $index = (int)$matches[1];
[1116] =>
[1117] => if ($index > 0) {
[1118] => $options[$index] = '\'' . trim($options[$index], '\'') . '\'';
[1119] => }
[1120] =>
[1121] => return $options[$index];
[1122] => },
[1123] => $command
[1124] => );
[1125] => }
[1126] =>
[1127] => /*
[1128] => Process data-filter-* attributes defined in filters array.
[1129] => Process attributes with json and transforms them to php arrays ex: data-my-data='{var:"value"}'
[1130] => Process macro definitions like @@macro Mymacro('var1', 'var2') Mymacro must be a function defined with name vtplMymacro and must accept two variables like in definition
[1131] => Process json path, for a node with data-v-myjson='{var:{subvar1:val1, subvar2:val2}}' @@myjson.var.subvar1@@ will return val1
[1132] => */
[1133] =>
[1134] => /**
[1135] => * @param string $value
[1136] => * @param Dom\HTMLElement|DOMNode &$node
[1137] => *
[1138] => * @return string
[1139] => */
[1140] => private function processAttributeFilters(string $value, Dom\HTMLElement|DOMNode &$node): string {
[1141] => /*
[1142] => filters
[1143] => search for filters and their options
[1144] => @filter_([^ :$]+):?(\'[^\']+\'|[^ $]+)?@ old
[1145] => */
[1146] => $filters = [];
[1147] => $length = $node->attributes->length;
[1148] =>
[1149] => for ($i = 0; $i < $length; $i++) {
[1150] => if ($item = $node->attributes->item($i)) {
[1151] => $name = $item->name;
[1152] =>
[1153] => if (str_contains($name, 'data-filter')) {
[1154] => $name = str_replace('data-filter-', '', $name);
[1155] => $filters[$name] = $item->value;
[1156] => //$node->removeAttribute($item->name);
[1157] => }
[1158] => }
[1159] => }
[1160] =>
[1161] => //if ($class && preg_match_all('@filter_([^ :$]+)(:\'[^\']+\'|:[^ $]+)*@', $class, $matches, PREG_SET_ORDER) > 0)
[1162] => if ($filters) {
[1163] => $chain = '_$variable';
[1164] =>
[1165] => foreach ($filters as $name => $options) {
[1166] => if ($options) {
[1167] => //string is json
[1168] => if ($options[0] === '{'/* || $options[0] == '['*/) {
[1169] => $options = json_decode($options, false);
[1170] => } else {
[1171] => $options = [$options];
[1172] => }
[1173] => } else {
[1174] => $options = [];
[1175] => }
[1176] =>
[1177] => //clean up, remove filter from attribute
[1178] => if (isset($this->variableFilters[$name])) {
[1179] => $type = '';
[1180] => $commands = $this->variableFilters[$name];
[1181] =>
[1182] => if (is_array($commands)) {
[1183] => $type = $commands[0];
[1184] => unset($commands[0]);
[1185] => } else {
[1186] => $commands = [1 => $commands];
[1187] => }
[1188] =>
[1189] => if (!$commands) {
[1190] => continue;
[1191] => }
[1192] =>
[1193] => foreach ($commands as &$command) {
[1194] => $commandVariableCount = preg_match_all('@\$\$[1-9]+@', $command);
[1195] =>
[1196] => //if different parameter number then don't add filter to filter chain
[1197] => if ($commandVariableCount != count($options)) {
[1198] => $this->debug->log('warning', 'Invalid number of options for filter ' . $name . ' for "' . $name . '"');
[1199] =>
[1200] => continue 2;
[1201] => }
[1202] =>
[1203] => //run php functions if any
[1204] => $command = preg_replace_callback(
[1205] => '/@@__VTPL_([^_]+)__@@/',
[1206] => fn ($matches) => eval('return ' . $matches[1] . ';'),
[1207] => $command
[1208] => );
[1209] => }
[1210] =>
[1211] => if ($type === 'class') {
[1212] => $command = $this->replaceFilterVars($command, $options);
[1213] => $this->addNodeClass($node, trim($commands[1], '\''), false);
[1214] => } else {
[1215] => if ($type === 'tag') {
[1216] => $command = $this->replaceFilterVars($command, $options);
[1217] => $openTag = $this->replaceFilterVars($commands[1], $options);
[1218] => $closeTag = $this->replaceFilterVars($commands[2], $options);
[1219] =>
[1220] => $nodeList = [$node]; //only one node and the methods accepts multiple nodes
[1221] => $this->tagWrap($nodeList, $openTag, $closeTag);
[1222] => } else {
[1223] => if (is_array($options)) {
[1224] => array_unshift($options, $chain);
[1225] => } else {
[1226] => $options[] = $chain;
[1227] => }
[1228] =>
[1229] => $chain = $this->replaceFilterVars($commands[1], $options);
[1230] => }
[1231] => }
[1232] => } else {
[1233] => $this->debug->log('warning', 'Unknown filter ' . $name . ' for "' . $name . '"');
[1234] => }
[1235] => }
[1236] =>
[1237] => preg_match('@echo htmlspecialchars\(([^)]+)\)@', $value, $variable);
[1238] =>
[1239] => if ($variable) {
[1240] => $chain = str_replace('_$variable', $variable[1], $chain);
[1241] => $value = str_replace($variable[0], 'echo htmlspecialchars(' . $chain . ')', $value);
[1242] => } else {
[1243] => preg_match('@echo\(([^)]+)\)@', $value, $variable);
[1244] =>
[1245] => if ($variable) {
[1246] => $chain = str_replace('_$variable', $variable[1], $chain);
[1247] => $value = str_replace($variable[0], 'echo(' . $chain . ')', $value);
[1248] => }
[1249] => }
[1250] => }
[1251] =>
[1252] => return $value;
[1253] => }
[1254] =>
[1255] => /*
[1256] => Replace placeholders in values
[1257] => Ex;
[1258] => // @@__innerText__@@ will be replaced with the text already present in the tag that has data-v-product-name attribute
[1259] => [data-v-product-name] =
[1260] =>
[1261] => Available placeholders:
[1262] => @@__innerText__@@ - inner html of the node
[1263] => @@__innerText__@@ - inner text of the node
[1264] => @@__my-attribute__@@ - value of my-attribute of current node
[1265] => @@__data-v-plugin-(.+)__@@ - run regex and return first match \1 for example for data-v-plugin-name it will return `name`
[1266] => @@__my-*:my-(*)__@@ - get attribute name that starts with 'my-' and run the regex after ':' used to extract attribute name from current node
[1267] =>
[1268] => Process data-filter-* attributes defined in filters array.
[1269] => Process attributes with json and transforms them to php arrays ex: data-my-data='{var:"value"}'
[1270] => Process macro definitions like @@macro Mymacro('var1', 'var2') Mymacro must be a function defined with name vtplMymacro and must accept two variables like in definition
[1271] => Process json path, for a node with data-v-myjson='{var:{subvar1:val1, subvar2:val2}}' @@myjson.var.subvar1@@ will return val1
[1272] => */
[1273] =>
[1274] => /**
[1275] => * Expand a JSON-looking value ({...}) to PHP array syntax via var_export.
[1276] => *
[1277] => * Shared by the @@__innerText__@@, @@__innerHTML__@@ and attribute
[1278] => * placeholder expansions in processAttributeConstants().
[1279] => *
[1280] => * @param string|null $value
[1281] => *
[1282] => * @return string|null
[1283] => */
[1284] => private function expandJsonValue(?string $value): ?string {
[1285] => if (isset($value[0]) && $value[0] === '{') {
[1286] => $value = var_export(json_decode($value, true), true);
[1287] => }
[1288] =>
[1289] => return $value;
[1290] => }
[1291] =>
[1292] => /**
[1293] => * @param string $value
[1294] => * @param Dom\HTMLElement|DOMNode $node
[1295] => *
[1296] => * @return string
[1297] => */
[1298] => private function processAttributeConstants(string $value, Dom\HTMLElement|DOMNode $node): string {
[1299] => $value = preg_replace_callback(
[1300] => '/@@__innerText__@@/',
[1301] => function($matches) use ($node) {
[1302] => $value = $this->innerText([$node]);
[1303] =>
[1304] => $value = $this->expandJsonValue($value);
[1305] =>
[1306] => $this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);
[1307] =>
[1308] => return trim($value);
[1309] => },
[1310] => $value
[1311] => );
[1312] =>
[1313] => $value = preg_replace_callback(
[1314] => '/@@__innerHTML__@@/',
[1315] => function($matches) use ($node) {
[1316] => $value = $this->innerHTML([$node]);
[1317] =>
[1318] => $value = $this->expandJsonValue($value);
[1319] =>
[1320] => $this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);
[1321] =>
[1322] => return $value;
[1323] => },
[1324] => $value
[1325] => );
[1326] =>
[1327] => //attribute value
[1328] => $value = preg_replace_callback(
[1329] => '/@@__([\.a-zA-Z*_-]+)__@@/',
[1330] => function($matches) use ($node) {
[1331] => $attributeName = $matches[1];
[1332] =>
[1333] => if (str_contains($attributeName, '*')) {
[1334] => //wildcard attribute
[1335] => $attributeName = str_replace('*', '', $attributeName);
[1336] =>
[1337] => $value = '';
[1338] =>
[1339] => foreach ($node->attributes as $attribute) {
[1340] => if (str_contains($attribute->name, $attributeName)) {
[1341] => $value = $attribute->value;
[1342] => }
[1343] => }
[1344] => } else {
[1345] => $value = $node->getAttribute($matches[1]);
[1346] => }
[1347] =>
[1348] => $value = $this->expandJsonValue($value);
[1349] =>
[1350] => $this->debug->log('VTPL_ATTRIBUTE', 'VALUE ' . $value);
[1351] =>
[1352] => return $value;
[1353] => },
[1354] => $value
[1355] => );
[1356] =>
[1357] => //run regex on attribute name @@__data-v-product-*:data-v-product-(*)__@@
[1358] => $value = preg_replace_callback(
[1359] => '/@@__([*a-zA-Z_-]+):([a-zA-Z-_\]\[\\\+\(\)\,\.\+\^:*]+)__@@/',
[1360] => function($matches) use ($node) {
[1361] => $attrib = $matches[1];
[1362] => $regex = $matches[2];
[1363] => $this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB NAME ' . $attrib);
[1364] => $this->debug->log('VTPL_ATTRIBUTE', 'REGEX ' . $regex);
[1365] => $value = $node->getAttribute($attrib);
[1366] => $this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB VALUE ' . $value);
[1367] =>
[1368] => foreach ($node->attributes as $name => $attrNode) {
[1369] => if (preg_match('@' . $regex . '@', $value, $_match)) {
[1370] => //$value = \Vvveb\System\filter('@[0-9a-zA-Z_\-\.\#\/]+@', $_match[1], 500);
[1371] => $value = $_match[1];
[1372] => $this->debug->log('VTPL_ATTRIBUTE', 'MATCH ' . $_match[1]);
[1373] => } else {
[1374] => $this->debug->log('VTPL_ATTRIBUTE', 'NO MATCH ' . $regex . ' - ' . $attrib . ' - ' . $attrNode->name);
[1375] => }
[1376] => }
[1377] =>
[1378] => return $value;
[1379] => },
[1380] => $value
[1381] => );
[1382] =>
[1383] => //attribute name ex @@__data-v-plugin-(.+)__@@
[1384] => $value = preg_replace_callback(
[1385] => '/@@__(.+?)__@@/',
[1386] => function($matches) use ($node) {
[1387] => $value = $node->getAttribute($matches[1]);
[1388] => $this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB NAME ' . $matches[1]);
[1389] => //expand shorthand expression (*) to regex ([a-zA-Z_0-9-]+)
[1390] => $regex = str_replace('(*)', '([a-zA-Z_0-9-\.]+)', $matches[1]);
[1391] =>
[1392] => foreach ($node->attributes as $name => $attrNode) {
[1393] => if (preg_match("@$regex@", $name, $_match)) {
[1394] => //$value = \Vvveb\System\filter('@[0-9a-zA-Z_\-\.\#\/]+@', $_match[1], 500);
[1395] => $value = $_match[1] ?? null;
[1396] => $this->debug->log('VTPL_ATTRIBUTE', 'MATCH ' . $value);
[1397] => } else {
[1398] => $this->debug->log('VTPL_ATTRIBUTE', 'NO MATCH ');
[1399] => }
[1400] => }
[1401] =>
[1402] => return $value;
[1403] => },
[1404] => $value
[1405] => );
[1406] =>
[1407] => $json = [];
[1408] =>
[1409] => //check if attribute value is json string
[1410] => if ($node->hasAttributes()) {
[1411] => foreach ($node->attributes as $attr) {
[1412] => $name = str_replace('data-v-', '', $attr->nodeName);
[1413] => $val = $attr->nodeValue;
[1414] =>
[1415] => if ($val && ($val[0] === '{'/* || $val[0] == '['*/)) {
[1416] => $json[$name] = json_decode($val, true);
[1417] => }
[1418] => }
[1419] => }
[1420] =>
[1421] => $value = preg_replace_callback(
[1422] => '/@@([\.a-zA-Z_-]+)@@/m',
[1423] => function($matches) use ($node, $json) {
[1424] => $attrib = var_export(\Vvveb\arrayPath($json, $matches[1]), true);
[1425] => $this->debug->log('VTPL_ATTRIBUTE', 'JSON NAME ' . $attrib);
[1426] => $value = $node->getAttribute($attrib);
[1427] => $this->debug->log('VTPL_ATTRIBUTE', 'ATTRIB VALUE ' . $value);
[1428] =>
[1429] => return $attrib;
[1430] => },
[1431] => $value
[1432] => );
[1433] =>
[1434] => //macros, compile time function calls
[1435] => $value = preg_replace_callback(
[1436] => '/@@macro ([a-z_A-Z]+)\((.+?)\)@@/',
[1437] => function($matches) use (&$node) {
[1438] => $function = 'vtpl' . $matches[1];
[1439] => //$parameters = preg_split('@\'?\s*,\s*\'?@', $matches[2]);
[1440] =>
[1441] => if (function_exists($function)) {
[1442] => preg_match_all('@"(.*?)",?@i', $matches[2], $parameters, PREG_SET_ORDER);
[1443] => //add node as first parameter to allow macros to alter node if needed
[1444] => $params[] = &$this;
[1445] => $params[] = &$node;
[1446] =>
[1447] => foreach ($parameters as $param) {
[1448] => $params[] = trim($param[1]);
[1449] => }
[1450] =>
[1451] => // $params_exp = var_export($params, true);
[1452] => return $function(...$params);
[1453] => }
[1454] =>
[1455] => return $matches[0];
[1456] => },
[1457] => $value
[1458] => );
[1459] =>
[1460] => return $this->processAttributeFilters($value, $node);
[1461] => }
[1462] =>
[1463] => /**
[1464] => * @param Dom\HTMLElement|DOMNode &$node
[1465] => *
[1466] => * @return void
[1467] => */
[1468] => /**
[1469] => * Detach a node from the document after emptying it.
[1470] => *
[1471] => * Shared by the delete* modifiers; the parent check makes it safe to
[1472] => * call on already-detached nodes.
[1473] => *
[1474] => * @param Dom\HTMLElement|DOMNode &$node
[1475] => *
[1476] => * @return void
[1477] => */
[1478] => private function removeNode(Dom\HTMLElement|DOMNode &$node): void {
[1479] => $this->removeChildren($node);
[1480] =>
[1481] => if ($node->parentNode) {
[1482] => $node->parentNode->removeChild($node);
[1483] => }
[1484] => }
[1485] =>
[1486] => /**
[1487] => * @param Dom\HTMLElement|DOMNode &$node
[1488] => *
[1489] => * @return void
[1490] => */
[1491] => private function removeChildren(Dom\HTMLElement|DOMNode &$node): void {
[1492] => while ($node->firstChild) {
[1493] => $node->removeChild($node->firstChild);
[1494] => }
[1495] => }
[1496] =>
[1497] => /**
[1498] => * Set a node's value based on its tag type.
[1499] => * - input → value attribute
[1500] => * - iframe/video/audio/source → src attribute
[1501] => * - form → action attribute
[1502] => * - default → innerHTML via document fragment
[1503] => *
[1504] => * @param DOMNode $node
[1505] => * @param string $html
[1506] => *
[1507] => * @return void
[1508] => */
[1509] => private function setNodeValueByType(Dom\HTMLElement|DOMNode $node, string $html): void {
[1510] => switch ($node->nodeName) {
[1511] => case 'input':
[1512] => $this->setAttribute($node, 'value', $html);
[1513] =>
[1514] => break;
[1515] =>
[1516] => case 'iframe':
[1517] => case 'video':
[1518] => case 'audio':
[1519] => case 'source':
[1520] => $this->setAttribute($node, 'src', $html);
[1521] =>
[1522] => break;
[1523] =>
[1524] => case 'form':
[1525] => $this->setAttribute($node, 'action', $html);
[1526] =>
[1527] => break;
[1528] =>
[1529] => default:
[1530] => $this->removeChildren($node);
[1531] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1532] => if ($f) {
[1533] => $node->appendChild($f);
[1534] => }
[1535] => }
[1536] => }
[1537] =>
[1538] => /**
[1539] => * @param array|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList
[1540] => * @param false|string $html
[1541] => *
[1542] => * @return string|null
[1543] => */
[1544] => private function innerHTML(array|Dom\NodeList|DOMNodeList $nodeList, false|string $html = false): ?string {
[1545] => if ($nodeList) {
[1546] => foreach ($nodeList as $node) {
[1547] => if ($html === false) {
[1548] => $doc = new DOMDocument();
[1549] =>
[1550] => foreach ($node->childNodes as $child) {
[1551] => $doc->appendChild($doc->importNode($child, true));
[1552] => }
[1553] =>
[1554] => return $this->saveDocument($doc);
[1555] => } else {
[1556] => if ($html === '') {
[1557] => continue;
[1558] => }
[1559] =>
[1560] => if ($this->externalElements) {
[1561] => $result = $this->loadFromExternalHtml($html, $node);
[1562] => $this->removeChildren($node);
[1563] =>
[1564] => foreach ($result as $externalNode) {
[1565] => $importedNode = $this->document->importNode($externalNode, true);
[1566] => $node->appendChild($importedNode);
[1567] => }
[1568] => } else {
[1569] => $this->setNodeValueByType($node, $html);
[1570] => }
[1571] => }
[1572] => }
[1573] => }
[1574] =>
[1575] => return null;
[1576] => }
[1577] =>
[1578] => /**
[1579] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1580] => * @param array|string|null $html
[1581] => *
[1582] => * @return void
[1583] => */
[1584] => private function outerHTML(null|Dom\NodeList|DOMNodeList &$nodeList, null|array|string $html = null): void {
[1585] => foreach ($nodeList as $node) {
[1586] => if ($html === '') {
[1587] => continue;
[1588] => }
[1589] =>
[1590] => if ($this->externalElements) {
[1591] => $result = $this->loadFromExternalHtml($html, $node);
[1592] =>
[1593] => $parent = $node->parentNode;
[1594] => $count = 0;
[1595] =>
[1596] => if ($result) {
[1597] => foreach ($result as $externalNode) {
[1598] => $importedNode = $this->document->importNode($externalNode, true);
[1599] =>
[1600] => if ($parent) {
[1601] => if ($count) {
[1602] => $parent->appendChild($importedNode);
[1603] => } else {
[1604] => $parent->replaceChild($importedNode, $node);
[1605] => }
[1606] => $node = $importedNode;
[1607] => $count++;
[1608] => }
[1609] => }
[1610] => }
[1611] => } else {
[1612] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1613] => if ($f) {
[1614] => $node->parentNode->replaceChild($f, $node);
[1615] => }
[1616] => }
[1617] => }
[1618] => }
[1619] =>
[1620] => /**
[1621] => * @param list|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null $nodeList
[1622] => * @param string|null $text
[1623] => *
[1624] => * @return string|null
[1625] => */
[1626] => private function innerText(array|Dom\NodeList|DOMNodeList|null $nodeList, ?string $text = null): ?string {
[1627] => foreach ($nodeList as $node) {
[1628] => if ($text === null) {
[1629] => return $node->nodeValue;
[1630] => } else {
[1631] => if ($node->hasChildNodes()) {
[1632] => foreach ($node->childNodes as $childNode) {
[1633] => $value = trim($childNode->nodeValue);
[1634] =>
[1635] => if ($childNode->nodeType === XML_TEXT_NODE && $value !== '') {
[1636] => $f = $this->createXmlFragment($this->processAttributeConstants($text, $node));
[1637] =>
[1638] => if ($f) {
[1639] => $node->replaceChild($f, $childNode);
[1640] => }
[1641] =>
[1642] => break;
[1643] => }
[1644] => }
[1645] => } else {
[1646] => $this->setNodeValueByType($node, $text);
[1647] => }
[1648] => }
[1649] => }
[1650] =>
[1651] => return null;
[1652] => }
[1653] =>
[1654] => /**
[1655] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1656] => * @param false|string $variable
[1657] => *
[1658] => * @return void
[1659] => */
[1660] => private function ifExists(null|Dom\NodeList|DOMNodeList &$nodeList, false|string $variable = false): void {
[1661] => if ($variable === '') {
[1662] => return;
[1663] => }
[1664] =>
[1665] => foreach ($nodeList as $node) {
[1666] => $condition = $this->processAttributeConstants($variable, $node);
[1667] =>
[1668] => //only variable/property chains may go through isset() — boolean
[1669] => //expressions (empty(), &&, calls …) are used as-is, keeping their
[1670] => //'!' intact (blanket str_replace would corrupt them)
[1671] => $probe = trim($condition, " \t\n\r()");
[1672] => if (preg_match('/^!?\s*\$[a-zA-Z_][a-zA-Z0-9_]*(?:->\w+|\[(?:\'[^\']*\'|"[^"]*"|\d+|[\w\$]+)\])*$/', $probe)) {
[1673] => $isset = str_replace('!', '', $condition);
[1674] => $guard = "isset($isset) && $condition";
[1675] => } else {
[1676] => $guard = $condition;
[1677] => }
[1678] =>
[1679] => //before
[1680] => $f = $this->createXmlFragment(VTPL_PHP_CDATA_OPEN . "if ($guard) {" . VTPL_PHP_CDATA_CLOSE);
[1681] => if ($f) {
[1682] => $node->parentNode->insertBefore($f, $node);
[1683] => }
[1684] =>
[1685] => //after
[1686] => $f = $this->createXmlFragment(VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE);
[1687] => if ($f) {
[1688] => $node->parentNode->insertBefore($f, $node->nextSibling);
[1689] => }
[1690] => }
[1691] => }
[1692] =>
[1693] => /**
[1694] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1695] => * @param false|string $variable
[1696] => *
[1697] => * @return void
[1698] => */
[1699] => private function hide(null|Dom\NodeList|DOMNodeList &$nodeList, false|string $variable = false): void {
[1700] => if ($variable) {
[1701] => $variable = '!' . $variable;
[1702] => }
[1703] =>
[1704] => $this->ifExists($nodeList, $variable);
[1705] => }
[1706] =>
[1707] => /**
[1708] => * @param list|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1709] => * @param false|string $open
[1710] => * @param false|string $close
[1711] => *
[1712] => * @return bool
[1713] => */
[1714] => private function tagWrap(array|Dom\NodeList|DOMNodeList|null &$nodeList, false|string $open = false, false|string $close = false): bool {
[1715] => if ($open === '' || $close === '') {
[1716] => return false;
[1717] => }
[1718] => $openStart = VTPL_PHP_CDATA_OPEN . $open . VTPL_PHP_CDATA_CLOSE;
[1719] => $openEnd = VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE;
[1720] =>
[1721] => $closeStart = VTPL_PHP_CDATA_OPEN . $close . VTPL_PHP_CDATA_CLOSE;
[1722] => $closeEnd = VTPL_PHP_OPEN . '}' . VTPL_PHP_CLOSE;
[1723] =>
[1724] => foreach ($nodeList as $node) {
[1725] => //before start
[1726] => $f = $this->createXmlFragment($this->processAttributeConstants($openStart, $node));
[1727] => if ($f) {
[1728] => $node->parentNode->insertBefore($f, $node);
[1729] => }
[1730] =>
[1731] => //before end
[1732] => $f = $this->createXmlFragment($this->processAttributeConstants($openEnd, $node));
[1733] =>
[1734] => if ($node->hasChildNodes()) {
[1735] => if ($f) {
[1736] => $node->insertBefore($f, $node->firstChild);
[1737] => }
[1738] => } else {
[1739] => if ($f) {
[1740] => $node->appendChild($f);
[1741] => }
[1742] => }
[1743] =>
[1744] => //after start
[1745] => $f = $this->createXmlFragment($this->processAttributeConstants($closeStart, $node));
[1746] => if ($f) {
[1747] => $node->appendChild($f);
[1748] => }
[1749] =>
[1750] => //after end
[1751] => $f = $this->createXmlFragment($this->processAttributeConstants($closeEnd, $node));
[1752] => if ($f) {
[1753] => $node->parentNode->insertBefore($f, $node->nextSibling);
[1754] => }
[1755] => }
[1756] =>
[1757] => return true;
[1758] => }
[1759] =>
[1760] => /**
[1761] => * Resolve external elements for insert/append operations.
[1762] => * If the from() reference is @_SELF_@, queries the current document;
[1763] => * otherwise loads from an external HTML file.
[1764] => *
[1765] => * @param array|string $html
[1766] => * @param DOMNode $node
[1767] => *
[1768] => * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false
[1769] => */
[1770] => private function resolveExternalElements(array|string $html, Dom\HTMLElement|DOMNode $node): Dom\NodeList|DOMNodeList|false {
[1771] => if ($this->froms && $this->froms[0][(int)$html[1]] === '@_SELF_@') {
[1772] => $selector = $this->froms[2][(int)$html[1]];
[1773] =>
[1774] => return $this->query($selector);
[1775] => }
[1776] =>
[1777] => return $this->loadFromExternalHtml($html, $node);
[1778] => }
[1779] =>
[1780] => /**
[1781] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1782] => * @param array|string $html
[1783] => *
[1784] => * @return bool
[1785] => */
[1786] => private function insertBefore(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {
[1787] => if ($html === '') {
[1788] => return false;
[1789] => }
[1790] =>
[1791] => if ($nodeList) {
[1792] => foreach ($nodeList as $node) {
[1793] => if ($this->externalElements) {
[1794] => $result = $this->resolveExternalElements($html, $node);
[1795] =>
[1796] => if (!$result) {
[1797] => continue;
[1798] => }
[1799] => foreach ($result as $externalNode) {
[1800] => $importedNode = $this->document->importNode($externalNode, true);
[1801] => $node->parentNode->insertBefore($importedNode, $node);
[1802] => }
[1803] => } else {
[1804] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1805] => if ($f) {
[1806] => $node->parentNode->insertBefore($f, $node);
[1807] => }
[1808] => }
[1809] => }
[1810] =>
[1811] => return true;
[1812] => }
[1813] =>
[1814] => return false;
[1815] => }
[1816] =>
[1817] => /**
[1818] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1819] => * @param array|string $html
[1820] => *
[1821] => * @return bool
[1822] => */
[1823] => private function insertAfter(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {
[1824] => if ($html === '') {
[1825] => return false;
[1826] => }
[1827] =>
[1828] => if ($nodeList) {
[1829] => foreach ($nodeList as $node) {
[1830] => if ($this->externalElements) {
[1831] => $result = $this->resolveExternalElements($html, $node);
[1832] =>
[1833] => if (!$result) {
[1834] => continue;
[1835] => }
[1836] => foreach ($result as $externalNode) {
[1837] => $importedNode = $this->document->importNode($externalNode, true);
[1838] =>
[1839] => //importNode returns the same node for nodes already in this document;
[1840] => //when the external element is $node's own nextSibling, inserting it
[1841] => //before itself throws "Cannot add newnode as the previous sibling of refnode".
[1842] => //In that case it is already in the correct position, so skip the move.
[1843] => if ($importedNode === $node->nextSibling) {
[1844] => continue;
[1845] => }
[1846] => $node->parentNode->insertBefore($importedNode, $node->nextSibling);
[1847] => }
[1848] => } else {
[1849] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1850] => if ($f) {
[1851] => $node->parentNode->insertBefore($f, $node->nextSibling);
[1852] => }
[1853] => }
[1854] => }
[1855] =>
[1856] => return true;
[1857] => }
[1858] =>
[1859] => return false;
[1860] => }
[1861] =>
[1862] => /**
[1863] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1864] => * @param array|string $html
[1865] => *
[1866] => * @return bool
[1867] => */
[1868] => private function append(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {
[1869] => if ($html === '') {
[1870] => return false;
[1871] => }
[1872] =>
[1873] => if ($nodeList) {
[1874] => foreach ($nodeList as $node) {
[1875] => if ($this->externalElements) {
[1876] => $result = $this->resolveExternalElements($html, $node);
[1877] =>
[1878] => if (!$result) {
[1879] => continue;
[1880] => }
[1881] => foreach ($result as $externalNode) {
[1882] => $importedNode = $this->document->importNode($externalNode, true);
[1883] => $node->appendChild($importedNode);
[1884] => }
[1885] => } else {
[1886] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1887] => if ($f) {
[1888] => $node->appendChild($f);
[1889] => }
[1890] => }
[1891] => }
[1892] =>
[1893] => return true;
[1894] => }
[1895] =>
[1896] => return false;
[1897] => }
[1898] =>
[1899] => /**
[1900] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1901] => * @param array|string $html
[1902] => *
[1903] => * @return bool
[1904] => */
[1905] => private function prepend(null|Dom\NodeList|DOMNodeList &$nodeList, array|string $html): bool {
[1906] => if (!$html) {
[1907] => return false;
[1908] => }
[1909] =>
[1910] => if ($nodeList) {
[1911] => foreach ($nodeList as $node) {
[1912] => if ($this->externalElements) {
[1913] => $result = $this->resolveExternalElements($html, $node);
[1914] =>
[1915] => if (!$result) {
[1916] => continue;
[1917] => }
[1918] =>
[1919] => foreach ($result as $externalNode) {
[1920] => $importedNode = $this->document->importNode($externalNode, true);
[1921] =>
[1922] => if ($importedNode === $node->firstChild) {
[1923] => //self-reference already in position, avoid inserting before itself
[1924] => continue;
[1925] => }
[1926] =>
[1927] => if ($node->firstChild) {
[1928] => // $ref has an immediate sibling : insert newnode before this one
[1929] => $node->insertBefore($importedNode, $node->firstChild);
[1930] => } else {
[1931] => // $ref has no sibling next to him : insert newnode as last child of his parent
[1932] => $node->appendChild($importedNode);
[1933] => }
[1934] => }
[1935] => } else {
[1936] => $f = $this->createXmlFragment($this->processAttributeConstants($html, $node));
[1937] =>
[1938] => if ($node->firstChild) {
[1939] => // $ref has an immediate sibling : insert newnode before this one
[1940] => if ($f) {
[1941] => $node->insertBefore($f, $node->firstChild);
[1942] => }
[1943] => } else {
[1944] => // $ref has no sibling next to him : insert newnode as last child of his parent
[1945] => if ($f) {
[1946] => $node->appendChild($f);
[1947] => }
[1948] => }
[1949] => }
[1950] => }
[1951] =>
[1952] => return true;
[1953] => }
[1954] =>
[1955] => return false;
[1956] => }
[1957] =>
[1958] => /**
[1959] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1960] => *
[1961] => * @return void
[1962] => */
[1963] => private function deleteAllButFirst(null|Dom\NodeList|DOMNodeList &$nodeList): void {
[1964] => $first = true;
[1965] =>
[1966] => if ($nodeList) {
[1967] => foreach ($nodeList as $node) {
[1968] => if (!$first) {
[1969] => $this->removeNode($node);
[1970] => }
[1971] => $first = false;
[1972] => }
[1973] => }
[1974] => }
[1975] =>
[1976] => /**
[1977] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1978] => *
[1979] => * @return void
[1980] => */
[1981] => private function deleteAllButFirstChild(null|Dom\NodeList|DOMNodeList &$nodeList): void {
[1982] => $parents = [];
[1983] =>
[1984] => if ($nodeList) {
[1985] => foreach ($nodeList as $node) {
[1986] => if (in_array($node->parentNode, $parents, true)) {
[1987] => $this->removeNode($node);
[1988] => }
[1989] =>
[1990] => if ($node->parentNode) {
[1991] => $parents[] = $node->parentNode;
[1992] => }
[1993] => }
[1994] => }
[1995] => }
[1996] =>
[1997] => /**
[1998] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|null &$nodeList
[1999] => *
[2000] => * @return void
[2001] => */
[2002] => private function delete(null|Dom\NodeList|DOMNodeList &$nodeList): void {
[2003] => if ($nodeList) {
[2004] => foreach ($nodeList as $node) {
[2005] => $this->removeNode($node);
[2006] => }
[2007] => }
[2008] => }
[2009] =>
[2010] => /**
[2011] => * @param Dom\HTMLElement|DOMNode $node
[2012] => * @param string $attribute
[2013] => * @param string $val
[2014] => *
[2015] => * @return void
[2016] => */
[2017] => private function setNodeAttribute(Dom\HTMLElement|DOMNode $node, string $attribute, string $val): void {
[2018] => if (!$val) {
[2019] => return;
[2020] => }
[2021] => $value = $this->processAttributeConstants($val, $node);
[2022] => //if the attribute value has no php in it add it directly
[2023] => if ($value) {
[2024] => if (!str_contains($value, VTPL_PHP_OPEN)) {
[2025] => $node->setAttribute($attribute, $value);
[2026] => } else {
[2027] => $this->attributes[++$this->attributesIndex] = $value;
[2028] => $node->setAttribute($attribute, "@@__VTPL__ATTRIBUTE_PLACEHOLDER__{$this->attributesIndex}@@");
[2029] => }
[2030] => }
[2031] => }
[2032] =>
[2033] => /**
[2034] => * @param Dom\HTMLElement|DOMNode|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> &$nodeList
[2035] => * @param string $attribute
[2036] => * @param string $val
[2037] => *
[2038] => * @return void
[2039] => */
[2040] => private function setAttribute(Dom\HTMLElement|Dom\NodeList|DOMNode|DOMNodeList &$nodeList, string $attribute, string $val): void {
[2041] => if (is_a($nodeList, 'DOMNodeList') || is_a($nodeList, 'Dom\NodeList')) {
[2042] => if ($nodeList->length > 0) {
[2043] => foreach ($nodeList as $node) {
[2044] => /* $attr = new DOMAttr($attribute);
[2045] => $attr->value = $val;
[2046] => $node->setAttributeNodeNS($attr);*/
[2047] =>
[2048] => $this->setNodeAttribute($node, $attribute, $val);
[2049] => }
[2050] => }
[2051] => } else {
[2052] => $this->setNodeAttribute($nodeList, $attribute, $val);
[2053] => }
[2054] => }
[2055] =>
[2056] => /**
[2057] => * @param Dom\HTMLElement|DOMNode &$node
[2058] => * @param string $val
[2059] => * @param bool $processConstants
[2060] => *
[2061] => * @return void
[2062] => */
[2063] => private function addNodeClass(Dom\HTMLElement|DOMNode &$node, string $val, bool $processConstants = true): void {
[2064] => if ($processConstants) {
[2065] => $val = $this->processAttributeConstants($val, $node);
[2066] => }
[2067] =>
[2068] => $this->attributes[++$this->attributesIndex] = $val;
[2069] => $node->setAttribute('class', $node->getAttribute('class') . " @@__VTPL__ATTRIBUTE_PLACEHOLDER__{$this->attributesIndex}@@");
[2070] => }
[2071] =>
[2072] => /**
[2073] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList
[2074] => * @param string $val
[2075] => *
[2076] => * @return void
[2077] => */
[2078] => private function addClass(Dom\NodeList|DOMNodeList $nodeList, string $val): void {
[2079] => if (is_a($nodeList, 'DOMNodeList')) {
[2080] => if ($nodeList->length > 0) {
[2081] => foreach ($nodeList as $node) {
[2082] => $this->addNodeClass($node, $val);
[2083] => }
[2084] => }
[2085] => }
[2086] => }
[2087] =>
[2088] => /**
[2089] => * @param Dom\HTMLElement|DOMNode &$node
[2090] => * @param string $val
[2091] => *
[2092] => * @return void
[2093] => */
[2094] => public function addNodeNewAttribute(Dom\HTMLElement|DOMNode &$node, string $val): void {
[2095] => $this->newAttributes[++$this->newAttributesIndex] = $this->processAttributeConstants($val, $node);
[2096] => $node->setAttribute("__VTPL__NEW_ATTRIBUTE_PLACEHOLDER__{$this->newAttributesIndex}", '');
[2097] => }
[2098] =>
[2099] => /**
[2100] => * @param array|Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> &$nodeList
[2101] => * @param string $val
[2102] => *
[2103] => * @return void
[2104] => */
[2105] => public function addNewAttribute(array|Dom\NodeList|DOMNodeList &$nodeList, string $val): void {
[2106] => if ($nodeList->length > 0) {
[2107] => foreach ($nodeList as $node) {
[2108] => $this->addNodeNewAttribute($node, $val);
[2109] => }
[2110] => }
[2111] => }
[2112] =>
[2113] => /**
[2114] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList
[2115] => * @param string $val
[2116] => *
[2117] => * @return void
[2118] => */
[2119] => private function removeAttribute(Dom\NodeList|DOMNodeList $nodeList, string $val): void {
[2120] => if ($nodeList->length > 0) {
[2121] => foreach ($nodeList as $node) {
[2122] => $node->removeAttribute($val);
[2123] => }
[2124] => }
[2125] => }
[2126] =>
[2127] => /**
[2128] => * @param Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode> $nodeList
[2129] => * @param string $val
[2130] => *
[2131] => * @return void
[2132] => */
[2133] => private function removeClass(Dom\NodeList|DOMNodeList $nodeList, string $val): void {
[2134] => if ($nodeList->length > 0) {
[2135] => foreach ($nodeList as $node) {
[2136] => $class = $node->getAttribute('class');
[2137] => $class = str_replace($val, '', $class);
[2138] => $node->setAttribute('class', $class);
[2139] => }
[2140] => }
[2141] => }
[2142] =>
[2143] => /**
[2144] => * @param array|string $val
[2145] => * @param DOMNode $node
[2146] => *
[2147] => * @return Dom\NodeList<\Dom\Node>|DOMNodeList<\DOMNode>|false
[2148] => */
[2149] => private function loadFromExternalHtml(array|string $val, Dom\HTMLElement|DOMNode $node): Dom\NodeList|DOMNodeList|false {
[2150] => $filename = $this->froms[0][(int)$val[1]]; //external html file
[2151] => $selector = $this->froms[2][(int)$val[1]];
[2152] => //load specified selector if available otherwise load html with the same selector
[2153] =>
[2154] => $filename = $this->processAttributeConstants($filename, $node);
[2155] => $selector = $this->processAttributeConstants($selector, $node);
[2156] =>
[2157] => if (str_starts_with($filename, '/plugins/') || str_starts_with($filename, PUBLIC_PATH)) {
[2158] => $filename = DIR_ROOT . $filename;
[2159] => } elseif ($filename[0] !== '/') {
[2160] => $filename = $this->htmlPath . $filename;
[2161] => }
[2162] =>
[2163] => $this->debug->log('SELECTOR_FROM', $filename);
[2164] =>
[2165] => if (!($html = @file_get_contents($filename))) {
[2166] => Vvveb\logError("can't load html $filename");
[2167] => $this->debug->log('LOAD', 'EXTERNAL ERROR ' . $filename . ' ' . $selector);
[2168] =>
[2169] => return false;
[2170] => }
[2171] =>
[2172] => $this->debug->log('LOAD', $filename . ' SELECTOR ' . $selector);
[2173] =>
[2174] => if (VTPL_DONT_ALLOW_PHP) {
[2175] => $html = $this->removePhp($html);
[2176] => }
[2177] =>
[2178] => if ($this->minifyHtml === true) {
[2179] => $html = $this->minifyHtml($html);
[2180] => }
[2181] =>
[2182] => $document = $this->loadDomFromString($html);
[2183] =>
[2184] => if ($selector) {
[2185] => if ($this->legacy) {
[2186] => $xpath = new DOMXPath($document);
[2187] => $xpathSelector = $this->cssToXpath($selector);
[2188] => $elements = $xpath->query($xpathSelector);
[2189] => } else {
[2190] => $elements = $document->querySelectorAll($selector);
[2191] => }
[2192] => } else {
[2193] => $elements = $document->childNodes;
[2194] => }
[2195] => //$elements = $this->query($selector);
[2196] =>
[2197] => return $elements;
[2198] => }
[2199] =>
[2200] => /**
[2201] => * Processes a node's attributes during the multi-language pass: rewrites
[2202] => * relative media urls against the theme path, removes data-v-* attributes
[2203] => * when configured, converts {$variable} attribute values to php echoes and
[2204] => * wraps translatable placeholder/title values with the translation function.
[2205] => *
[2206] => * @param Dom\HTMLElement|DOMNode $node
[2207] => *
[2208] => * @return void
[2209] => */
[2210] => private function processNodeAttributes(Dom\Element|Dom\HTMLElement|DOMNode $node): void {
[2211] => if (!$node->hasAttributes()) {
[2212] => return;
[2213] => }
[2214] =>
[2215] => $publicLen = strlen(PUBLIC_PATH);
[2216] =>
[2217] => foreach ($node->attributes as $name => $attrNode) {
[2218] => $value = $attrNode->nodeValue;
[2219] => $name = $attrNode->nodeName;
[2220] =>
[2221] => //set relative media to theme folder
[2222] => if ($this->relativePath) {
[2223] => if (($name === 'src' || $name === 'href' || $name === 'srcset') && $node->nodeName !== 'A') {
[2224] => if ($value && $value[0] !== '#'
[2225] => && $value[0] !== '@'
[2226] => && !str_starts_with($value, '//')
[2227] => && !str_starts_with($value, '/feed/')
[2228] => && (!str_contains($value, ':'))) {
[2229] => if ($value[0] === '/') {
[2230] => if (str_starts_with($value, PUBLIC_PATH)) {
[2231] => $value = substr($value, $publicLen);
[2232] => } else {
[2233] => $value = substr($value, 1);
[2234] => }
[2235] =>
[2236] => $this->setNodeAttribute($node, $name, PUBLIC_PATH . $value);
[2237] => } else {
[2238] => $this->setNodeAttribute($node, $name, $this->relativePath . $value);
[2239] => }
[2240] => }
[2241] => }
[2242] =>
[2243] => if ($name === 'style' && str_contains($value, 'url(')) {
[2244] => $value = preg_replace_callback(
[2245] => '/url\(([^\)]+)\)/',
[2246] => function($matches) use ($publicLen) {
[2247] => $url = trim($matches[1], ' \'"');
[2248] =>
[2249] => if (str_starts_with($url, 'http')) {
[2250] => return 'url(' . $url . ')';
[2251] => }
[2252] =>
[2253] => if ($url[0] === '/') {
[2254] => if (str_starts_with($url, PUBLIC_PATH)) {
[2255] => $url = substr($url, $publicLen);
[2256] => } else {
[2257] => $url = substr($url, 1);
[2258] => }
[2259] =>
[2260] => $url = PUBLIC_PATH . $url;
[2261] => } else {
[2262] => $url = $this->relativePath . $url;
[2263] => }
[2264] =>
[2265] => return 'url(' . $url . ')';
[2266] => },
[2267] => $value
[2268] => );
[2269] =>
[2270] => $this->setNodeAttribute($node, $name, $value);
[2271] => }
[2272] => }
[2273] =>
[2274] => if ($this->removeVattrs && str_starts_with($name, 'data-v-')) {
[2275] => $node->removeAttribute($name);
[2276] =>
[2277] => continue;
[2278] => } else {
[2279] => if (str_contains($value, '$')) {
[2280] => $value = preg_replace_callback(
[2281] => '/{(\$[a-z][\w\.>\-]+)}/i',
[2282] => function($matches) {
[2283] => $value = $matches[1];
[2284] =>
[2285] => if (str_starts_with($value, '$this')) {
[2286] => $value = str_replace('$this.', '$this->', $value);
[2287] => }
[2288] => $value = Vvveb\dotToArrayKey($value);
[2289] =>
[2290] => return VTPL_PHP_CDATA_OPEN . 'if (isset(' . $value . ')) echo htmlspecialchars(' . $value . ');' . VTPL_PHP_CDATA_CLOSE;
[2291] => },
[2292] => $value
[2293] => );
[2294] =>
[2295] => $this->setNodeAttribute($node, $name, $value);
[2296] => }
[2297] => }
[2298] =>
[2299] => if (($name === 'placeholder' || $name === 'title'/* || $name == 'alt'*/) && ($value && $value[0] !== '@' && $value[0] !== '<')) {
[2300] => $trimmed = trim($value);
[2301] =>
[2302] => if ($trimmed) {
[2303] => $php = VTPL_PHP_CDATA_OPEN . 'echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');' . VTPL_PHP_CDATA_CLOSE;
[2304] => $this->setNodeAttribute($node, $name, $php);
[2305] => }
[2306] => }
[2307] => /*
[2308] => if (strpos($name, 'data-v-') === 0) {
[2309] => $node->removeAttribute($name);
[2310] => }*/
[2311] => }
[2312] => }
[2313] =>
[2314] => /**
[2315] => * @param Dom\HTMLElement|DOMNode $currentNode
[2316] => *
[2317] => * @return void
[2318] => */
[2319] => private function setMultiLanguageText(Dom\Node|DOMNode $currentNode): void {
[2320] => if ($currentNode->childNodes->length === 0) {
[2321] => return;
[2322] => }
[2323] =>
[2324] => $length = $currentNode->childNodes->length;
[2325] => $publicLen = strlen(PUBLIC_PATH);
[2326] => for ($i = 0; $i < $length; $i++) {
[2327] => $node = $currentNode->childNodes[$i];
[2328] => if (!$node) {
[2329] => continue;
[2330] => }
[2331] => //strip comments
[2332] => if ($node->nodeType === XML_COMMENT_NODE && $this->removeComments) {
[2333] => $node->parentNode->removeChild($node);
[2334] => $i--;
[2335] => continue;
[2336] => }
[2337] =>
[2338] => //process media urls, data-v-* attrs, {$variables} and translatable placeholders
[2339] => if (property_exists($node, 'attributes') || (method_exists($node, 'hasAttributes') && $node->hasAttributes())) {
[2340] => $this->processNodeAttributes($node);
[2341] => }
[2342] =>
[2343] => $tagName = isset($node->parentNode->tagName) ? strtolower($node->parentNode->tagName) : false;
[2344] =>
[2345] => if ($node && $node->nodeType === XML_TEXT_NODE
[2346] => && (!$tagName || ($tagName !== 'textarea' && $tagName !== '_script' && $tagName !== 'script'
[2347] => && $tagName !== 'code' && $tagName !== 'iframe' && $tagName !== 'html'
[2348] => && $tagName !== 'noscript'))) {
[2349] => if (isset($node->wholeText)) {
[2350] => $text = $node->wholeText;
[2351] => } else {
[2352] => $text = $node->textContent;
[2353] => }
[2354] =>
[2355] => $text = \Vvveb\stripExtraSpaces($text);
[2356] => $trimmed = trim($text);
[2357] =>
[2358] => //skip untranslatable text
[2359] => if (strlen($trimmed) < 2 //too small
[2360] => || str_starts_with($trimmed, '{$') //starts with variable
[2361] => || str_starts_with($trimmed, 'http') //is url
[2362] => || str_contains($trimmed, '{$') //string has variable
[2363] => || str_contains($trimmed, ' || (is_numeric($trimmed[0]))
[2365] => ) {
[2366] => continue;
[2367] => }
[2368] =>
[2369] => $before = $currentNode->childNodes->length;
[2370] =>
[2371] => //don't translate long texts
[2372] => if (strlen($trimmed) < 1024) {
[2373] => if ($this->legacy) {
[2374] => $c = $this->document->createCDATASection('echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');');
[2375] => } else {
[2376] => $c = $this->document->createTextNode('echo ' . $this->translationFunction . '(\'' . addcslashes($trimmed, "'") . '\');');
[2377] => }
[2378] => $f = $this->document->createElement('_script');
[2379] => $f->setAttribute('language', 'php');
[2380] => $f->appendChild($c);
[2381] => $node->parentNode->replaceChild($f, $node);
[2382] =>
[2383] => //add previously trimmed space around text
[2384] => $space = str_replace($trimmed, '|', $text);
[2385] => $explode = explode('|', $space);
[2386] => //space before text
[2387] => if ($explode[0]) {
[2388] => $s = $this->document->createTextNode($explode[0]);
[2389] => $f->parentNode->insertBefore($s, $f);
[2390] => }
[2391] => //space after text
[2392] => if (isset($explode[1]) && $explode[1]) {
[2393] => $e = $this->document->createTextNode($explode[1]);
[2394] => //php 8
[2395] => if (isset($f->nextElementSibling)) {
[2396] => //add before next element
[2397] => $f->parentNode->insertBefore($e, $f->nextSibling);
[2398] => } else {
[2399] => if ($f->nextSibling) {
[2400] => //add before next element
[2401] => $f->parentNode->insertBefore($e, $f->nextSibling);
[2402] => } else {
[2403] => //add as last element
[2404] => $f->parentNode->appendChild($e);
[2405] => }
[2406] => }
[2407] => }
[2408] => }
[2409] =>
[2410] => $diff = $currentNode->childNodes->length - $before;
[2411] => $length += $diff;
[2412] => } else {
[2413] => $this->setMultiLanguageText($node);
[2414] => }
[2415] => }
[2416] => }
[2417] =>
[2418] => /**
[2419] => * @param string $php
[2420] => *
[2421] => * @return string
[2422] => */
[2423] => private function minifyPhp(string $php): string {
[2424] => if (function_exists('token_get_all')) {
[2425] => //tokenize so // and /* inside strings (e.g. "https://host") are not stripped as comments
[2426] => $tokens = token_get_all(' $clean = '';
[2428] => foreach ($tokens as $token) {
[2429] => if (is_array($token) && ($token[0] === T_DOC_COMMENT || ($token[0] === T_COMMENT && !str_starts_with($token[1], '#')))) {
[2430] => continue;
[2431] => }
[2432] => $clean .= is_array($token) ? $token[1] : $token;
[2433] => }
[2434] => $php = preg_replace('/^<\?php\s*/', '', $clean, 1);
[2435] => } else {
[2436] => //php comments outside strings /* */
[2437] => $php = preg_replace('/(?
[2439] => //php comments outside strings //
[2440] => $php = preg_replace('@(\/\/)(?=(?:[^"\']|["\'][^"\']*["\'])*$)[^\n]*@s', '', $php);
[2441] => }
[2442] =>
[2443] => //repeating spaces
[2444] => $php = preg_replace('/\s+/', ' ', $php);
[2445] =>
[2446] => //repeating end lines
[2447] => return preg_replace('/\n+/', '', $php);
[2448] => }
[2449] =>
[2450] => /**
[2451] => * @param string $html
[2452] => *
[2453] => * @return string
[2454] => */
[2455] => private function removePhp(string $html): string {
[2456] => //hack, php allows different opening and closing tags
[2457] => return preg_replace('@(<\?php|<\? |<\?=|<\s*script\s*language\s*=\s*"\s*php\s*"\s*>|<%[^%]*%>).*?(\?>|<\s*/\s*script\s*>|%>)@sm', '', $html);
[2458] => }
[2459] =>
[2460] => /**
[2461] => * @param string $html
[2462] => *
[2463] => * @return string
[2464] => */
[2465] => private function minifyHtml(string $html): string {
[2466] => //html comments but keep ie conditionals
[2467] => $html = preg_replace('//sm', '', $html);
[2468] => $html = str_replace('