From 0aac1a79d6991c5fbefb6f4fc92b6c002ad59398 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Wed, 16 Jun 2021 09:05:16 +0200 Subject: [PATCH] new: [helper:scopedElement] Added scoped element helper --- src/View/AppView.php | 1 + src/View/Helper/ScopedElementHelper.php | 274 ++++++++++++++++++++++++ webroot/js/main.js | 24 +++ 3 files changed, 299 insertions(+) create mode 100644 src/View/Helper/ScopedElementHelper.php diff --git a/src/View/AppView.php b/src/View/AppView.php index 87b775b..12416b9 100644 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -43,5 +43,6 @@ class AppView extends View $this->loadHelper('PrettyPrint'); $this->loadHelper('FormFieldMassage'); $this->loadHelper('Paginator', ['templates' => 'cerebrate-pagination-templates']); + $this->loadHelper('ScopedElement'); } } diff --git a/src/View/Helper/ScopedElementHelper.php b/src/View/Helper/ScopedElementHelper.php new file mode 100644 index 0000000..f6162d1 --- /dev/null +++ b/src/View/Helper/ScopedElementHelper.php @@ -0,0 +1,274 @@ +ScopedElement->element('element-path', $elementData, $elementOptions); + * + * For a scoped element to work properly, the following must be done: + * - The + * + * + * ``` + */ +class ScopedElementHelper extends Helper { + + public function element($elementPath, $elementData=[], $elementOptions=[]): String + { + if (!isset($this->seedDepth)) { + $this->seedDepth = []; + $this->processedSeeds = []; + } + $seed = rand(); + $this->seedDepth[] = $seed; + $this->processedSeeds[] = $seed; + $elementHtml = $this->_View->element($elementPath, $elementData, $elementOptions); + $scopedHtml = $this->createScoped($elementHtml); + array_pop($this->seedDepth); + return $scopedHtml; + } + + private function endsWith($haystack, $needle) + { + $length = strlen($needle); + if ($length == 0) { + return true; + } + return (substr($haystack, -$length) === $needle); + } + + private function preppendScopedId($css) + { + $prependSelector = array_map(function($seed) { + return sprintf('[data-scoped="%s"]', $seed); + }, $this->seedDepth); + $prependSelector = implode(' ', $prependSelector); + $cssLines = explode(PHP_EOL, $css); + foreach ($cssLines as $i => $line) { + if (strlen($line) > 0) { + if ($this->endsWith($line, "{") || $this->endsWith($line, ",")) { + $cssLines[$i] = sprintf("%s %s", $prependSelector, $line); + } + } + } + $cssScopedLines = implode(PHP_EOL, $cssLines); + return sprintf("", PHP_EOL, $cssScopedLines, PHP_EOL); + } + + private function createScoped($html): String + { + $scopedCSS = $this->createScopedCSS($html)['bundle']; + $scopedHtml = $this->createScopedJS($scopedCSS)['bundle']; + return $scopedHtml; + } + + + /** + * Replace a declared CSS scoped style and prepend a random CSS data filter to any CSS selector discovered. + * Usage: Add the following style tag `"; + $styleTagIndex = strpos($html, $htmlStyleTag); + $closingStyleTagIndex = strpos($html, $styleClosingTag, $styleTagIndex) + strlen($styleClosingTag); + if ($styleTagIndex !== false && $closingStyleTagIndex !== false && $closingStyleTagIndex > $styleTagIndex) { // enforced scoped css + $css = substr($html, $styleTagIndex, $closingStyleTagIndex - $styleTagIndex); + $html = str_replace($css, "", $html); // remove CSS part + $css = str_replace($htmlStyleTag, "", $css); // remove the style node + $css = str_replace($styleClosingTag, "", $css); // remove closing style node + $scopedCSS = $this->preppendScopedId($css); + $scopedHtml = sprintf("
%s%s
", + sprintf("data-scoped=\"%s\" ", $seed), + $html, + $scopedCSS + ); + $bundle = $scopedHtml; + } + return array( + "bundle" => $bundle, + "html" => $scopedHtml, + "css" => $scopedCSS, + "seed" => $seed, + "originalHtml" => $originalHtml, + ); + } + + private function varNameHasSeed($varname): bool + { + $pieces = explode('_', $varname); + foreach ($pieces as $piece) { + if (in_array($piece, $this->processedSeeds)) { + return true; + } + } + return false; + } + + private function findNonProcessedScriptOpeningTag($html) + { + $offset = 0; + $i = 0; + while ($i<5) { + $fullOpeningTagObj = $this->findScriptOpeningTag($html, $offset, true); + $offset = $fullOpeningTagObj['openingTagClosingIndex']; + if ($fullOpeningTagObj === false) { // no more tag to process + return false; + } + $varName = $this->getScriptVarname($fullOpeningTagObj['fullOpeningTag']); + if (!$this->varNameHasSeed($varName)) { // found unprocessed tag + return $fullOpeningTagObj['fullOpeningTag']; + } + $i++; + } + } + + private function findScriptOpeningTag($html, $offset=0, $returnIndexes=false) + { + $openingTag = "