104fd306cSNickeau<?php 204fd306cSNickeau/** 304fd306cSNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 404fd306cSNickeau * 504fd306cSNickeau * This source code is licensed under the GPL license found in the 604fd306cSNickeau * COPYING file in the root directory of this source tree. 704fd306cSNickeau * 804fd306cSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 904fd306cSNickeau * @author ComboStrap <support@combostrap.com> 1004fd306cSNickeau * 1104fd306cSNickeau */ 1204fd306cSNickeau 1304fd306cSNickeaunamespace ComboStrap; 1404fd306cSNickeau 1504fd306cSNickeau 1604fd306cSNickeauuse ComboStrap\Web\Url; 17*5cb7c87dSgerardnicouse RuntimeException; 1804fd306cSNickeau 1904fd306cSNickeau/** 2004fd306cSNickeau * @package ComboStrap 2104fd306cSNickeau * 2204fd306cSNickeau * Public interface of {@link Snippet} 2304fd306cSNickeau * 2404fd306cSNickeau * All plugin/component should use the attach functions to add a internal or external 2504fd306cSNickeau * stylesheet/javascript to a slot or request scoped 2604fd306cSNickeau * 2704fd306cSNickeau * Note: 2804fd306cSNickeau * All function with the suffix 2904fd306cSNickeau * * `ForSlot` are snippets for a bar (ie page, sidebar, ...) - cached 3004fd306cSNickeau * * `ForRequests` are snippets added for the HTTP request - not cached. Example of request component: message, anchor 3104fd306cSNickeau * 3204fd306cSNickeau * 3304fd306cSNickeau * Minification: 3404fd306cSNickeau * Wrapper: https://packagist.org/packages/jalle19/php-yui-compressor 3504fd306cSNickeau * Require Yui compressor: https://packagist.org/packages/nervo/yuicompressor 3604fd306cSNickeau * sudo apt-get install default-jre 3704fd306cSNickeau * 3804fd306cSNickeau */ 3904fd306cSNickeauclass SnippetSystem 4004fd306cSNickeau{ 4104fd306cSNickeau 4204fd306cSNickeau 4304fd306cSNickeau const CANONICAL = "snippet-system"; 4404fd306cSNickeau 4504fd306cSNickeau 4604fd306cSNickeau /** 4704fd306cSNickeau * @return SnippetSystem - the global reference 4804fd306cSNickeau * that is set for every run at the end of this file 4904fd306cSNickeau * TODO: migrate the attach function to {@link Snippet} 5004fd306cSNickeau * because Snippet has already a global variable {@link Snippet::getOrCreateFromComponentId()} 5104fd306cSNickeau */ 5204fd306cSNickeau public static function getFromContext(): SnippetSystem 5304fd306cSNickeau { 5404fd306cSNickeau 5504fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 5604fd306cSNickeau try { 5704fd306cSNickeau return $executionContext->getRuntimeObject(self::CANONICAL); 5804fd306cSNickeau } catch (ExceptionNotFound $e) { 5904fd306cSNickeau $snippetSystem = new SnippetSystem(); 6004fd306cSNickeau $executionContext->setRuntimeObject(self::CANONICAL, $snippetSystem); 6104fd306cSNickeau return $snippetSystem; 6204fd306cSNickeau } 6304fd306cSNickeau 6404fd306cSNickeau } 6504fd306cSNickeau 6604fd306cSNickeau /** 6704fd306cSNickeau * @param Snippet[] $snippets 6804fd306cSNickeau * @return string 6904fd306cSNickeau */ 7004fd306cSNickeau public static function toHtmlFromSnippetArray(array $snippets): string 7104fd306cSNickeau { 7204fd306cSNickeau $xhtmlContent = ""; 7304fd306cSNickeau foreach ($snippets as $snippet) { 7404fd306cSNickeau 7504fd306cSNickeau if ($snippet->hasHtmlOutputAlreadyOccurred()) { 7604fd306cSNickeau continue; 7704fd306cSNickeau } 7804fd306cSNickeau 7904fd306cSNickeau $xhtmlContent .= $snippet->toXhtml(); 8004fd306cSNickeau 8104fd306cSNickeau 8204fd306cSNickeau } 8304fd306cSNickeau return $xhtmlContent; 8404fd306cSNickeau } 8504fd306cSNickeau 8604fd306cSNickeau 8704fd306cSNickeau /** 8804fd306cSNickeau * Returns all snippets (request and slot scoped) 8904fd306cSNickeau * 9004fd306cSNickeau * @return Snippet[] of node type and an array of array of html attributes 9104fd306cSNickeau */ 9204fd306cSNickeau public function getAllSnippets(): array 9304fd306cSNickeau { 9404fd306cSNickeau return Snippet::getSnippets(); 9504fd306cSNickeau } 9604fd306cSNickeau 9704fd306cSNickeau /** 9804fd306cSNickeau * @return Snippet[] - the slot snippets (not the request snippet) 9904fd306cSNickeau */ 10004fd306cSNickeau private function getSlotSnippets(): array 10104fd306cSNickeau { 10204fd306cSNickeau $snippets = Snippet::getSnippets(); 10304fd306cSNickeau $slotSnippets = []; 10404fd306cSNickeau foreach ($snippets as $snippet) { 10504fd306cSNickeau if ($snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 10604fd306cSNickeau continue; 10704fd306cSNickeau } 10804fd306cSNickeau $slotSnippets[] = $snippet; 10904fd306cSNickeau } 11004fd306cSNickeau return $slotSnippets; 11104fd306cSNickeau } 11204fd306cSNickeau 11304fd306cSNickeau 11404fd306cSNickeau public static 11504fd306cSNickeau function toJsonArrayFromSlotSnippets($snippetsForSlot): array 11604fd306cSNickeau { 11704fd306cSNickeau 11804fd306cSNickeau $jsonSnippets = []; 11904fd306cSNickeau foreach ($snippetsForSlot as $snippet) { 12004fd306cSNickeau $jsonSnippets[] = $snippet->toJsonArray(); 12104fd306cSNickeau } 12204fd306cSNickeau return $jsonSnippets; 12304fd306cSNickeau 12404fd306cSNickeau } 12504fd306cSNickeau 12604fd306cSNickeau /** 12704fd306cSNickeau * @param array $array 12804fd306cSNickeau * @param string $slot 12904fd306cSNickeau * @return null|Snippet[] 13004fd306cSNickeau * @throws ExceptionCompile 13104fd306cSNickeau */ 13204fd306cSNickeau public 13304fd306cSNickeau function getSlotSnippetsFromJsonArray(array $array, string $slot): ?array 13404fd306cSNickeau { 13504fd306cSNickeau $snippets = null; 13604fd306cSNickeau foreach ($array as $element) { 13704fd306cSNickeau $snippets[] = Snippet::createFromJson($element) 13804fd306cSNickeau ->addElement($slot); 13904fd306cSNickeau } 14004fd306cSNickeau return $snippets; 14104fd306cSNickeau } 14204fd306cSNickeau 14304fd306cSNickeau 14404fd306cSNickeau /** 14504fd306cSNickeau * @param $componentId 14604fd306cSNickeau * @param string|null $script - the css snippet to add, otherwise it takes the file 14704fd306cSNickeau * @return Snippet a snippet not in a slot 14804fd306cSNickeau * 14904fd306cSNickeau * If you need to split the css by type of action, see {@link \action_plugin_combo_docss::handleCssForDoAction()} 15004fd306cSNickeau */ 15104fd306cSNickeau public 15204fd306cSNickeau function &attachCssInternalStyleSheet($componentId, string $script = null): Snippet 15304fd306cSNickeau { 15404fd306cSNickeau $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_CSS); 15504fd306cSNickeau if ($script !== null) { 15604fd306cSNickeau $snippet->setInlineContent($script); 15704fd306cSNickeau } 15804fd306cSNickeau return $snippet; 15904fd306cSNickeau } 16004fd306cSNickeau 16104fd306cSNickeau 16204fd306cSNickeau /** 16304fd306cSNickeau * @param $componentId 16404fd306cSNickeau * @param string|null $script 16504fd306cSNickeau * @return Snippet a snippet in a slot 16604fd306cSNickeau */ 16704fd306cSNickeau public function attachJavascriptFromComponentId($componentId, string $script = null): Snippet 16804fd306cSNickeau { 16904fd306cSNickeau $snippet = Snippet::getOrCreateFromComponentId($componentId, Snippet::EXTENSION_JS); 17004fd306cSNickeau if ($script !== null) { 17104fd306cSNickeau try { 17204fd306cSNickeau $content = "{$snippet->getInternalDynamicContent()} $script"; 17304fd306cSNickeau } catch (ExceptionNotFound $e) { 17404fd306cSNickeau $content = $script; 17504fd306cSNickeau } 17604fd306cSNickeau $snippet->setInlineContent($content); 17704fd306cSNickeau } 17804fd306cSNickeau return $snippet; 17904fd306cSNickeau } 18004fd306cSNickeau 18104fd306cSNickeau 18204fd306cSNickeau public 18304fd306cSNickeau function attachInternalJavascriptFromPathForRequest($componentId, Path $path): Snippet 18404fd306cSNickeau { 18504fd306cSNickeau return Snippet::getOrCreateFromContext($path) 18604fd306cSNickeau ->addElement(Snippet::REQUEST_SCOPE) 18704fd306cSNickeau ->setComponentId($componentId); 18804fd306cSNickeau } 18904fd306cSNickeau 19004fd306cSNickeau 19104fd306cSNickeau /** 19204fd306cSNickeau * @param $componentId 19304fd306cSNickeau * @return Snippet[] 19404fd306cSNickeau */ 19504fd306cSNickeau public function getSnippetsForComponent($componentId): array 19604fd306cSNickeau { 19704fd306cSNickeau $snippets = []; 19804fd306cSNickeau foreach ($this->getSnippets() as $snippet) { 19904fd306cSNickeau try { 20004fd306cSNickeau if ($snippet->getComponentId() === $componentId) { 20104fd306cSNickeau $snippets[] = $snippet; 20204fd306cSNickeau } 20304fd306cSNickeau } catch (ExceptionNotFound $e) { 20404fd306cSNickeau // 20504fd306cSNickeau } 20604fd306cSNickeau } 20704fd306cSNickeau return $snippets; 20804fd306cSNickeau } 20904fd306cSNickeau 21004fd306cSNickeau /** 21104fd306cSNickeau * Utility function used in test 21204fd306cSNickeau * or to show how to test if snippets are present 21304fd306cSNickeau * @param $componentId 21404fd306cSNickeau * @return bool 21504fd306cSNickeau */ 21604fd306cSNickeau public function hasSnippetsForComponent($componentId): bool 21704fd306cSNickeau { 21804fd306cSNickeau return count($this->getSnippetsForComponent($componentId)) > 0; 21904fd306cSNickeau } 22004fd306cSNickeau 22104fd306cSNickeau /** 22204fd306cSNickeau * @param $componentId 22304fd306cSNickeau * @param $type 22404fd306cSNickeau * @return Snippet 22504fd306cSNickeau * @deprecated - the slot is now added automatically at creation time via the context system 22604fd306cSNickeau */ 22704fd306cSNickeau private 22804fd306cSNickeau function attachSnippetFromRequest($componentId, $type): Snippet 22904fd306cSNickeau { 23004fd306cSNickeau return Snippet::getOrCreateFromComponentId($componentId, $type) 23104fd306cSNickeau ->addElement(Snippet::REQUEST_SCOPE); 23204fd306cSNickeau } 23304fd306cSNickeau 23404fd306cSNickeau 23504fd306cSNickeau /** 23604fd306cSNickeau * @param string $snippetId 23704fd306cSNickeau * @param string $pathFromComboDrive 23804fd306cSNickeau * @param string|null $integrity 23904fd306cSNickeau * @return Snippet 24004fd306cSNickeau */ 24104fd306cSNickeau public 24204fd306cSNickeau function attachJavascriptComboResourceForSlot(string $snippetId, string $pathFromComboDrive, string $integrity = null): Snippet 24304fd306cSNickeau { 24404fd306cSNickeau 24504fd306cSNickeau $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 24604fd306cSNickeau return Snippet::getOrCreateFromContext($dokuPath) 24704fd306cSNickeau ->setComponentId($snippetId) 24804fd306cSNickeau ->setIntegrity($integrity); 24904fd306cSNickeau 25004fd306cSNickeau } 25104fd306cSNickeau 25204fd306cSNickeau /** 25304fd306cSNickeau * Add a local javascript script as tag 25404fd306cSNickeau * (ie same as {@link SnippetSystem::attachRemoteJavascriptLibrary()}) 25504fd306cSNickeau * but for local resource combo file (library) 25604fd306cSNickeau * 25704fd306cSNickeau * For instance: 25804fd306cSNickeau * * library:combo:combo.js 25904fd306cSNickeau * * for a file located at dokuwiki_home\lib\plugins\combo\resources\library\combo\combo.js 26004fd306cSNickeau * @return Snippet 26104fd306cSNickeau */ 26204fd306cSNickeau public 26304fd306cSNickeau function attachJavascriptComboLibrary(): Snippet 26404fd306cSNickeau { 26504fd306cSNickeau 26604fd306cSNickeau $wikiPath = ":library:combo:combo.min.js"; 26704fd306cSNickeau $componentId = "combo"; 26804fd306cSNickeau return $this->attachSnippetFromComboResourceDrive($wikiPath, $componentId); 26904fd306cSNickeau 27004fd306cSNickeau } 27104fd306cSNickeau 27204fd306cSNickeau public function attachSnippetFromComboResourceDrive(string $pathFromComboDrive, string $componentId): Snippet 27304fd306cSNickeau { 27404fd306cSNickeau 27504fd306cSNickeau $dokuPath = WikiPath::createComboResource($pathFromComboDrive); 27604fd306cSNickeau return Snippet::getOrCreateFromContext($dokuPath) 27704fd306cSNickeau ->setComponentId($componentId); 27804fd306cSNickeau 27904fd306cSNickeau } 28004fd306cSNickeau 28104fd306cSNickeau /** 282*5cb7c87dSgerardnico * @throws ExceptionBadSyntax - bad url 283*5cb7c87dSgerardnico * @throws ExceptionBadArgument - the url needs to have a file name 28404fd306cSNickeau */ 28504fd306cSNickeau public 28604fd306cSNickeau function attachRemoteJavascriptLibrary(string $componentId, string $url, string $integrity = null): Snippet 28704fd306cSNickeau { 28804fd306cSNickeau $url = Url::createFromString($url); 28904fd306cSNickeau return Snippet::getOrCreateFromRemoteUrl($url) 29004fd306cSNickeau ->setIntegrity($integrity) 29104fd306cSNickeau ->setComponentId($componentId); 29204fd306cSNickeau } 29304fd306cSNickeau 29404fd306cSNickeau /** 295*5cb7c87dSgerardnico * Same component as attachRemoteJavascriptLibrary but without error 296*5cb7c87dSgerardnico * as the url is a code literal (ie written in the code) 297*5cb7c87dSgerardnico * @param string $componentId 298*5cb7c87dSgerardnico * @param string $url 299*5cb7c87dSgerardnico * @param string|null $integrity 300*5cb7c87dSgerardnico * @return Snippet 301*5cb7c87dSgerardnico */ 302*5cb7c87dSgerardnico public 303*5cb7c87dSgerardnico function attachRemoteJavascriptLibraryFromLiteral(string $componentId, string $url, string $integrity = null): Snippet 304*5cb7c87dSgerardnico { 305*5cb7c87dSgerardnico try { 306*5cb7c87dSgerardnico $url = Url::createFromString($url); 307*5cb7c87dSgerardnico return Snippet::getOrCreateFromRemoteUrl($url) 308*5cb7c87dSgerardnico ->setIntegrity($integrity) 309*5cb7c87dSgerardnico ->setComponentId($componentId); 310*5cb7c87dSgerardnico } catch (ExceptionBadArgument|ExceptionBadSyntax $e) { 311*5cb7c87dSgerardnico throw new RuntimeException("Bad URL (" . $e->getMessage() .")", $e); 312*5cb7c87dSgerardnico } 313*5cb7c87dSgerardnico 314*5cb7c87dSgerardnico } 315*5cb7c87dSgerardnico 316*5cb7c87dSgerardnico /** 31704fd306cSNickeau * @param string $componentId - the component id attached to this URL 31804fd306cSNickeau * @param string $url - the external url (The URL should have a file name as last name in the path) 31904fd306cSNickeau * @param string|null $integrity - the file integrity 32004fd306cSNickeau * @return Snippet 32104fd306cSNickeau * @throws ExceptionBadArgument 32204fd306cSNickeau * @throws ExceptionBadSyntax 32304fd306cSNickeau * @throws ExceptionNotFound 32404fd306cSNickeau */ 32504fd306cSNickeau public 32604fd306cSNickeau function attachRemoteCssStyleSheet(string $componentId, string $url, string $integrity = null): Snippet 32704fd306cSNickeau { 32804fd306cSNickeau $url = Url::createFromString($url); 32904fd306cSNickeau 33004fd306cSNickeau return Snippet::getOrCreateFromRemoteUrl($url) 33104fd306cSNickeau ->setIntegrity($integrity) 33204fd306cSNickeau ->setRemoteUrl($url) 33304fd306cSNickeau ->setComponentId($componentId); 33404fd306cSNickeau } 33504fd306cSNickeau 336*5cb7c87dSgerardnico /** 337*5cb7c87dSgerardnico * The same as attachRemoteCssStyleSheet but without any exception 338*5cb7c87dSgerardnico * as the URL is written in the code, it's to the dev to not messed up 339*5cb7c87dSgerardnico * @param string $componentId 340*5cb7c87dSgerardnico * @param string $url 341*5cb7c87dSgerardnico * @param string|null $integrity 342*5cb7c87dSgerardnico * @return Snippet 343*5cb7c87dSgerardnico */ 344*5cb7c87dSgerardnico public 345*5cb7c87dSgerardnico function attachRemoteCssStyleSheetFromLiteral(string $componentId, string $url, string $integrity = null): Snippet 346*5cb7c87dSgerardnico { 347*5cb7c87dSgerardnico try { 348*5cb7c87dSgerardnico $url = Url::createFromString($url); 349*5cb7c87dSgerardnico return Snippet::getOrCreateFromRemoteUrl($url) 350*5cb7c87dSgerardnico ->setIntegrity($integrity) 351*5cb7c87dSgerardnico ->setRemoteUrl($url) 352*5cb7c87dSgerardnico ->setComponentId($componentId); 353*5cb7c87dSgerardnico } catch (ExceptionBadArgument|ExceptionBadSyntax $e) { 354*5cb7c87dSgerardnico throw new RuntimeException("Bad URL (" . $e->getMessage() .")", $e); 355*5cb7c87dSgerardnico } 356*5cb7c87dSgerardnico } 357*5cb7c87dSgerardnico 35804fd306cSNickeau 35904fd306cSNickeau /** 36004fd306cSNickeau * @return Snippet[] 36104fd306cSNickeau */ 36204fd306cSNickeau public 36304fd306cSNickeau function getSnippets(): array 36404fd306cSNickeau { 36504fd306cSNickeau return Snippet::getSnippets(); 36604fd306cSNickeau } 36704fd306cSNickeau 36804fd306cSNickeau private 36904fd306cSNickeau function getRequestSnippets(): array 37004fd306cSNickeau { 37104fd306cSNickeau $snippets = Snippet::getSnippets(); 37204fd306cSNickeau $slotSnippets = []; 37304fd306cSNickeau foreach ($snippets as $snippet) { 37404fd306cSNickeau if (!$snippet->hasSlot(Snippet::REQUEST_SCOPE)) { 37504fd306cSNickeau continue; 37604fd306cSNickeau } 37704fd306cSNickeau $slotSnippets[] = $snippet; 37804fd306cSNickeau } 37904fd306cSNickeau return $slotSnippets; 38004fd306cSNickeau } 38104fd306cSNickeau 38204fd306cSNickeau /** 38304fd306cSNickeau * Output the snippet in HTML format 38404fd306cSNickeau * The scope is mandatory: 38504fd306cSNickeau * * {@link Snippet::ALL_SCOPE} 38604fd306cSNickeau * * {@link Snippet::REQUEST_SCOPE} 38704fd306cSNickeau * * {@link Snippet::SLOT_SCOPE} 38804fd306cSNickeau * 38904fd306cSNickeau * @return string - html string 39004fd306cSNickeau */ 39104fd306cSNickeau private 39204fd306cSNickeau function toHtml($scope): string 39304fd306cSNickeau { 39404fd306cSNickeau switch ($scope) { 39504fd306cSNickeau case Snippet::SLOT_SCOPE: 39604fd306cSNickeau $snippets = $this->getSlotSnippets(); 39704fd306cSNickeau break; 39804fd306cSNickeau case Snippet::REQUEST_SCOPE: 39904fd306cSNickeau $snippets = $this->getRequestSnippets(); 40004fd306cSNickeau break; 40104fd306cSNickeau default: 40204fd306cSNickeau case Snippet::ALL_SCOPE: 40304fd306cSNickeau $snippets = $this->getAllSnippets(); 40404fd306cSNickeau if ($scope !== Snippet::ALL_SCOPE) { 40504fd306cSNickeau LogUtility::internalError("Scope ($scope) is unknown, we have defaulted to all"); 40604fd306cSNickeau } 40704fd306cSNickeau break; 40804fd306cSNickeau } 40904fd306cSNickeau 41004fd306cSNickeau 41104fd306cSNickeau return self::toHtmlFromSnippetArray($snippets); 41204fd306cSNickeau } 41304fd306cSNickeau 41404fd306cSNickeau public 41504fd306cSNickeau function toHtmlForAllSnippets(): string 41604fd306cSNickeau { 41704fd306cSNickeau return $this->toHtml(Snippet::ALL_SCOPE); 41804fd306cSNickeau } 41904fd306cSNickeau 42004fd306cSNickeau public 42104fd306cSNickeau function toHtmlForSlotSnippets(): string 42204fd306cSNickeau { 42304fd306cSNickeau return $this->toHtml(Snippet::SLOT_SCOPE); 42404fd306cSNickeau } 42504fd306cSNickeau 42604fd306cSNickeau public function addPopoverLibrary(): SnippetSystem 42704fd306cSNickeau { 42804fd306cSNickeau $this->attachJavascriptFromComponentId(Snippet::COMBO_POPOVER); 42904fd306cSNickeau $this->attachCssInternalStylesheet(Snippet::COMBO_POPOVER); 43004fd306cSNickeau return $this; 43104fd306cSNickeau } 43204fd306cSNickeau 43304fd306cSNickeau /** 43404fd306cSNickeau * @param $slot 43504fd306cSNickeau * @return Snippet[] 43604fd306cSNickeau */ 43704fd306cSNickeau public function getSnippetsForSlot($slot): array 43804fd306cSNickeau { 43904fd306cSNickeau $snippets = Snippet::getSnippets(); 44004fd306cSNickeau return array_filter($snippets, 44104fd306cSNickeau function ($s) use ($slot) { 44204fd306cSNickeau return $s->hasSlot($slot); 44304fd306cSNickeau }); 44404fd306cSNickeau } 44504fd306cSNickeau 44604fd306cSNickeau 44704fd306cSNickeau} 448