xref: /plugin/combo/ComboStrap/SnippetSystem.php (revision 5cb7c87d4de446a77e815169392c9289114dce31)
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