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