1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4*04fd306cSNickeau * 5*04fd306cSNickeau * This source code is licensed under the GPL license found in the 6*04fd306cSNickeau * COPYING file in the root directory of this source tree. 7*04fd306cSNickeau * 8*04fd306cSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9*04fd306cSNickeau * @author ComboStrap <support@combostrap.com> 10*04fd306cSNickeau * 11*04fd306cSNickeau */ 12*04fd306cSNickeau 13*04fd306cSNickeaunamespace ComboStrap; 14*04fd306cSNickeau 15*04fd306cSNickeau 16*04fd306cSNickeauuse ComboStrap\Web\Url; 17*04fd306cSNickeau 18*04fd306cSNickeau/** 19*04fd306cSNickeau * @package ComboStrap 20*04fd306cSNickeau * 21*04fd306cSNickeau * Public interface of {@link Snippet} 22*04fd306cSNickeau * 23*04fd306cSNickeau * All plugin/component should use the attach functions to add a internal or external 24*04fd306cSNickeau * stylesheet/javascript to a slot or request scoped 25*04fd306cSNickeau * 26*04fd306cSNickeau * Note: 27*04fd306cSNickeau * All function with the suffix 28*04fd306cSNickeau * * `ForSlot` are snippets for a bar (ie page, sidebar, ...) - cached 29*04fd306cSNickeau * * `ForRequests` are snippets added for the HTTP request - not cached. Example of request component: message, anchor 30*04fd306cSNickeau * 31*04fd306cSNickeau * 32*04fd306cSNickeau * Minification: 33*04fd306cSNickeau * Wrapper: https://packagist.org/packages/jalle19/php-yui-compressor 34*04fd306cSNickeau * Require Yui compressor: https://packagist.org/packages/nervo/yuicompressor 35*04fd306cSNickeau * sudo apt-get install default-jre 36*04fd306cSNickeau * 37*04fd306cSNickeau */ 38*04fd306cSNickeauclass SnippetSystem 39*04fd306cSNickeau{ 40*04fd306cSNickeau 41*04fd306cSNickeau 42*04fd306cSNickeau const CANONICAL = "snippet-system"; 43*04fd306cSNickeau 44*04fd306cSNickeau 45*04fd306cSNickeau /** 46*04fd306cSNickeau * @return SnippetSystem - the global reference 47*04fd306cSNickeau * that is set for every run at the end of this file 48*04fd306cSNickeau * TODO: migrate the attach function to {@link Snippet} 49*04fd306cSNickeau * because Snippet has already a global variable {@link Snippet::getOrCreateFromComponentId()} 50*04fd306cSNickeau */ 51*04fd306cSNickeau public static function getFromContext(): SnippetSystem 52*04fd306cSNickeau { 53*04fd306cSNickeau 54*04fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 55*04fd306cSNickeau try { 56*04fd306cSNickeau return $executionContext->getRuntimeObject(self::CANONICAL); 57*04fd306cSNickeau } catch (ExceptionNotFound $e) { 58*04fd306cSNickeau $snippetSystem = new SnippetSystem(); 59*04fd306cSNickeau $executionContext->setRuntimeObject(self::CANONICAL, $snippetSystem); 60*04fd306cSNickeau return $snippetSystem; 61*04fd306cSNickeau } 62*04fd306cSNickeau 63*04fd306cSNickeau } 64*04fd306cSNickeau 65*04fd306cSNickeau /** 66*04fd306cSNickeau * @param Snippet[] $snippets 67*04fd306cSNickeau * @return string 68*04fd306cSNickeau */ 69*04fd306cSNickeau public static function toHtmlFromSnippetArray(array $snippets): string 70*04fd306cSNickeau { 71*04fd306cSNickeau $xhtmlContent = ""; 72*04fd306cSNickeau foreach ($snippets as $snippet) { 73*04fd306cSNickeau 74*04fd306cSNickeau if ($snippet->hasHtmlOutputAlreadyOccurred()) { 75*04fd306cSNickeau continue; 76*04fd306cSNickeau } 77*04fd306cSNickeau 78*04fd306cSNickeau $xhtmlContent .= $snippet->toXhtml(); 79*04fd306cSNickeau 80*04fd306cSNickeau 81*04fd306cSNickeau } 82*04fd306cSNickeau return $xhtmlContent; 83*04fd306cSNickeau } 84*04fd306cSNickeau 85*04fd306cSNickeau 86*04fd306cSNickeau /** 87*04fd306cSNickeau * Returns all snippets (request and slot scoped) 88*04fd306cSNickeau * 89*04fd306cSNickeau * @return Snippet[] of node type and an array of array of html attributes 90*04fd306cSNickeau */ 91*04fd306cSNickeau public function getAllSnippets(): array 92*04fd306cSNickeau { 93*04fd306cSNickeau return Snippet::getSnippets(); 94*04fd306cSNickeau } 95*04fd306cSNickeau 96*04fd306cSNickeau /** 97*04fd306cSNickeau * @return Snippet[] - the slot snippets (not the request snippet) 98*04fd306cSNickeau */ 99*04fd306cSNickeau private function getSlotSnippets(): array 100*04fd306cSNickeau { 101*04fd306cSNickeau $snippets = Snippet::getSnippets(); 102*04fd306cSNickeau $slotSnippets = []; 103*04fd306cSNickeau foreach ($snippets as $snippet) { 104*04fd306cSNickeau if ($snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 105*04fd306cSNickeau continue; 106*04fd306cSNickeau } 107*04fd306cSNickeau $slotSnippets[] = $snippet; 108*04fd306cSNickeau } 109*04fd306cSNickeau return $slotSnippets; 110*04fd306cSNickeau } 111*04fd306cSNickeau 112*04fd306cSNickeau 113*04fd306cSNickeau public static 114*04fd306cSNickeau function toJsonArrayFromSlotSnippets($snippetsForSlot): array 115*04fd306cSNickeau { 116*04fd306cSNickeau 117*04fd306cSNickeau $jsonSnippets = []; 118*04fd306cSNickeau foreach ($snippetsForSlot as $snippet) { 119*04fd306cSNickeau $jsonSnippets[] = $snippet->toJsonArray(); 120*04fd306cSNickeau } 121*04fd306cSNickeau return $jsonSnippets; 122*04fd306cSNickeau 123*04fd306cSNickeau } 124*04fd306cSNickeau 125*04fd306cSNickeau /** 126*04fd306cSNickeau * @param array $array 127*04fd306cSNickeau * @param string $slot 128*04fd306cSNickeau * @return null|Snippet[] 129*04fd306cSNickeau * @throws ExceptionCompile 130*04fd306cSNickeau */ 131*04fd306cSNickeau public 132*04fd306cSNickeau function getSlotSnippetsFromJsonArray(array $array, string $slot): ?array 133*04fd306cSNickeau { 134*04fd306cSNickeau $snippets = null; 135*04fd306cSNickeau foreach ($array as $element) { 136*04fd306cSNickeau $snippets[] = Snippet::createFromJson($element) 137*04fd306cSNickeau ->addElement($slot); 138*04fd306cSNickeau } 139*04fd306cSNickeau return $snippets; 140*04fd306cSNickeau } 141*04fd306cSNickeau 142*04fd306cSNickeau 143*04fd306cSNickeau /** 144*04fd306cSNickeau * @param $componentId 145*04fd306cSNickeau * @param string|null $script - the css snippet to add, otherwise it takes the file 146*04fd306cSNickeau * @return Snippet a snippet not in a slot 147*04fd306cSNickeau * 148*04fd306cSNickeau * If you need to split the css by type of action, see {@link \action_plugin_combo_docss::handleCssForDoAction()} 149*04fd306cSNickeau */ 150*04fd306cSNickeau public 151*04fd306cSNickeau function &attachCssInternalStyleSheet($componentId, string $script = null): Snippet 152*04fd306cSNickeau { 153*04fd306cSNickeau $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_CSS); 154*04fd306cSNickeau if ($script !== null) { 155*04fd306cSNickeau $snippet->setInlineContent($script); 156*04fd306cSNickeau } 157*04fd306cSNickeau return $snippet; 158*04fd306cSNickeau } 159*04fd306cSNickeau 160*04fd306cSNickeau 161*04fd306cSNickeau /** 162*04fd306cSNickeau * @param $componentId 163*04fd306cSNickeau * @param string|null $script 164*04fd306cSNickeau * @return Snippet a snippet in a slot 165*04fd306cSNickeau */ 166*04fd306cSNickeau public function attachJavascriptFromComponentId($componentId, string $script = null): Snippet 167*04fd306cSNickeau { 168*04fd306cSNickeau $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_JS); 169*04fd306cSNickeau if ($script !== null) { 170*04fd306cSNickeau try { 171*04fd306cSNickeau $content = "{$snippet->getInternalDynamicContent()} $script"; 172*04fd306cSNickeau } catch (ExceptionNotFound $e) { 173*04fd306cSNickeau $content = $script; 174*04fd306cSNickeau } 175*04fd306cSNickeau $snippet->setInlineContent($content); 176*04fd306cSNickeau } 177*04fd306cSNickeau return $snippet; 178*04fd306cSNickeau } 179*04fd306cSNickeau 180*04fd306cSNickeau 181*04fd306cSNickeau public 182*04fd306cSNickeau function attachInternalJavascriptFromPathForRequest($componentId, Path $path): Snippet 183*04fd306cSNickeau { 184*04fd306cSNickeau return Snippet::getOrCreateFromContext($path) 185*04fd306cSNickeau ->addElement(Snippet::REQUEST_SCOPE) 186*04fd306cSNickeau ->setComponentId($componentId); 187*04fd306cSNickeau } 188*04fd306cSNickeau 189*04fd306cSNickeau 190*04fd306cSNickeau /** 191*04fd306cSNickeau * @param $componentId 192*04fd306cSNickeau * @return Snippet[] 193*04fd306cSNickeau */ 194*04fd306cSNickeau public function getSnippetsForComponent($componentId): array 195*04fd306cSNickeau { 196*04fd306cSNickeau $snippets = []; 197*04fd306cSNickeau foreach ($this->getSnippets() as $snippet) { 198*04fd306cSNickeau try { 199*04fd306cSNickeau if ($snippet->getComponentId() === $componentId) { 200*04fd306cSNickeau $snippets[] = $snippet; 201*04fd306cSNickeau } 202*04fd306cSNickeau } catch (ExceptionNotFound $e) { 203*04fd306cSNickeau // 204*04fd306cSNickeau } 205*04fd306cSNickeau } 206*04fd306cSNickeau return $snippets; 207*04fd306cSNickeau } 208*04fd306cSNickeau 209*04fd306cSNickeau /** 210*04fd306cSNickeau * Utility function used in test 211*04fd306cSNickeau * or to show how to test if snippets are present 212*04fd306cSNickeau * @param $componentId 213*04fd306cSNickeau * @return bool 214*04fd306cSNickeau */ 215*04fd306cSNickeau public function hasSnippetsForComponent($componentId): bool 216*04fd306cSNickeau { 217*04fd306cSNickeau return count($this->getSnippetsForComponent($componentId)) > 0; 218*04fd306cSNickeau } 219*04fd306cSNickeau 220*04fd306cSNickeau /** 221*04fd306cSNickeau * @param $componentId 222*04fd306cSNickeau * @param $type 223*04fd306cSNickeau * @return Snippet 224*04fd306cSNickeau * @deprecated - the slot is now added automatically at creation time via the context system 225*04fd306cSNickeau */ 226*04fd306cSNickeau private 227*04fd306cSNickeau function attachSnippetFromRequest($componentId, $type): Snippet 228*04fd306cSNickeau { 229*04fd306cSNickeau return Snippet::getOrCreateFromComponentId($componentId, $type) 230*04fd306cSNickeau ->addElement(Snippet::REQUEST_SCOPE); 231*04fd306cSNickeau } 232*04fd306cSNickeau 233*04fd306cSNickeau 234*04fd306cSNickeau /** 235*04fd306cSNickeau * @param string $snippetId 236*04fd306cSNickeau * @param string $pathFromComboDrive 237*04fd306cSNickeau * @param string|null $integrity 238*04fd306cSNickeau * @return Snippet 239*04fd306cSNickeau */ 240*04fd306cSNickeau public 241*04fd306cSNickeau function attachJavascriptComboResourceForSlot(string $snippetId, string $pathFromComboDrive, string $integrity = null): Snippet 242*04fd306cSNickeau { 243*04fd306cSNickeau 244*04fd306cSNickeau $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 245*04fd306cSNickeau return Snippet::getOrCreateFromContext($dokuPath) 246*04fd306cSNickeau ->setComponentId($snippetId) 247*04fd306cSNickeau ->setIntegrity($integrity); 248*04fd306cSNickeau 249*04fd306cSNickeau } 250*04fd306cSNickeau 251*04fd306cSNickeau /** 252*04fd306cSNickeau * Add a local javascript script as tag 253*04fd306cSNickeau * (ie same as {@link SnippetSystem::attachRemoteJavascriptLibrary()}) 254*04fd306cSNickeau * but for local resource combo file (library) 255*04fd306cSNickeau * 256*04fd306cSNickeau * For instance: 257*04fd306cSNickeau * * library:combo:combo.js 258*04fd306cSNickeau * * for a file located at dokuwiki_home\lib\plugins\combo\resources\library\combo\combo.js 259*04fd306cSNickeau * @return Snippet 260*04fd306cSNickeau */ 261*04fd306cSNickeau public 262*04fd306cSNickeau function attachJavascriptComboLibrary(): Snippet 263*04fd306cSNickeau { 264*04fd306cSNickeau 265*04fd306cSNickeau $wikiPath = ":library:combo:combo.min.js"; 266*04fd306cSNickeau $componentId = "combo"; 267*04fd306cSNickeau return $this->attachSnippetFromComboResourceDrive($wikiPath, $componentId); 268*04fd306cSNickeau 269*04fd306cSNickeau } 270*04fd306cSNickeau 271*04fd306cSNickeau public function attachSnippetFromComboResourceDrive(string $pathFromComboDrive, string $componentId): Snippet 272*04fd306cSNickeau { 273*04fd306cSNickeau 274*04fd306cSNickeau $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 275*04fd306cSNickeau return Snippet::getOrCreateFromContext($dokuPath) 276*04fd306cSNickeau ->setComponentId($componentId); 277*04fd306cSNickeau 278*04fd306cSNickeau } 279*04fd306cSNickeau 280*04fd306cSNickeau /** 281*04fd306cSNickeau * @throws ExceptionBadSyntax 282*04fd306cSNickeau * @throws ExceptionBadArgument 283*04fd306cSNickeau */ 284*04fd306cSNickeau public 285*04fd306cSNickeau function attachRemoteJavascriptLibrary(string $componentId, string $url, string $integrity = null): Snippet 286*04fd306cSNickeau { 287*04fd306cSNickeau $url = Url::createFromString($url); 288*04fd306cSNickeau return Snippet::getOrCreateFromRemoteUrl($url) 289*04fd306cSNickeau ->setIntegrity($integrity) 290*04fd306cSNickeau ->setComponentId($componentId); 291*04fd306cSNickeau } 292*04fd306cSNickeau 293*04fd306cSNickeau /** 294*04fd306cSNickeau * @param string $componentId - the component id attached to this URL 295*04fd306cSNickeau * @param string $url - the external url (The URL should have a file name as last name in the path) 296*04fd306cSNickeau * @param string|null $integrity - the file integrity 297*04fd306cSNickeau * @return Snippet 298*04fd306cSNickeau * @throws ExceptionBadArgument 299*04fd306cSNickeau * @throws ExceptionBadSyntax 300*04fd306cSNickeau * @throws ExceptionNotFound 301*04fd306cSNickeau */ 302*04fd306cSNickeau public 303*04fd306cSNickeau function attachRemoteCssStyleSheet(string $componentId, string $url, string $integrity = null): Snippet 304*04fd306cSNickeau { 305*04fd306cSNickeau $url = Url::createFromString($url); 306*04fd306cSNickeau 307*04fd306cSNickeau return Snippet::getOrCreateFromRemoteUrl($url) 308*04fd306cSNickeau ->setIntegrity($integrity) 309*04fd306cSNickeau ->setRemoteUrl($url) 310*04fd306cSNickeau ->setComponentId($componentId); 311*04fd306cSNickeau } 312*04fd306cSNickeau 313*04fd306cSNickeau 314*04fd306cSNickeau /** 315*04fd306cSNickeau * @return Snippet[] 316*04fd306cSNickeau */ 317*04fd306cSNickeau public 318*04fd306cSNickeau function getSnippets(): array 319*04fd306cSNickeau { 320*04fd306cSNickeau return Snippet::getSnippets(); 321*04fd306cSNickeau } 322*04fd306cSNickeau 323*04fd306cSNickeau private 324*04fd306cSNickeau function getRequestSnippets(): array 325*04fd306cSNickeau { 326*04fd306cSNickeau $snippets = Snippet::getSnippets(); 327*04fd306cSNickeau $slotSnippets = []; 328*04fd306cSNickeau foreach ($snippets as $snippet) { 329*04fd306cSNickeau if (!$snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 330*04fd306cSNickeau continue; 331*04fd306cSNickeau } 332*04fd306cSNickeau $slotSnippets[] = $snippet; 333*04fd306cSNickeau } 334*04fd306cSNickeau return $slotSnippets; 335*04fd306cSNickeau } 336*04fd306cSNickeau 337*04fd306cSNickeau /** 338*04fd306cSNickeau * Output the snippet in HTML format 339*04fd306cSNickeau * The scope is mandatory: 340*04fd306cSNickeau * * {@link Snippet::ALL_SCOPE} 341*04fd306cSNickeau * * {@link Snippet::REQUEST_SCOPE} 342*04fd306cSNickeau * * {@link Snippet::SLOT_SCOPE} 343*04fd306cSNickeau * 344*04fd306cSNickeau * @return string - html string 345*04fd306cSNickeau */ 346*04fd306cSNickeau private 347*04fd306cSNickeau function toHtml($scope): string 348*04fd306cSNickeau { 349*04fd306cSNickeau switch ($scope) { 350*04fd306cSNickeau case Snippet::SLOT_SCOPE: 351*04fd306cSNickeau $snippets = $this->getSlotSnippets(); 352*04fd306cSNickeau break; 353*04fd306cSNickeau case Snippet::REQUEST_SCOPE: 354*04fd306cSNickeau $snippets = $this->getRequestSnippets(); 355*04fd306cSNickeau break; 356*04fd306cSNickeau default: 357*04fd306cSNickeau case Snippet::ALL_SCOPE: 358*04fd306cSNickeau $snippets = $this->getAllSnippets(); 359*04fd306cSNickeau if ($scope !== Snippet::ALL_SCOPE) { 360*04fd306cSNickeau LogUtility::internalError("Scope ($scope) is unknown, we have defaulted to all"); 361*04fd306cSNickeau } 362*04fd306cSNickeau break; 363*04fd306cSNickeau } 364*04fd306cSNickeau 365*04fd306cSNickeau 366*04fd306cSNickeau return self::toHtmlFromSnippetArray($snippets); 367*04fd306cSNickeau } 368*04fd306cSNickeau 369*04fd306cSNickeau public 370*04fd306cSNickeau function toHtmlForAllSnippets(): string 371*04fd306cSNickeau { 372*04fd306cSNickeau return $this->toHtml(Snippet::ALL_SCOPE); 373*04fd306cSNickeau } 374*04fd306cSNickeau 375*04fd306cSNickeau public 376*04fd306cSNickeau function toHtmlForSlotSnippets(): string 377*04fd306cSNickeau { 378*04fd306cSNickeau return $this->toHtml(Snippet::SLOT_SCOPE); 379*04fd306cSNickeau } 380*04fd306cSNickeau 381*04fd306cSNickeau public function addPopoverLibrary(): SnippetSystem 382*04fd306cSNickeau { 383*04fd306cSNickeau $this->attachJavascriptFromComponentId(Snippet::COMBO_POPOVER); 384*04fd306cSNickeau $this->attachCssInternalStylesheet(Snippet::COMBO_POPOVER); 385*04fd306cSNickeau return $this; 386*04fd306cSNickeau } 387*04fd306cSNickeau 388*04fd306cSNickeau /** 389*04fd306cSNickeau * @param $slot 390*04fd306cSNickeau * @return Snippet[] 391*04fd306cSNickeau */ 392*04fd306cSNickeau public function getSnippetsForSlot($slot): array 393*04fd306cSNickeau { 394*04fd306cSNickeau $snippets = Snippet::getSnippets(); 395*04fd306cSNickeau return array_filter($snippets, 396*04fd306cSNickeau function ($s) use ($slot) { 397*04fd306cSNickeau return $s->hasSlot($slot); 398*04fd306cSNickeau }); 399*04fd306cSNickeau } 400*04fd306cSNickeau 401*04fd306cSNickeau 402*04fd306cSNickeau} 403