xref: /template/strap/ComboStrap/FetcherMarkup.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau
4*04fd306cSNickeaunamespace ComboStrap;
5*04fd306cSNickeau
6*04fd306cSNickeau
7*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
8*04fd306cSNickeauuse ComboStrap\Web\Url;
9*04fd306cSNickeauuse ComboStrap\Web\UrlEndpoint;
10*04fd306cSNickeauuse ComboStrap\Xml\XmlDocument;
11*04fd306cSNickeauuse Doku_Renderer_metadata;
12*04fd306cSNickeauuse dokuwiki\Cache\CacheInstructions;
13*04fd306cSNickeauuse dokuwiki\Cache\CacheParser;
14*04fd306cSNickeauuse dokuwiki\Cache\CacheRenderer;
15*04fd306cSNickeauuse Exception;
16*04fd306cSNickeau
17*04fd306cSNickeau
18*04fd306cSNickeau/**
19*04fd306cSNickeau * A class that renders a markup fragment
20*04fd306cSNickeau * This is the context object
21*04fd306cSNickeau * during parsing and rendering is determined by {@link FetcherMarkup}
22*04fd306cSNickeau *
23*04fd306cSNickeau * You can get it in any place via {@link ExecutionContext::getExecutingMarkupHandler()}
24*04fd306cSNickeau *
25*04fd306cSNickeau * It:
26*04fd306cSNickeau * * does not output any full page (HTML document) but only fragment.
27*04fd306cSNickeau * * manage the dependencies (snippets, cache)
28*04fd306cSNickeau *
29*04fd306cSNickeau * This is not really a {@link IFetcher function} because it should not be called
30*04fd306cSNickeau * from the outside but to be able to use the {@link FetcherCache} we need to.
31*04fd306cSNickeau * (as fetcher cache uses the url as unique identifier)
32*04fd306cSNickeau *
33*04fd306cSNickeau *
34*04fd306cSNickeau * TODO: {@link MarkupRenderer} could be one with {@link FetcherMarkup} ?
35*04fd306cSNickeau *
36*04fd306cSNickeau * Not all properties are public to support
37*04fd306cSNickeau * the {@link FetcherMarkupBuilder} pattern.
38*04fd306cSNickeau * Php does not support internal class and protected does not
39*04fd306cSNickeau * work for class on the same namespace.
40*04fd306cSNickeau */
41*04fd306cSNickeauclass FetcherMarkup extends IFetcherAbs implements IFetcherSource, IFetcherString
42*04fd306cSNickeau{
43*04fd306cSNickeau
44*04fd306cSNickeau
45*04fd306cSNickeau    const XHTML_MODE = "xhtml";
46*04fd306cSNickeau    const MAX_CACHE_AGE = 999999;
47*04fd306cSNickeau
48*04fd306cSNickeau    const CANONICAL = "markup-fragment-fetcher";
49*04fd306cSNickeau
50*04fd306cSNickeau    /**
51*04fd306cSNickeau     * When the rendering is done from:
52*04fd306cSNickeau     * * a string
53*04fd306cSNickeau     * * or an instructions (template)
54*04fd306cSNickeau     * but not from a file
55*04fd306cSNickeau     */
56*04fd306cSNickeau    public const MARKUP_DYNAMIC_EXECUTION_NAME = "markup-dynamic-execution";
57*04fd306cSNickeau
58*04fd306cSNickeau    /**
59*04fd306cSNickeau     * @var array - toc in a dokuwiki format
60*04fd306cSNickeau     */
61*04fd306cSNickeau    public array $toc;
62*04fd306cSNickeau
63*04fd306cSNickeau    /**
64*04fd306cSNickeau     * @var CacheParser cache file (may be not set if this is not a {@link self::isPathExecution() execution}
65*04fd306cSNickeau     */
66*04fd306cSNickeau    public CacheParser $contentCache;
67*04fd306cSNickeau
68*04fd306cSNickeau    /**
69*04fd306cSNickeau     * @var string the type of object (known as renderer in Dokuwiki)
70*04fd306cSNickeau     */
71*04fd306cSNickeau    public string $builderName;
72*04fd306cSNickeau
73*04fd306cSNickeau    public array $requestedInstructions;
74*04fd306cSNickeau    public array $contextData;
75*04fd306cSNickeau
76*04fd306cSNickeau    public CacheInstructions $instructionsCache;
77*04fd306cSNickeau
78*04fd306cSNickeau    /**
79*04fd306cSNickeau     * @var CacheRenderer This cache file stores the last render timestamp (see {@link p_get_metadata()}
80*04fd306cSNickeau     */
81*04fd306cSNickeau    public CacheRenderer $metaCache;
82*04fd306cSNickeau    public LocalPath $metaPath;
83*04fd306cSNickeau
84*04fd306cSNickeau    /**
85*04fd306cSNickeau     * @var CacheParser
86*04fd306cSNickeau     */
87*04fd306cSNickeau    public CacheParser $snippetCache;
88*04fd306cSNickeau
89*04fd306cSNickeau    /**
90*04fd306cSNickeau     * @var FetcherMarkup - the parent (a instructions run may run inside a path run, ie {@link \syntax_plugin_combo_iterator)
91*04fd306cSNickeau     */
92*04fd306cSNickeau    public FetcherMarkup $parentMarkupHandler;
93*04fd306cSNickeau
94*04fd306cSNickeau    /**
95*04fd306cSNickeau     * @var bool threat the markup as a document (not as a fragment)
96*04fd306cSNickeau     */
97*04fd306cSNickeau    public bool $isDoc;
98*04fd306cSNickeau
99*04fd306cSNickeau
100*04fd306cSNickeau    public Mime $mime;
101*04fd306cSNickeau    private bool $cacheAfterRendering = true;
102*04fd306cSNickeau    public MarkupCacheDependencies $outputCacheDependencies;
103*04fd306cSNickeau
104*04fd306cSNickeau
105*04fd306cSNickeau    /**
106*04fd306cSNickeau     * @var Snippet[]
107*04fd306cSNickeau     */
108*04fd306cSNickeau    private array $localSnippets = [];
109*04fd306cSNickeau
110*04fd306cSNickeau    public bool $deleteRootBlockElement = false;
111*04fd306cSNickeau
112*04fd306cSNickeau    /**
113*04fd306cSNickeau     * @var WikiPath the context path, it's important to resolve relative link and to create cache for each context namespace for instance
114*04fd306cSNickeau     */
115*04fd306cSNickeau    public WikiPath $requestedContextPath;
116*04fd306cSNickeau
117*04fd306cSNickeau    /**
118*04fd306cSNickeau     * @var Path the source path of the markup (may be not set if we render a markup string for instance)
119*04fd306cSNickeau     */
120*04fd306cSNickeau    public Path $markupSourcePath;
121*04fd306cSNickeau
122*04fd306cSNickeau
123*04fd306cSNickeau    public string $markupString;
124*04fd306cSNickeau
125*04fd306cSNickeau    /**
126*04fd306cSNickeau     * @var bool true if this fetcher has already run
127*04fd306cSNickeau     * (
128*04fd306cSNickeau     * Fighting file modified time, even if we cache has been stored,
129*04fd306cSNickeau     * the modified time is not always good, this indicator will
130*04fd306cSNickeau     * make the processing not run twice)
131*04fd306cSNickeau     */
132*04fd306cSNickeau    private bool $hasExecuted = false;
133*04fd306cSNickeau
134*04fd306cSNickeau    /**
135*04fd306cSNickeau     * The result
136*04fd306cSNickeau     * @var string
137*04fd306cSNickeau     */
138*04fd306cSNickeau    private string $fetchString;
139*04fd306cSNickeau
140*04fd306cSNickeau
141*04fd306cSNickeau    /**
142*04fd306cSNickeau     * @var array
143*04fd306cSNickeau     */
144*04fd306cSNickeau    private array $meta;
145*04fd306cSNickeau
146*04fd306cSNickeau    /**
147*04fd306cSNickeau     * @var bool - when a execution is not a {@link self::isPathExecution()}, the snippet will not be stored automatically.
148*04fd306cSNickeau     * To avoid this problem, a warning is send if the calling code does not set explicitly that this is specifically a
149*04fd306cSNickeau     * standalone execution
150*04fd306cSNickeau     */
151*04fd306cSNickeau    public bool $isNonPathStandaloneExecution = false;
152*04fd306cSNickeau    /**
153*04fd306cSNickeau     * @var array
154*04fd306cSNickeau     */
155*04fd306cSNickeau    private array $processedInstructions;
156*04fd306cSNickeau
157*04fd306cSNickeau
158*04fd306cSNickeau    /**
159*04fd306cSNickeau     * @param Path $executingPath - the path where we can find the markup
160*04fd306cSNickeau     * @param ?WikiPath $contextPath - the context path, the requested path in the browser url (from where relative component are resolved (ie links, ...))
161*04fd306cSNickeau     * @return FetcherMarkup
162*04fd306cSNickeau     * @throws ExceptionNotExists
163*04fd306cSNickeau     */
164*04fd306cSNickeau    public static function createXhtmlMarkupFetcherFromPath(Path $executingPath, WikiPath $contextPath = null): FetcherMarkup
165*04fd306cSNickeau    {
166*04fd306cSNickeau        if ($contextPath === null) {
167*04fd306cSNickeau            try {
168*04fd306cSNickeau                $contextPath = $executingPath->toWikiPath();
169*04fd306cSNickeau            } catch (ExceptionCast $e) {
170*04fd306cSNickeau                /**
171*04fd306cSNickeau                 * Not a wiki path, default to the default
172*04fd306cSNickeau                 */
173*04fd306cSNickeau                $contextPath = ExecutionContext::getActualOrCreateFromEnv()->getDefaultContextPath();
174*04fd306cSNickeau            }
175*04fd306cSNickeau        }
176*04fd306cSNickeau        return FetcherMarkup::confRoot()
177*04fd306cSNickeau            ->setRequestedExecutingPath($executingPath)
178*04fd306cSNickeau            ->setRequestedContextPath($contextPath)
179*04fd306cSNickeau            ->setRequestedMimeToXhtml()
180*04fd306cSNickeau            ->build();
181*04fd306cSNickeau    }
182*04fd306cSNickeau
183*04fd306cSNickeau
184*04fd306cSNickeau    public static function confRoot(): FetcherMarkupBuilder
185*04fd306cSNickeau    {
186*04fd306cSNickeau        return new FetcherMarkupBuilder();
187*04fd306cSNickeau    }
188*04fd306cSNickeau
189*04fd306cSNickeau    /**
190*04fd306cSNickeau     * Use mostly in test
191*04fd306cSNickeau     * The coutnerpart of {@link \ComboStrap\Test\TestUtility::renderText2XhtmlWithoutP()}
192*04fd306cSNickeau     * @throws ExceptionNotExists
193*04fd306cSNickeau     */
194*04fd306cSNickeau    public static function createStandaloneExecutionFromStringMarkupToXhtml(string $markup): FetcherMarkup
195*04fd306cSNickeau    {
196*04fd306cSNickeau        return self::confRoot()
197*04fd306cSNickeau            ->setRequestedMarkupString($markup)
198*04fd306cSNickeau            ->setDeleteRootBlockElement(true)
199*04fd306cSNickeau            ->setRequestedContextPathWithDefault()
200*04fd306cSNickeau            ->setRequestedMimeToXhtml()
201*04fd306cSNickeau            ->setIsStandAloneCodeExecution(true)
202*04fd306cSNickeau            ->build();
203*04fd306cSNickeau    }
204*04fd306cSNickeau
205*04fd306cSNickeau    /**
206*04fd306cSNickeau     * Starts a child fetcher markup
207*04fd306cSNickeau     * This is needed for instructions or markup run
208*04fd306cSNickeau     * Why ? Because the snippets advertised during this run, need to be stored
209*04fd306cSNickeau     * and we need to know the original request path that is in the parent run.
210*04fd306cSNickeau     * @return FetcherMarkupBuilder
211*04fd306cSNickeau     */
212*04fd306cSNickeau    public static function confChild(): FetcherMarkupBuilder
213*04fd306cSNickeau    {
214*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
215*04fd306cSNickeau        try {
216*04fd306cSNickeau            $executing = $executionContext->getExecutingMarkupHandler();
217*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
218*04fd306cSNickeau            if (PluginUtility::isDevOrTest() && $executionContext->getExecutingAction() !== ExecutionContext::PREVIEW_ACTION) {
219*04fd306cSNickeau                LogUtility::warning("A markup handler is not running, we couldn't create a child.");
220*04fd306cSNickeau            }
221*04fd306cSNickeau            return self::confRoot();
222*04fd306cSNickeau        }
223*04fd306cSNickeau        return self::confRoot()
224*04fd306cSNickeau            ->setParentMarkupHandler($executing)
225*04fd306cSNickeau            ->setRequestedContextPath($executing->getRequestedContextPath());
226*04fd306cSNickeau    }
227*04fd306cSNickeau
228*04fd306cSNickeau
229*04fd306cSNickeau    /**
230*04fd306cSNickeau     * Dokuwiki will wrap the markup in a p element
231*04fd306cSNickeau     * if the first element is not a block
232*04fd306cSNickeau     * This option permits to delete it. This is used mostly in test to get
233*04fd306cSNickeau     * the generated html
234*04fd306cSNickeau     */
235*04fd306cSNickeau    public function deleteRootPElementsIfRequested(array &$instructions): void
236*04fd306cSNickeau    {
237*04fd306cSNickeau
238*04fd306cSNickeau        if (!$this->deleteRootBlockElement) {
239*04fd306cSNickeau            return;
240*04fd306cSNickeau        }
241*04fd306cSNickeau
242*04fd306cSNickeau        /**
243*04fd306cSNickeau         * Delete the p added by {@link Block::process()}
244*04fd306cSNickeau         * if the plugin of the {@link SyntaxPlugin::getPType() normal} and not in a block
245*04fd306cSNickeau         *
246*04fd306cSNickeau         * p_open = document_start in renderer
247*04fd306cSNickeau         */
248*04fd306cSNickeau        if ($instructions[1][0] !== 'p_open') {
249*04fd306cSNickeau            return;
250*04fd306cSNickeau        }
251*04fd306cSNickeau        unset($instructions[1]);
252*04fd306cSNickeau
253*04fd306cSNickeau        /**
254*04fd306cSNickeau         * The last p position is not fix
255*04fd306cSNickeau         * We may have other calls due for instance
256*04fd306cSNickeau         * of {@link \action_plugin_combo_syntaxanalytics}
257*04fd306cSNickeau         */
258*04fd306cSNickeau        $n = 1;
259*04fd306cSNickeau        while (($lastPBlockPosition = (sizeof($instructions) - $n)) >= 0) {
260*04fd306cSNickeau
261*04fd306cSNickeau            /**
262*04fd306cSNickeau             * p_open = document_end in renderer
263*04fd306cSNickeau             */
264*04fd306cSNickeau            if ($instructions[$lastPBlockPosition][0] == 'p_close') {
265*04fd306cSNickeau                unset($instructions[$lastPBlockPosition]);
266*04fd306cSNickeau                break;
267*04fd306cSNickeau            } else {
268*04fd306cSNickeau                $n = $n + 1;
269*04fd306cSNickeau            }
270*04fd306cSNickeau        }
271*04fd306cSNickeau
272*04fd306cSNickeau    }
273*04fd306cSNickeau
274*04fd306cSNickeau    /**
275*04fd306cSNickeau     *
276*04fd306cSNickeau     * @param Url|null $url
277*04fd306cSNickeau     * @return Url
278*04fd306cSNickeau     *
279*04fd306cSNickeau     * Note: The fetch url is the {@link FetcherCache keyCache}
280*04fd306cSNickeau     */
281*04fd306cSNickeau    function getFetchUrl(Url $url = null): Url
282*04fd306cSNickeau    {
283*04fd306cSNickeau        /**
284*04fd306cSNickeau         * Overwrite default fetcher endpoint
285*04fd306cSNickeau         * that is {@link UrlEndpoint::createFetchUrl()}
286*04fd306cSNickeau         */
287*04fd306cSNickeau        $url = UrlEndpoint::createDokuUrl();
288*04fd306cSNickeau        $url = parent::getFetchUrl($url);
289*04fd306cSNickeau        try {
290*04fd306cSNickeau            $wikiPath = $this->getSourcePath()->toWikiPath();
291*04fd306cSNickeau            $url->addQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $wikiPath->getWikiId());
292*04fd306cSNickeau            $url->addQueryParameter(WikiPath::DRIVE_ATTRIBUTE, $wikiPath->getDrive());
293*04fd306cSNickeau        } catch (ExceptionCast|ExceptionNotFound $e) {
294*04fd306cSNickeau            // not an accessible source path
295*04fd306cSNickeau        }
296*04fd306cSNickeau        $url->addQueryParameter("context-id", $this->getRequestedContextPath()->getWikiId());
297*04fd306cSNickeau        return $url;
298*04fd306cSNickeau
299*04fd306cSNickeau    }
300*04fd306cSNickeau
301*04fd306cSNickeau
302*04fd306cSNickeau    /**
303*04fd306cSNickeau     * @return Mime
304*04fd306cSNickeau     */
305*04fd306cSNickeau    public function getMime(): Mime
306*04fd306cSNickeau    {
307*04fd306cSNickeau        if (isset($this->mime)) {
308*04fd306cSNickeau            return $this->mime;
309*04fd306cSNickeau        }
310*04fd306cSNickeau
311*04fd306cSNickeau        // XHTML default
312*04fd306cSNickeau        try {
313*04fd306cSNickeau            return Mime::createFromExtension(self::XHTML_MODE);
314*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
315*04fd306cSNickeau            // should not happen
316*04fd306cSNickeau            throw new ExceptionRuntime("Internal error: The XHTML mime was not found.", self::CANONICAL, 1, $e);
317*04fd306cSNickeau        }
318*04fd306cSNickeau    }
319*04fd306cSNickeau
320*04fd306cSNickeau    /**
321*04fd306cSNickeau     * TODO: split We should split fetcherMarkup by object type output and {@link Mime}
322*04fd306cSNickeau     * @return bool
323*04fd306cSNickeau     */
324*04fd306cSNickeau    private function shouldInstructionProcess(): bool
325*04fd306cSNickeau    {
326*04fd306cSNickeau
327*04fd306cSNickeau        if (!$this->isPathExecution()) {
328*04fd306cSNickeau            return true;
329*04fd306cSNickeau        }
330*04fd306cSNickeau
331*04fd306cSNickeau        if (isset($this->processedInstructions)) {
332*04fd306cSNickeau            return false;
333*04fd306cSNickeau        }
334*04fd306cSNickeau
335*04fd306cSNickeau        /**
336*04fd306cSNickeau         * Edge Case
337*04fd306cSNickeau         * (as dokuwiki starts the rendering process here
338*04fd306cSNickeau         * we need to set the execution id)
339*04fd306cSNickeau         */
340*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()->setExecutingMarkupHandler($this);
341*04fd306cSNickeau        try {
342*04fd306cSNickeau            $useCache = $this->instructionsCache->useCache();
343*04fd306cSNickeau        } finally {
344*04fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
345*04fd306cSNickeau        }
346*04fd306cSNickeau        return ($useCache === false);
347*04fd306cSNickeau    }
348*04fd306cSNickeau
349*04fd306cSNickeau    public function shouldProcess(): bool
350*04fd306cSNickeau    {
351*04fd306cSNickeau
352*04fd306cSNickeau        if (!$this->isPathExecution()) {
353*04fd306cSNickeau            return true;
354*04fd306cSNickeau        }
355*04fd306cSNickeau
356*04fd306cSNickeau        if ($this->hasExecuted) {
357*04fd306cSNickeau            return false;
358*04fd306cSNickeau        }
359*04fd306cSNickeau
360*04fd306cSNickeau        /**
361*04fd306cSNickeau         * The cache is stored by requested page scope
362*04fd306cSNickeau         *
363*04fd306cSNickeau         * We set the environment because
364*04fd306cSNickeau         * {@link CacheParser::useCache()} may call a parsing of the markup fragment
365*04fd306cSNickeau         * And the global environment are not always passed
366*04fd306cSNickeau         * in all actions and is needed to log the {@link  CacheResult cache
367*04fd306cSNickeau         * result}
368*04fd306cSNickeau         *
369*04fd306cSNickeau         * Use cache should be always called because it trigger
370*04fd306cSNickeau         * the event coupled to the cache (ie PARSER_CACHE_USE)
371*04fd306cSNickeau         */
372*04fd306cSNickeau        $depends['age'] = $this->getCacheAge();
373*04fd306cSNickeau        if ($this->isFragment()) {
374*04fd306cSNickeau            /**
375*04fd306cSNickeau             * Fragment may use variables of the requested page
376*04fd306cSNickeau             * We have dependency on {@link MarkupCacheDependencies::PAGE_PRIMARY_META_DEPENDENCY}
377*04fd306cSNickeau             * but as they may be derived such as the {@link PageTitle}
378*04fd306cSNickeau             * comes from the H1 or the feature image comes from the first image in the section 1
379*04fd306cSNickeau             * We can't really use this event.
380*04fd306cSNickeau             */
381*04fd306cSNickeau            try {
382*04fd306cSNickeau                $depends['files'][] = FetcherMarkup::confRoot()
383*04fd306cSNickeau                    ->setRequestedContextPath($this->getRequestedContextPath())
384*04fd306cSNickeau                    ->setRequestedExecutingPath($this->getRequestedContextPath())
385*04fd306cSNickeau                    ->setRequestedMimeToMetadata()
386*04fd306cSNickeau                    ->build()
387*04fd306cSNickeau                    ->getMetadataPath()
388*04fd306cSNickeau                    ->toAbsoluteId();
389*04fd306cSNickeau            } catch (ExceptionNotExists|ExceptionNotFound $e) {
390*04fd306cSNickeau                /**
391*04fd306cSNickeau                 * Computer are hard
392*04fd306cSNickeau                 * At the beginning there is no markup path
393*04fd306cSNickeau                 * We may get this error then
394*04fd306cSNickeau                 *
395*04fd306cSNickeau                 * We don't allow on test
396*04fd306cSNickeau                 */
397*04fd306cSNickeau                if (PluginUtility::isTest()) {
398*04fd306cSNickeau                    /**
399*04fd306cSNickeau                     * The first edit, the page does not exists
400*04fd306cSNickeau                     */
401*04fd306cSNickeau                    $executingAction = ExecutionContext::getActualOrCreateFromEnv()->getExecutingAction();
402*04fd306cSNickeau                    if (!in_array($executingAction, [ExecutionContext::EDIT_ACTION, ExecutionContext::PREVIEW_ACTION])) {
403*04fd306cSNickeau                        LogUtility::error("The metadata path should be known. " . $e->getMessage(), self::CANONICAL, $e);
404*04fd306cSNickeau                    }
405*04fd306cSNickeau                }
406*04fd306cSNickeau            }
407*04fd306cSNickeau        }
408*04fd306cSNickeau        /**
409*04fd306cSNickeau         * Edge Case
410*04fd306cSNickeau         * (as dokuwiki starts the rendering process here
411*04fd306cSNickeau         * we need to set the execution id)
412*04fd306cSNickeau         */
413*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()
414*04fd306cSNickeau            ->setExecutingMarkupHandler($this);
415*04fd306cSNickeau        try {
416*04fd306cSNickeau            $useCache = $this->contentCache->useCache($depends);
417*04fd306cSNickeau        } finally {
418*04fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
419*04fd306cSNickeau        }
420*04fd306cSNickeau        return ($useCache === false);
421*04fd306cSNickeau
422*04fd306cSNickeau    }
423*04fd306cSNickeau
424*04fd306cSNickeau
425*04fd306cSNickeau    public
426*04fd306cSNickeau    function storeSnippets()
427*04fd306cSNickeau    {
428*04fd306cSNickeau
429*04fd306cSNickeau        /**
430*04fd306cSNickeau         * Snippet
431*04fd306cSNickeau         */
432*04fd306cSNickeau        $snippets = $this->getSnippets();
433*04fd306cSNickeau        $jsonDecodeSnippets = SnippetSystem::toJsonArrayFromSlotSnippets($snippets);
434*04fd306cSNickeau
435*04fd306cSNickeau        /**
436*04fd306cSNickeau         * Cache file
437*04fd306cSNickeau         * Using a cache parser, set the page id and will trigger
438*04fd306cSNickeau         * the parser cache use event in order to log/report the cache usage
439*04fd306cSNickeau         * At {@link action_plugin_combo_cache::createCacheReport()}
440*04fd306cSNickeau         */
441*04fd306cSNickeau        $snippetCache = $this->getSnippetCacheStore();
442*04fd306cSNickeau        $this->outputCacheDependencies->rerouteCacheDestination($snippetCache);
443*04fd306cSNickeau
444*04fd306cSNickeau        if (count($jsonDecodeSnippets) > 0) {
445*04fd306cSNickeau            $data1 = json_encode($jsonDecodeSnippets);
446*04fd306cSNickeau            $snippetCache->storeCache($data1);
447*04fd306cSNickeau        } else {
448*04fd306cSNickeau            $snippetCache->removeCache();
449*04fd306cSNickeau        }
450*04fd306cSNickeau
451*04fd306cSNickeau    }
452*04fd306cSNickeau
453*04fd306cSNickeau    /**
454*04fd306cSNickeau     * This functon loads the snippets in the global array
455*04fd306cSNickeau     * by creating them. Not ideal but works for now.
456*04fd306cSNickeau     * @return Snippet[]
457*04fd306cSNickeau     */
458*04fd306cSNickeau    public
459*04fd306cSNickeau    function loadSnippets(): array
460*04fd306cSNickeau    {
461*04fd306cSNickeau
462*04fd306cSNickeau        $snippetCacheStore = $this->getSnippetCacheStore();
463*04fd306cSNickeau        $data = $snippetCacheStore->retrieveCache();
464*04fd306cSNickeau        $nativeSnippets = [];
465*04fd306cSNickeau        if (!empty($data)) {
466*04fd306cSNickeau            $jsonDecodeSnippets = json_decode($data, true);
467*04fd306cSNickeau            foreach ($jsonDecodeSnippets as $snippet) {
468*04fd306cSNickeau                try {
469*04fd306cSNickeau                    $nativeSnippets[] = Snippet::createFromJson($snippet);
470*04fd306cSNickeau                } catch (ExceptionCompile $e) {
471*04fd306cSNickeau                    LogUtility::error("The snippet json array cannot be build into a snippet object. " . $e->getMessage() . "\n" . ArrayUtility::formatAsString($snippet), LogUtility::SUPPORT_CANONICAL,);
472*04fd306cSNickeau                }
473*04fd306cSNickeau            }
474*04fd306cSNickeau        }
475*04fd306cSNickeau        return $nativeSnippets;
476*04fd306cSNickeau
477*04fd306cSNickeau    }
478*04fd306cSNickeau
479*04fd306cSNickeau    private
480*04fd306cSNickeau    function removeSnippets()
481*04fd306cSNickeau    {
482*04fd306cSNickeau        $snippetCacheFile = $this->getSnippetCacheStore()->cache;
483*04fd306cSNickeau        if ($snippetCacheFile !== null) {
484*04fd306cSNickeau            if (file_exists($snippetCacheFile)) {
485*04fd306cSNickeau                unlink($snippetCacheFile);
486*04fd306cSNickeau            }
487*04fd306cSNickeau        }
488*04fd306cSNickeau    }
489*04fd306cSNickeau
490*04fd306cSNickeau    /**
491*04fd306cSNickeau     * @return CacheParser - the cache where the snippets are stored
492*04fd306cSNickeau     * Cache file
493*04fd306cSNickeau     * Using a cache parser, set the page id and will trigger
494*04fd306cSNickeau     * the parser cache use event in order to log/report the cache usage
495*04fd306cSNickeau     * At {@link action_plugin_combo_cache::createCacheReport()}
496*04fd306cSNickeau     */
497*04fd306cSNickeau    public
498*04fd306cSNickeau    function getSnippetCacheStore(): CacheParser
499*04fd306cSNickeau    {
500*04fd306cSNickeau        if (isset($this->snippetCache)) {
501*04fd306cSNickeau            return $this->snippetCache;
502*04fd306cSNickeau        }
503*04fd306cSNickeau        if ($this->isPathExecution()) {
504*04fd306cSNickeau            throw new ExceptionRuntimeInternal("A source path should be available as this is a path execution");
505*04fd306cSNickeau        }
506*04fd306cSNickeau        throw new ExceptionRuntime("There is no snippet cache store for a non-path execution");
507*04fd306cSNickeau
508*04fd306cSNickeau    }
509*04fd306cSNickeau
510*04fd306cSNickeau
511*04fd306cSNickeau    public
512*04fd306cSNickeau    function getDependenciesCacheStore(): CacheParser
513*04fd306cSNickeau    {
514*04fd306cSNickeau        return $this->outputCacheDependencies->getDependenciesCacheStore();
515*04fd306cSNickeau    }
516*04fd306cSNickeau
517*04fd306cSNickeau    public
518*04fd306cSNickeau    function getDependenciesCachePath(): LocalPath
519*04fd306cSNickeau    {
520*04fd306cSNickeau        $cachePath = $this->outputCacheDependencies->getDependenciesCacheStore()->cache;
521*04fd306cSNickeau        return LocalPath::createFromPathString($cachePath);
522*04fd306cSNickeau    }
523*04fd306cSNickeau
524*04fd306cSNickeau    /**
525*04fd306cSNickeau     * @return LocalPath the fetch path - start the process and returns a path. If the cache is on, return the {@link FetcherMarkup::getContentCachePath()}
526*04fd306cSNickeau     * @throws ExceptionCompile
527*04fd306cSNickeau     */
528*04fd306cSNickeau    function processIfNeededAndGetFetchPath(): LocalPath
529*04fd306cSNickeau    {
530*04fd306cSNickeau        $this->processIfNeeded();
531*04fd306cSNickeau
532*04fd306cSNickeau        /**
533*04fd306cSNickeau         * The cache path may have change due to the cache key rerouting
534*04fd306cSNickeau         * We should there always use the {@link FetcherMarkup::getContentCachePath()}
535*04fd306cSNickeau         * as fetch path
536*04fd306cSNickeau         */
537*04fd306cSNickeau        return $this->getContentCachePath();
538*04fd306cSNickeau
539*04fd306cSNickeau    }
540*04fd306cSNickeau
541*04fd306cSNickeau
542*04fd306cSNickeau    /**
543*04fd306cSNickeau     * @return $this
544*04fd306cSNickeau     * @throws ExceptionCompile
545*04fd306cSNickeau     */
546*04fd306cSNickeau    public function process(): FetcherMarkup
547*04fd306cSNickeau    {
548*04fd306cSNickeau
549*04fd306cSNickeau        $this->hasExecuted = true;
550*04fd306cSNickeau
551*04fd306cSNickeau        /**
552*04fd306cSNickeau         * Rendering
553*04fd306cSNickeau         */
554*04fd306cSNickeau        $executionContext = (ExecutionContext::getActualOrCreateFromEnv());
555*04fd306cSNickeau
556*04fd306cSNickeau        $extension = $this->getMime()->getExtension();
557*04fd306cSNickeau        switch ($extension) {
558*04fd306cSNickeau
559*04fd306cSNickeau            case MarkupRenderer::METADATA_EXTENSION:
560*04fd306cSNickeau                /**
561*04fd306cSNickeau                 * The user may ask just for the metadata
562*04fd306cSNickeau                 * and should then use the {@link self::getMetadata()}
563*04fd306cSNickeau                 * function instead
564*04fd306cSNickeau                 */
565*04fd306cSNickeau                break;
566*04fd306cSNickeau            case MarkupRenderer::INSTRUCTION_EXTENSION:
567*04fd306cSNickeau                /**
568*04fd306cSNickeau                 * The user may ask just for the instuctions
569*04fd306cSNickeau                 * and should then use the {@link self::getInstructions()}
570*04fd306cSNickeau                 * function to get the instructions
571*04fd306cSNickeau                 */
572*04fd306cSNickeau                return $this;
573*04fd306cSNickeau            default:
574*04fd306cSNickeau
575*04fd306cSNickeau                $instructions = $this->getInstructions();
576*04fd306cSNickeau
577*04fd306cSNickeau                /**
578*04fd306cSNickeau                 * Edge case: We delete here
579*04fd306cSNickeau                 * because the instructions may have been created by dokuwiki
580*04fd306cSNickeau                 * when we test for the cache with {@link CacheParser::useCache()}
581*04fd306cSNickeau                 */
582*04fd306cSNickeau                if ($this->deleteRootBlockElement) {
583*04fd306cSNickeau                    self::deleteRootPElementsIfRequested($instructions);
584*04fd306cSNickeau                }
585*04fd306cSNickeau
586*04fd306cSNickeau                if (!isset($this->builderName)) {
587*04fd306cSNickeau                    $this->builderName = $this->getMime()->getExtension();
588*04fd306cSNickeau                }
589*04fd306cSNickeau
590*04fd306cSNickeau                $executionContext->setExecutingMarkupHandler($this);
591*04fd306cSNickeau                try {
592*04fd306cSNickeau                    if ($this->isDocument()) {
593*04fd306cSNickeau                        $markupRenderer = MarkupRenderer::createFromMarkupInstructions($instructions, $this)
594*04fd306cSNickeau                            ->setRequestedMime($this->getMime())
595*04fd306cSNickeau                            ->setRendererName($this->builderName);
596*04fd306cSNickeau
597*04fd306cSNickeau                        $output = $markupRenderer->getOutput();
598*04fd306cSNickeau                        if ($output === null && !empty($instructions)) {
599*04fd306cSNickeau                            LogUtility::error("The renderer ({$this->builderName}) seems to have been not found");
600*04fd306cSNickeau                        }
601*04fd306cSNickeau                        $this->cacheAfterRendering = $markupRenderer->getCacheAfterRendering();
602*04fd306cSNickeau                    } else {
603*04fd306cSNickeau                        $output = MarkupDynamicRender::create($this->builderName)->processInstructions($instructions);
604*04fd306cSNickeau                    }
605*04fd306cSNickeau                } catch (\Exception $e) {
606*04fd306cSNickeau                    /**
607*04fd306cSNickeau                     * Example of errors;
608*04fd306cSNickeau                     * method_exists() expects parameter 2 to be string, array given
609*04fd306cSNickeau                     * inc\parserutils.php:672
610*04fd306cSNickeau                     */
611*04fd306cSNickeau                    throw new ExceptionCompile("An error has occurred while getting the output. Error: {$e->getMessage()}", self::CANONICAL, 1, $e);
612*04fd306cSNickeau
613*04fd306cSNickeau                } finally {
614*04fd306cSNickeau                    $executionContext->closeExecutingMarkupHandler();
615*04fd306cSNickeau                }
616*04fd306cSNickeau                if (is_array($output)) {
617*04fd306cSNickeau                    LogUtility::internalError("The output was an array", self::CANONICAL);
618*04fd306cSNickeau                    $this->fetchString = serialize($output);
619*04fd306cSNickeau                } else {
620*04fd306cSNickeau                    $this->fetchString = $output;
621*04fd306cSNickeau                }
622*04fd306cSNickeau
623*04fd306cSNickeau                break;
624*04fd306cSNickeau        }
625*04fd306cSNickeau
626*04fd306cSNickeau        /**
627*04fd306cSNickeau         * Storage of snippets or dependencies
628*04fd306cSNickeau         * none if this is not a path execution
629*04fd306cSNickeau         * and for now, metadata storage is done by dokuwiki
630*04fd306cSNickeau         */
631*04fd306cSNickeau        if (!$this->isPathExecution() || $this->mime->getExtension() === MarkupRenderer::METADATA_EXTENSION) {
632*04fd306cSNickeau            return $this;
633*04fd306cSNickeau        }
634*04fd306cSNickeau
635*04fd306cSNickeau
636*04fd306cSNickeau        /**
637*04fd306cSNickeau         * Snippets and cache dependencies are only for HTML rendering
638*04fd306cSNickeau         * Otherwise, otherwise other type rendering may override them
639*04fd306cSNickeau         * (such as analtyical json, ...)
640*04fd306cSNickeau         */
641*04fd306cSNickeau        if (in_array($this->getMime()->toString(), [Mime::XHTML, Mime::HTML])) {
642*04fd306cSNickeau
643*04fd306cSNickeau            /**
644*04fd306cSNickeau             * We make the Snippet store to Html store an atomic operation
645*04fd306cSNickeau             *
646*04fd306cSNickeau             * Why ? Because if the rendering of the page is stopped,
647*04fd306cSNickeau             * the cache of the HTML page may be stored but not the cache of the snippets
648*04fd306cSNickeau             * leading to a bad page because the next rendering will see then no snippets.
649*04fd306cSNickeau             */
650*04fd306cSNickeau            try {
651*04fd306cSNickeau                $this->storeSnippets();
652*04fd306cSNickeau            } catch (Exception $e) {
653*04fd306cSNickeau                // if any write os exception
654*04fd306cSNickeau                LogUtility::msg("Error while storing the xhtml content: {$e->getMessage()}");
655*04fd306cSNickeau                $this->removeSnippets();
656*04fd306cSNickeau            }
657*04fd306cSNickeau
658*04fd306cSNickeau            /**
659*04fd306cSNickeau             * Cache output dependencies
660*04fd306cSNickeau             * Reroute the cache output by runtime dependencies
661*04fd306cSNickeau             * set during processing
662*04fd306cSNickeau             */
663*04fd306cSNickeau            $this->outputCacheDependencies->storeDependencies();
664*04fd306cSNickeau            $this->outputCacheDependencies->rerouteCacheDestination($this->contentCache);
665*04fd306cSNickeau
666*04fd306cSNickeau        }
667*04fd306cSNickeau
668*04fd306cSNickeau        /**
669*04fd306cSNickeau         * We store always the output in the cache
670*04fd306cSNickeau         * if the cache is not on, the file is just overwritten
671*04fd306cSNickeau         *
672*04fd306cSNickeau         * We don't use
673*04fd306cSNickeau         * {{@link CacheParser::storeCache()}
674*04fd306cSNickeau         * because it uses the protected parameter `__nocache`
675*04fd306cSNickeau         * that will disallow the storage
676*04fd306cSNickeau         */
677*04fd306cSNickeau        io_saveFile($this->contentCache->cache, $this->fetchString);
678*04fd306cSNickeau
679*04fd306cSNickeau        return $this;
680*04fd306cSNickeau    }
681*04fd306cSNickeau
682*04fd306cSNickeau
683*04fd306cSNickeau    function getBuster(): string
684*04fd306cSNickeau    {
685*04fd306cSNickeau        // no buster
686*04fd306cSNickeau        return "";
687*04fd306cSNickeau    }
688*04fd306cSNickeau
689*04fd306cSNickeau    public
690*04fd306cSNickeau    function getFetcherName(): string
691*04fd306cSNickeau    {
692*04fd306cSNickeau        return "markup-fetcher";
693*04fd306cSNickeau    }
694*04fd306cSNickeau
695*04fd306cSNickeau
696*04fd306cSNickeau    private function getCacheAge(): int
697*04fd306cSNickeau    {
698*04fd306cSNickeau
699*04fd306cSNickeau        $extension = $this->getMime()->getExtension();
700*04fd306cSNickeau        switch ($extension) {
701*04fd306cSNickeau            case self::XHTML_MODE:
702*04fd306cSNickeau                if (!Site::isHtmlRenderCacheOn()) {
703*04fd306cSNickeau                    return 0;
704*04fd306cSNickeau                }
705*04fd306cSNickeau                break;
706*04fd306cSNickeau            case MarkupRenderer::INSTRUCTION_EXTENSION:
707*04fd306cSNickeau                // indefinitely
708*04fd306cSNickeau                return self::MAX_CACHE_AGE;
709*04fd306cSNickeau        }
710*04fd306cSNickeau        try {
711*04fd306cSNickeau            $requestedCache = $this->getRequestedCache();
712*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
713*04fd306cSNickeau            $requestedCache = IFetcherAbs::RECACHE_VALUE;
714*04fd306cSNickeau        }
715*04fd306cSNickeau        $cacheAge = $this->getCacheMaxAgeInSec($requestedCache);
716*04fd306cSNickeau        return $this->cacheAfterRendering ? $cacheAge : 0;
717*04fd306cSNickeau
718*04fd306cSNickeau    }
719*04fd306cSNickeau
720*04fd306cSNickeau
721*04fd306cSNickeau    public function __toString()
722*04fd306cSNickeau    {
723*04fd306cSNickeau
724*04fd306cSNickeau        return parent::__toString() . " ({$this->getSourceName()}, {$this->getMime()->toString()})";
725*04fd306cSNickeau    }
726*04fd306cSNickeau
727*04fd306cSNickeau
728*04fd306cSNickeau    /**
729*04fd306cSNickeau     * @throws ExceptionBadArgument
730*04fd306cSNickeau     */
731*04fd306cSNickeau    public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherMarkup
732*04fd306cSNickeau    {
733*04fd306cSNickeau        parent::buildFromTagAttributes($tagAttributes);
734*04fd306cSNickeau        return $this;
735*04fd306cSNickeau    }
736*04fd306cSNickeau
737*04fd306cSNickeau
738*04fd306cSNickeau    /**
739*04fd306cSNickeau     * @return LocalPath - the cache path is where the result is stored if the cache is on
740*04fd306cSNickeau     * The cache path may have change due to the cache key rerouting
741*04fd306cSNickeau     * We should there always use the {@link FetcherMarkup::getContentCachePath()}
742*04fd306cSNickeau     * as fetch path
743*04fd306cSNickeau     */
744*04fd306cSNickeau    public function getContentCachePath(): LocalPath
745*04fd306cSNickeau    {
746*04fd306cSNickeau        $path = $this->contentCache->cache;
747*04fd306cSNickeau        return LocalPath::createFromPathString($path);
748*04fd306cSNickeau    }
749*04fd306cSNickeau
750*04fd306cSNickeau
751*04fd306cSNickeau    public function getOutputCacheDependencies(): MarkupCacheDependencies
752*04fd306cSNickeau    {
753*04fd306cSNickeau        return $this->outputCacheDependencies;
754*04fd306cSNickeau    }
755*04fd306cSNickeau
756*04fd306cSNickeau
757*04fd306cSNickeau    /**
758*04fd306cSNickeau     * @return string - with replacement if any
759*04fd306cSNickeau     * TODO: edit button replacement could be a script tag with a json, permits to do DOM manipulation
760*04fd306cSNickeau     * @throws ExceptionCompile - if any processing error occurs
761*04fd306cSNickeau     */
762*04fd306cSNickeau    public function getFetchString(): string
763*04fd306cSNickeau    {
764*04fd306cSNickeau        $this->processIfNeeded();
765*04fd306cSNickeau
766*04fd306cSNickeau        if (!$this->isPathExecution()) {
767*04fd306cSNickeau            return $this->fetchString;
768*04fd306cSNickeau        }
769*04fd306cSNickeau
770*04fd306cSNickeau        /**
771*04fd306cSNickeau         * Source path execution
772*04fd306cSNickeau         * The cache path may have change due to the cache key rerouting
773*04fd306cSNickeau         * We should there always use the {@link FetcherMarkup::getContentCachePath()}
774*04fd306cSNickeau         * as fetch path
775*04fd306cSNickeau         */
776*04fd306cSNickeau        $path = $this->getContentCachePath();
777*04fd306cSNickeau        try {
778*04fd306cSNickeau            $text = FileSystems::getContent($path);
779*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
780*04fd306cSNickeau            throw new ExceptionRuntime("Internal error: The fetch path should exists.", self::CANONICAL, 1, $e);
781*04fd306cSNickeau        }
782*04fd306cSNickeau
783*04fd306cSNickeau        /**
784*04fd306cSNickeau         * Edit button Processing for XHtml
785*04fd306cSNickeau         * (Path is mandatory to create the buttons)
786*04fd306cSNickeau         */
787*04fd306cSNickeau        if (!in_array($this->getMime()->getExtension(), ["html", "xhtml"])) {
788*04fd306cSNickeau            return $text;
789*04fd306cSNickeau        }
790*04fd306cSNickeau        try {
791*04fd306cSNickeau            if ($this->getSourcePath()->toWikiPath()->getDrive() !== WikiPath::MARKUP_DRIVE) {
792*04fd306cSNickeau                // case when this is a default page in the resource/template directory
793*04fd306cSNickeau                return EditButton::deleteAll($text);
794*04fd306cSNickeau            }
795*04fd306cSNickeau        } catch (ExceptionNotFound|ExceptionCast $e) {
796*04fd306cSNickeau            // not a wiki path
797*04fd306cSNickeau        }
798*04fd306cSNickeau        return EditButton::replaceOrDeleteAll($text);
799*04fd306cSNickeau
800*04fd306cSNickeau    }
801*04fd306cSNickeau
802*04fd306cSNickeau
803*04fd306cSNickeau    public function getLabel(): string
804*04fd306cSNickeau    {
805*04fd306cSNickeau        try {
806*04fd306cSNickeau            $sourcePath = $this->getSourcePath();
807*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
808*04fd306cSNickeau            return self::MARKUP_DYNAMIC_EXECUTION_NAME;
809*04fd306cSNickeau        }
810*04fd306cSNickeau        return ResourceName::getFromPath($sourcePath);
811*04fd306cSNickeau    }
812*04fd306cSNickeau
813*04fd306cSNickeau
814*04fd306cSNickeau    public function getRequestedContextPath(): WikiPath
815*04fd306cSNickeau    {
816*04fd306cSNickeau        return $this->requestedContextPath;
817*04fd306cSNickeau    }
818*04fd306cSNickeau
819*04fd306cSNickeau
820*04fd306cSNickeau    /**
821*04fd306cSNickeau     * @throws ExceptionNotFound
822*04fd306cSNickeau     */
823*04fd306cSNickeau    public function getSourcePath(): Path
824*04fd306cSNickeau    {
825*04fd306cSNickeau        if (isset($this->markupSourcePath)) {
826*04fd306cSNickeau            return $this->markupSourcePath;
827*04fd306cSNickeau        }
828*04fd306cSNickeau        throw new ExceptionNotFound("No source path for this markup");
829*04fd306cSNickeau    }
830*04fd306cSNickeau
831*04fd306cSNickeau    /**
832*04fd306cSNickeau     * Utility class that return the source path
833*04fd306cSNickeau     * @return Path
834*04fd306cSNickeau     * @throws ExceptionNotFound
835*04fd306cSNickeau     */
836*04fd306cSNickeau    public function getRequestedExecutingPath(): Path
837*04fd306cSNickeau    {
838*04fd306cSNickeau        return $this->getSourcePath();
839*04fd306cSNickeau    }
840*04fd306cSNickeau
841*04fd306cSNickeau    /**
842*04fd306cSNickeau     * @return Path|null - utility class to get the source markup path or null (if this is a markup snippet/string rendering)
843*04fd306cSNickeau     */
844*04fd306cSNickeau    public function getExecutingPathOrNull(): ?Path
845*04fd306cSNickeau    {
846*04fd306cSNickeau        try {
847*04fd306cSNickeau            return $this->getSourcePath();
848*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
849*04fd306cSNickeau            return null;
850*04fd306cSNickeau        }
851*04fd306cSNickeau    }
852*04fd306cSNickeau
853*04fd306cSNickeau
854*04fd306cSNickeau    /**
855*04fd306cSNickeau     * @param string $componentId
856*04fd306cSNickeau     * @return Snippet[]
857*04fd306cSNickeau     */
858*04fd306cSNickeau    public function getSnippetsForComponent(string $componentId): array
859*04fd306cSNickeau    {
860*04fd306cSNickeau
861*04fd306cSNickeau        $snippets = $this->getSnippets();
862*04fd306cSNickeau        $snippetsForComponent = [];
863*04fd306cSNickeau        foreach ($snippets as $snippet) {
864*04fd306cSNickeau            try {
865*04fd306cSNickeau                if ($snippet->getComponentId() === $componentId) {
866*04fd306cSNickeau                    $snippetsForComponent[] = $snippet;
867*04fd306cSNickeau                }
868*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
869*04fd306cSNickeau                //
870*04fd306cSNickeau            }
871*04fd306cSNickeau        }
872*04fd306cSNickeau        return $snippetsForComponent;
873*04fd306cSNickeau
874*04fd306cSNickeau    }
875*04fd306cSNickeau
876*04fd306cSNickeau    /**
877*04fd306cSNickeau     * @return Snippet[]
878*04fd306cSNickeau     */
879*04fd306cSNickeau    public function getSnippets(): array
880*04fd306cSNickeau    {
881*04fd306cSNickeau
882*04fd306cSNickeau        $snippets = $this->localSnippets;
883*04fd306cSNickeau
884*04fd306cSNickeau        /**
885*04fd306cSNickeau         * Old ways where snippets were added to the global scope
886*04fd306cSNickeau         * and not to the fetcher markup via {@link self::addSnippet()}
887*04fd306cSNickeau         *
888*04fd306cSNickeau         * During the transition, we support the two
889*04fd306cSNickeau         *
890*04fd306cSNickeau         * Note that with the new system where render code
891*04fd306cSNickeau         * can access this object via {@link ExecutionContext::getExecutingMarkupHandler()}
892*04fd306cSNickeau         * the code may had snippets without any id
893*04fd306cSNickeau         * (For the time being, not yet)
894*04fd306cSNickeau         */
895*04fd306cSNickeau        try {
896*04fd306cSNickeau            $slotId = $this->getSourcePath()->toWikiPath()->getWikiId();
897*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
898*04fd306cSNickeau            // a markup string run
899*04fd306cSNickeau            return $snippets;
900*04fd306cSNickeau        } catch (ExceptionCast $e) {
901*04fd306cSNickeau            // not a wiki path
902*04fd306cSNickeau            return $snippets;
903*04fd306cSNickeau        }
904*04fd306cSNickeau
905*04fd306cSNickeau        $snippetManager = PluginUtility::getSnippetManager();
906*04fd306cSNickeau        $oldWaySnippets = $snippetManager->getSnippetsForSlot($slotId);
907*04fd306cSNickeau        return array_merge($oldWaySnippets, $snippets);
908*04fd306cSNickeau
909*04fd306cSNickeau    }
910*04fd306cSNickeau
911*04fd306cSNickeau    /**
912*04fd306cSNickeau     * @param Snippet $snippet
913*04fd306cSNickeau     * @return FetcherMarkup
914*04fd306cSNickeau     */
915*04fd306cSNickeau    public function addSnippet(Snippet $snippet): FetcherMarkup
916*04fd306cSNickeau    {
917*04fd306cSNickeau        /**
918*04fd306cSNickeau         * Snippet should be added only when they can be store
919*04fd306cSNickeau         * (ie when this is path execution)
920*04fd306cSNickeau         * If this is not a path execution, the snippet cannot be
921*04fd306cSNickeau         * stored in a cache and are therefore lost if not used
922*04fd306cSNickeau         */
923*04fd306cSNickeau
924*04fd306cSNickeau        /**
925*04fd306cSNickeau         * If there is a parent markup handler
926*04fd306cSNickeau         * Store the snippets there
927*04fd306cSNickeau         */
928*04fd306cSNickeau        if (isset($this->parentMarkupHandler)) {
929*04fd306cSNickeau            $this->parentMarkupHandler->addSnippet($snippet);
930*04fd306cSNickeau            return $this;
931*04fd306cSNickeau        }
932*04fd306cSNickeau
933*04fd306cSNickeau        if (!$this->isPathExecution()
934*04fd306cSNickeau            && !$this->isNonPathStandaloneExecution
935*04fd306cSNickeau            // In preview, there is no parent handler because we didn't take over
936*04fd306cSNickeau            && ExecutionContext::getActualOrCreateFromEnv()->getExecutingAction() !== ExecutionContext::PREVIEW_ACTION
937*04fd306cSNickeau        ) {
938*04fd306cSNickeau            LogUtility::warning("The execution ($this) is not a path execution. The snippet $snippet will not be preserved after initial rendering. Set the execution as standalone or set a parent markup handler.");
939*04fd306cSNickeau        }
940*04fd306cSNickeau        if (!in_array($this->getMime()->toString(), [Mime::XHTML, Mime::HTML])) {
941*04fd306cSNickeau            LogUtility::warning("The execution ($this) is not a HTML execution. The snippet $snippet will not be preserved because they are reserved for XHMTL execution");
942*04fd306cSNickeau        }
943*04fd306cSNickeau
944*04fd306cSNickeau        $snippetGuid = $snippet->getPath()->toUriString();
945*04fd306cSNickeau        $this->localSnippets[$snippetGuid] = $snippet;
946*04fd306cSNickeau        return $this;
947*04fd306cSNickeau
948*04fd306cSNickeau
949*04fd306cSNickeau    }
950*04fd306cSNickeau
951*04fd306cSNickeau    /**
952*04fd306cSNickeau     * @return bool true if the markup string comes from a path
953*04fd306cSNickeau     * This is motsly important for cache as we use the path as the cache key
954*04fd306cSNickeau     * (Cache:
955*04fd306cSNickeau     * * of the {@link self::getInstructions() instructions},
956*04fd306cSNickeau     * * of the {@link self::getOutputCacheDependencies() output dependencies}
957*04fd306cSNickeau     * * of the {@link self::getSnippets() snippets}
958*04fd306cSNickeau     * * of the {@link self::processMetadataIfNotYetDone() metadata}
959*04fd306cSNickeau     *
960*04fd306cSNickeau     * The rule is this is a path execution of the {@link self::$markupSourcePath executing source path} is set.
961*04fd306cSNickeau     *
962*04fd306cSNickeau     * Ie this is not a path execution, if the input is:
963*04fd306cSNickeau     * * {@link self::$requestedInstructions} (used for templating)
964*04fd306cSNickeau     * * a {@link self::$markupString} (used for test or webcode)
965*04fd306cSNickeau     *
966*04fd306cSNickeau     */
967*04fd306cSNickeau    public function isPathExecution(): bool
968*04fd306cSNickeau    {
969*04fd306cSNickeau        if (isset($this->markupSourcePath)) {
970*04fd306cSNickeau            return true;
971*04fd306cSNickeau        }
972*04fd306cSNickeau        return false;
973*04fd306cSNickeau    }
974*04fd306cSNickeau
975*04fd306cSNickeau    /**
976*04fd306cSNickeau     * @throws ExceptionCompile - if any processing errors occurs
977*04fd306cSNickeau     */
978*04fd306cSNickeau    public function processIfNeeded(): FetcherMarkup
979*04fd306cSNickeau    {
980*04fd306cSNickeau
981*04fd306cSNickeau        if (!$this->shouldProcess()) {
982*04fd306cSNickeau            return $this;
983*04fd306cSNickeau        }
984*04fd306cSNickeau
985*04fd306cSNickeau        $this->process();
986*04fd306cSNickeau        return $this;
987*04fd306cSNickeau
988*04fd306cSNickeau    }
989*04fd306cSNickeau
990*04fd306cSNickeau
991*04fd306cSNickeau    /**
992*04fd306cSNickeau     * @return array - the markup instructions
993*04fd306cSNickeau     * @throws ExceptionNotExists - if the executing markup file does not exist
994*04fd306cSNickeau     */
995*04fd306cSNickeau    public function getInstructions(): array
996*04fd306cSNickeau    {
997*04fd306cSNickeau
998*04fd306cSNickeau        if (isset($this->requestedInstructions)) {
999*04fd306cSNickeau
1000*04fd306cSNickeau            return $this->requestedInstructions;
1001*04fd306cSNickeau
1002*04fd306cSNickeau        }
1003*04fd306cSNickeau
1004*04fd306cSNickeau        /**
1005*04fd306cSNickeau         * We create a fetcher markup to not have the same {@link self::getId()}
1006*04fd306cSNickeau         * on execution
1007*04fd306cSNickeau         */
1008*04fd306cSNickeau        if (ExecutionContext::getActualOrCreateFromEnv()->hasExecutingMarkupHandler()) {
1009*04fd306cSNickeau            $fetcherMarkupBuilder = FetcherMarkup::confChild();
1010*04fd306cSNickeau        } else {
1011*04fd306cSNickeau            $fetcherMarkupBuilder = FetcherMarkup::confRoot();
1012*04fd306cSNickeau        }
1013*04fd306cSNickeau        $fetcherMarkupBuilder = $fetcherMarkupBuilder
1014*04fd306cSNickeau            ->setRequestedMime(Mime::create(Mime::INSTRUCTIONS))
1015*04fd306cSNickeau            ->setRequestedRenderer(FetcherMarkupInstructions::NAME)
1016*04fd306cSNickeau            ->setIsDocument($this->isDoc)
1017*04fd306cSNickeau            ->setRequestedContextPath($this->getRequestedContextPath());
1018*04fd306cSNickeau        if ($this->isPathExecution()) {
1019*04fd306cSNickeau            $fetcherMarkupBuilder->setRequestedExecutingPath($this->getExecutingPathOrFail());
1020*04fd306cSNickeau        } else {
1021*04fd306cSNickeau            $fetcherMarkupBuilder->setRequestedMarkupString($this->markupString);
1022*04fd306cSNickeau        }
1023*04fd306cSNickeau        $fetcherMarkup = $fetcherMarkupBuilder->build();
1024*04fd306cSNickeau        return $fetcherMarkup->getProcessedInstructions();
1025*04fd306cSNickeau
1026*04fd306cSNickeau
1027*04fd306cSNickeau    }
1028*04fd306cSNickeau
1029*04fd306cSNickeau
1030*04fd306cSNickeau    /**
1031*04fd306cSNickeau     * @return bool - a document
1032*04fd306cSNickeau     *
1033*04fd306cSNickeau     * A document will get an {@link Outline} processing
1034*04fd306cSNickeau     * while a {@link self::isFragment() fragment} will not.
1035*04fd306cSNickeau     */
1036*04fd306cSNickeau    public function isDocument(): bool
1037*04fd306cSNickeau    {
1038*04fd306cSNickeau
1039*04fd306cSNickeau        return $this->isDoc;
1040*04fd306cSNickeau
1041*04fd306cSNickeau    }
1042*04fd306cSNickeau
1043*04fd306cSNickeau    public function getSnippetManager(): SnippetSystem
1044*04fd306cSNickeau    {
1045*04fd306cSNickeau        return PluginUtility::getSnippetManager();
1046*04fd306cSNickeau    }
1047*04fd306cSNickeau
1048*04fd306cSNickeau    /**
1049*04fd306cSNickeau     * @throws ExceptionBadSyntax
1050*04fd306cSNickeau     * @throws ExceptionCompile
1051*04fd306cSNickeau     */
1052*04fd306cSNickeau    public function getFetchStringAsDom(): XmlDocument
1053*04fd306cSNickeau    {
1054*04fd306cSNickeau        return XmlDocument::createXmlDocFromMarkup($this->getFetchString());
1055*04fd306cSNickeau    }
1056*04fd306cSNickeau
1057*04fd306cSNickeau    public function getSnippetsAsHtmlString(): string
1058*04fd306cSNickeau    {
1059*04fd306cSNickeau
1060*04fd306cSNickeau        try {
1061*04fd306cSNickeau            $globalSnippets = SnippetSystem::getFromContext()->getSnippetsForSlot($this->getRequestedExecutingPath()->toAbsoluteId());
1062*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1063*04fd306cSNickeau            // string execution
1064*04fd306cSNickeau            $globalSnippets = [];
1065*04fd306cSNickeau        }
1066*04fd306cSNickeau        $allSnippets = array_merge($globalSnippets, $this->localSnippets);
1067*04fd306cSNickeau        return SnippetSystem::toHtmlFromSnippetArray($allSnippets);
1068*04fd306cSNickeau
1069*04fd306cSNickeau    }
1070*04fd306cSNickeau
1071*04fd306cSNickeau    public function isFragment(): bool
1072*04fd306cSNickeau    {
1073*04fd306cSNickeau        return $this->isDocument() === false;
1074*04fd306cSNickeau    }
1075*04fd306cSNickeau
1076*04fd306cSNickeau    private function getMarkupStringToExecute(): string
1077*04fd306cSNickeau    {
1078*04fd306cSNickeau        if (isset($this->markupString)) {
1079*04fd306cSNickeau            return $this->markupString;
1080*04fd306cSNickeau        } else {
1081*04fd306cSNickeau            try {
1082*04fd306cSNickeau                $sourcePath = $this->getSourcePath();
1083*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
1084*04fd306cSNickeau                throw new ExceptionRuntimeInternal("A markup or a source markup path should be specified.");
1085*04fd306cSNickeau            }
1086*04fd306cSNickeau            try {
1087*04fd306cSNickeau                return FileSystems::getContent($sourcePath);
1088*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
1089*04fd306cSNickeau                LogUtility::error("The path ($sourcePath) does not exist, we have set the markup to the empty string during rendering. If you want to delete the cache path, ask it via the cache path function", self::CANONICAL, $e);
1090*04fd306cSNickeau                return "";
1091*04fd306cSNickeau            }
1092*04fd306cSNickeau        }
1093*04fd306cSNickeau    }
1094*04fd306cSNickeau
1095*04fd306cSNickeau    public function getContextData(): array
1096*04fd306cSNickeau    {
1097*04fd306cSNickeau        if (isset($this->contextData)) {
1098*04fd306cSNickeau            return $this->contextData;
1099*04fd306cSNickeau        }
1100*04fd306cSNickeau        $this->contextData = MarkupPath::createPageFromPathObject($this->getRequestedContextPath())->getMetadataForRendering();
1101*04fd306cSNickeau        return $this->contextData;
1102*04fd306cSNickeau    }
1103*04fd306cSNickeau
1104*04fd306cSNickeau
1105*04fd306cSNickeau    public function getToc(): array
1106*04fd306cSNickeau    {
1107*04fd306cSNickeau
1108*04fd306cSNickeau        if (isset($this->toc)) {
1109*04fd306cSNickeau            return $this->toc;
1110*04fd306cSNickeau        }
1111*04fd306cSNickeau        try {
1112*04fd306cSNickeau            return TOC::createForPage($this->getRequestedExecutingPath())->getValue();
1113*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1114*04fd306cSNickeau            // no executing page or no value
1115*04fd306cSNickeau        }
1116*04fd306cSNickeau        /**
1117*04fd306cSNickeau         * Derived TOC from instructions
1118*04fd306cSNickeau         */
1119*04fd306cSNickeau        return Outline::createFromCallStack(CallStack::createFromInstructions($this->getInstructions()))->toTocDokuwikiFormat();
1120*04fd306cSNickeau
1121*04fd306cSNickeau
1122*04fd306cSNickeau    }
1123*04fd306cSNickeau
1124*04fd306cSNickeau    public function getInstructionsPath(): LocalPath
1125*04fd306cSNickeau    {
1126*04fd306cSNickeau        $path = $this->instructionsCache->cache;
1127*04fd306cSNickeau        return LocalPath::createFromPathString($path);
1128*04fd306cSNickeau    }
1129*04fd306cSNickeau
1130*04fd306cSNickeau    public function getOutline(): Outline
1131*04fd306cSNickeau    {
1132*04fd306cSNickeau        $instructions = $this->getInstructions();
1133*04fd306cSNickeau        $callStack = CallStack::createFromInstructions($instructions);
1134*04fd306cSNickeau        try {
1135*04fd306cSNickeau            $markupPath = MarkupPath::createPageFromPathObject($this->getRequestedExecutingPath());
1136*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1137*04fd306cSNickeau            $markupPath = null;
1138*04fd306cSNickeau        }
1139*04fd306cSNickeau        return Outline::createFromCallStack($callStack, $markupPath);
1140*04fd306cSNickeau    }
1141*04fd306cSNickeau
1142*04fd306cSNickeau
1143*04fd306cSNickeau    public function getMetadata(): array
1144*04fd306cSNickeau    {
1145*04fd306cSNickeau
1146*04fd306cSNickeau        $this->processMetadataIfNotYetDone();
1147*04fd306cSNickeau        return $this->meta;
1148*04fd306cSNickeau
1149*04fd306cSNickeau    }
1150*04fd306cSNickeau
1151*04fd306cSNickeau
1152*04fd306cSNickeau    /**
1153*04fd306cSNickeau     * Adaptation of {@link p_get_metadata()}
1154*04fd306cSNickeau     * to take into account {@link self::getInstructions()}
1155*04fd306cSNickeau     * where we can just pass our own instructions.
1156*04fd306cSNickeau     *
1157*04fd306cSNickeau     * And yes, adaptation of {@link p_get_metadata()}
1158*04fd306cSNickeau     * that process the metadata. Yeah, it calls {@link p_render_metadata()}
1159*04fd306cSNickeau     * and save them
1160*04fd306cSNickeau     *
1161*04fd306cSNickeau     */
1162*04fd306cSNickeau    public function processMetadataIfNotYetDone(): FetcherMarkup
1163*04fd306cSNickeau    {
1164*04fd306cSNickeau
1165*04fd306cSNickeau        /**
1166*04fd306cSNickeau         * Already set ?
1167*04fd306cSNickeau         */
1168*04fd306cSNickeau        if (isset($this->meta)) {
1169*04fd306cSNickeau            return $this;
1170*04fd306cSNickeau        }
1171*04fd306cSNickeau
1172*04fd306cSNickeau        $actualMeta = [];
1173*04fd306cSNickeau
1174*04fd306cSNickeau        /**
1175*04fd306cSNickeau         * We wrap the whole block
1176*04fd306cSNickeau         * because {@link CacheRenderer::useCache()}
1177*04fd306cSNickeau         * and the renderer needs it
1178*04fd306cSNickeau         */
1179*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()->setExecutingMarkupHandler($this);
1180*04fd306cSNickeau        try {
1181*04fd306cSNickeau
1182*04fd306cSNickeau            /**
1183*04fd306cSNickeau             * Can we read from the meta file
1184*04fd306cSNickeau             */
1185*04fd306cSNickeau
1186*04fd306cSNickeau
1187*04fd306cSNickeau            if ($this->isPathExecution()) {
1188*04fd306cSNickeau
1189*04fd306cSNickeau                /**
1190*04fd306cSNickeau                 * If the meta file exists
1191*04fd306cSNickeau                 */
1192*04fd306cSNickeau                if (FileSystems::exists($this->getMetaPathOrFail())) {
1193*04fd306cSNickeau
1194*04fd306cSNickeau                    $executingPath = $this->getExecutingPathOrFail();
1195*04fd306cSNickeau                    $actualMeta = MetadataDokuWikiStore::getOrCreateFromResource(MarkupPath::createPageFromPathObject($executingPath))
1196*04fd306cSNickeau                        ->getDataCurrentAndPersistent();
1197*04fd306cSNickeau
1198*04fd306cSNickeau                    /**
1199*04fd306cSNickeau                     * The metadata useCache function has side effect
1200*04fd306cSNickeau                     * and triggers a render that fails if the wiki file does not exists
1201*04fd306cSNickeau                     */
1202*04fd306cSNickeau                    $depends['files'][] = $this->instructionsCache->cache;
1203*04fd306cSNickeau                    $depends['files'][] = $executingPath->toAbsolutePath()->toAbsoluteId();
1204*04fd306cSNickeau                    $useCache = $this->metaCache->useCache($depends);
1205*04fd306cSNickeau                    if ($useCache) {
1206*04fd306cSNickeau                        $this->meta = $actualMeta;
1207*04fd306cSNickeau                        return $this;
1208*04fd306cSNickeau                    }
1209*04fd306cSNickeau                }
1210*04fd306cSNickeau            }
1211*04fd306cSNickeau
1212*04fd306cSNickeau            /**
1213*04fd306cSNickeau             * Process and derived meta
1214*04fd306cSNickeau             */
1215*04fd306cSNickeau            try {
1216*04fd306cSNickeau                $wikiId = $this->getRequestedExecutingPath()->toWikiPath()->getWikiId();
1217*04fd306cSNickeau            } catch (ExceptionCast|ExceptionNotFound $e) {
1218*04fd306cSNickeau                // not a wiki path execution
1219*04fd306cSNickeau                $wikiId = null;
1220*04fd306cSNickeau            }
1221*04fd306cSNickeau
1222*04fd306cSNickeau            /**
1223*04fd306cSNickeau             * Dokuwiki global variable used to see if the process is in rendering mode
1224*04fd306cSNickeau             * See {@link p_get_metadata()}
1225*04fd306cSNickeau             * Store the original metadata in the global $METADATA_RENDERERS
1226*04fd306cSNickeau             * ({@link p_set_metadata()} use it)
1227*04fd306cSNickeau             */
1228*04fd306cSNickeau            global $METADATA_RENDERERS;
1229*04fd306cSNickeau            $METADATA_RENDERERS[$wikiId] =& $actualMeta;
1230*04fd306cSNickeau
1231*04fd306cSNickeau            // add an extra key for the event - to tell event handlers the page whose metadata this is
1232*04fd306cSNickeau            $actualMeta['page'] = $wikiId;
1233*04fd306cSNickeau            $evt = new \dokuwiki\Extension\Event('PARSER_METADATA_RENDER', $actualMeta);
1234*04fd306cSNickeau            if ($evt->advise_before()) {
1235*04fd306cSNickeau
1236*04fd306cSNickeau                // get instructions (from string or file)
1237*04fd306cSNickeau                $instructions = $this->getInstructions();
1238*04fd306cSNickeau
1239*04fd306cSNickeau                // set up the renderer
1240*04fd306cSNickeau                $renderer = new Doku_Renderer_metadata();
1241*04fd306cSNickeau
1242*04fd306cSNickeau
1243*04fd306cSNickeau                /**
1244*04fd306cSNickeau                 * Runtime/ Derived metadata
1245*04fd306cSNickeau                 * The runtime meta are not even deleted
1246*04fd306cSNickeau                 * (See {@link p_render_metadata()}
1247*04fd306cSNickeau                 */
1248*04fd306cSNickeau                $renderer->meta =& $actualMeta['current'];
1249*04fd306cSNickeau
1250*04fd306cSNickeau                /**
1251*04fd306cSNickeau                 * The {@link Doku_Renderer_metadata}
1252*04fd306cSNickeau                 * will fail if the file and the date modified property does not exist
1253*04fd306cSNickeau                 */
1254*04fd306cSNickeau                try {
1255*04fd306cSNickeau                    $path = $this->getRequestedExecutingPath();
1256*04fd306cSNickeau                    if (!FileSystems::exists($path)) {
1257*04fd306cSNickeau                        $renderer->meta['date']['modified'] = null;
1258*04fd306cSNickeau                    }
1259*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
1260*04fd306cSNickeau                    // ok
1261*04fd306cSNickeau                }
1262*04fd306cSNickeau
1263*04fd306cSNickeau                /**
1264*04fd306cSNickeau                 * The persistent data are now available
1265*04fd306cSNickeau                 */
1266*04fd306cSNickeau                $renderer->persistent =& $actualMeta['persistent'];
1267*04fd306cSNickeau
1268*04fd306cSNickeau                // Loop through the instructions
1269*04fd306cSNickeau                foreach ($instructions as $instruction) {
1270*04fd306cSNickeau                    // execute the callback against the renderer
1271*04fd306cSNickeau                    call_user_func_array(array(&$renderer, $instruction[0]), (array)$instruction[1]);
1272*04fd306cSNickeau                }
1273*04fd306cSNickeau
1274*04fd306cSNickeau                $evt->result = array('current' => &$renderer->meta, 'persistent' => &$renderer->persistent);
1275*04fd306cSNickeau
1276*04fd306cSNickeau            }
1277*04fd306cSNickeau            $evt->advise_after();
1278*04fd306cSNickeau
1279*04fd306cSNickeau            $this->meta = $evt->result;
1280*04fd306cSNickeau
1281*04fd306cSNickeau            /**
1282*04fd306cSNickeau             * Dokuwiki global variable
1283*04fd306cSNickeau             * See {@link p_get_metadata()}
1284*04fd306cSNickeau             */
1285*04fd306cSNickeau            unset($METADATA_RENDERERS[$wikiId]);
1286*04fd306cSNickeau
1287*04fd306cSNickeau            /**
1288*04fd306cSNickeau             * Storage
1289*04fd306cSNickeau             */
1290*04fd306cSNickeau            if ($wikiId !== null) {
1291*04fd306cSNickeau                p_save_metadata($wikiId, $this->meta);
1292*04fd306cSNickeau                $this->metaCache->storeCache(time());
1293*04fd306cSNickeau            }
1294*04fd306cSNickeau
1295*04fd306cSNickeau        } finally {
1296*04fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
1297*04fd306cSNickeau        }
1298*04fd306cSNickeau        return $this;
1299*04fd306cSNickeau
1300*04fd306cSNickeau    }
1301*04fd306cSNickeau
1302*04fd306cSNickeau    /**
1303*04fd306cSNickeau     * @throws ExceptionNotFound
1304*04fd306cSNickeau     */
1305*04fd306cSNickeau    public function getMetadataPath(): LocalPath
1306*04fd306cSNickeau    {
1307*04fd306cSNickeau        if (isset($this->metaPath)) {
1308*04fd306cSNickeau            return $this->metaPath;
1309*04fd306cSNickeau        }
1310*04fd306cSNickeau        throw new ExceptionNotFound("No meta path for this markup");
1311*04fd306cSNickeau    }
1312*04fd306cSNickeau
1313*04fd306cSNickeau    /**
1314*04fd306cSNickeau     * A wrapper from when we are in a code block
1315*04fd306cSNickeau     * were we expect to be a {@link self::isPathExecution()}
1316*04fd306cSNickeau     * All path should then be available
1317*04fd306cSNickeau     * @return Path
1318*04fd306cSNickeau     */
1319*04fd306cSNickeau    private
1320*04fd306cSNickeau    function getExecutingPathOrFail(): Path
1321*04fd306cSNickeau    {
1322*04fd306cSNickeau        try {
1323*04fd306cSNickeau            return $this->getRequestedExecutingPath();
1324*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1325*04fd306cSNickeau            throw new ExceptionRuntime($e);
1326*04fd306cSNickeau        }
1327*04fd306cSNickeau    }
1328*04fd306cSNickeau
1329*04fd306cSNickeau    /**
1330*04fd306cSNickeau     * A wrapper from when we are in a code block
1331*04fd306cSNickeau     * were we expect to be a {@link self::isPathExecution()}
1332*04fd306cSNickeau     * All path should then be available
1333*04fd306cSNickeau     * @return Path
1334*04fd306cSNickeau     */
1335*04fd306cSNickeau    private
1336*04fd306cSNickeau    function getMetaPathOrFail()
1337*04fd306cSNickeau    {
1338*04fd306cSNickeau        try {
1339*04fd306cSNickeau            return $this->getMetadataPath();
1340*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
1341*04fd306cSNickeau            throw new ExceptionRuntime($e);
1342*04fd306cSNickeau        }
1343*04fd306cSNickeau    }
1344*04fd306cSNickeau
1345*04fd306cSNickeau    /**
1346*04fd306cSNickeau     * Process the instructions
1347*04fd306cSNickeau     * TODO: move to a FetcherMarkup by tree instruction and array/encoding mime
1348*04fd306cSNickeau     * @return $this
1349*04fd306cSNickeau     */
1350*04fd306cSNickeau    public function processInstructions(): FetcherMarkup
1351*04fd306cSNickeau    {
1352*04fd306cSNickeau        if (isset($this->processedInstructions)) {
1353*04fd306cSNickeau            return $this;
1354*04fd306cSNickeau        }
1355*04fd306cSNickeau
1356*04fd306cSNickeau        $markup = $this->getMarkupStringToExecute();
1357*04fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()
1358*04fd306cSNickeau            ->setExecutingMarkupHandler($this);
1359*04fd306cSNickeau        try {
1360*04fd306cSNickeau            $markupRenderer = MarkupRenderer::createFromMarkup($markup, $this->getExecutingPathOrNull(), $this->getRequestedContextPath())
1361*04fd306cSNickeau                ->setRequestedMimeToInstruction();
1362*04fd306cSNickeau            $instructions = $markupRenderer->getOutput();
1363*04fd306cSNickeau            if (isset($this->instructionsCache)) {
1364*04fd306cSNickeau                /**
1365*04fd306cSNickeau                 * Not a string execution, ie {@link self::isPathExecution()}
1366*04fd306cSNickeau                 * a path execution
1367*04fd306cSNickeau                 */
1368*04fd306cSNickeau                $this->instructionsCache->storeCache($instructions);
1369*04fd306cSNickeau            }
1370*04fd306cSNickeau            $this->processedInstructions = $instructions;
1371*04fd306cSNickeau            return $this;
1372*04fd306cSNickeau        } catch (\Exception $e) {
1373*04fd306cSNickeau            throw new ExceptionRuntimeInternal("An error has occurred while getting the output. Error: {$e->getMessage()}", self::CANONICAL, 1, $e);
1374*04fd306cSNickeau        } finally {
1375*04fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
1376*04fd306cSNickeau        }
1377*04fd306cSNickeau    }
1378*04fd306cSNickeau
1379*04fd306cSNickeau    public function getSnippetCachePath(): LocalPath
1380*04fd306cSNickeau    {
1381*04fd306cSNickeau        $cache = $this->getSnippetCacheStore()->cache;
1382*04fd306cSNickeau        return LocalPath::createFromPathString($cache);
1383*04fd306cSNickeau
1384*04fd306cSNickeau    }
1385*04fd306cSNickeau
1386*04fd306cSNickeau    /**
1387*04fd306cSNickeau     * @return string - an execution id to be sure that we don't execute the same twice in recursion
1388*04fd306cSNickeau     */
1389*04fd306cSNickeau    public function getId(): string
1390*04fd306cSNickeau    {
1391*04fd306cSNickeau
1392*04fd306cSNickeau        return "({$this->getSourceName()}) to ({$this->builderName} - {$this->getMime()}) with context ({$this->getRequestedContextPath()->toUriString()})";
1393*04fd306cSNickeau    }
1394*04fd306cSNickeau
1395*04fd306cSNickeau    /**
1396*04fd306cSNickeau     * @return string - a name for the source (used in {@link self::__toString()} and {@link self::getId() identification})
1397*04fd306cSNickeau     */
1398*04fd306cSNickeau    private function getSourceName(): string
1399*04fd306cSNickeau    {
1400*04fd306cSNickeau        if (!$this->isPathExecution()) {
1401*04fd306cSNickeau            if (isset($this->markupString)) {
1402*04fd306cSNickeau                $md5 = md5($this->markupString);
1403*04fd306cSNickeau                return "Markup String Execution ($md5)";
1404*04fd306cSNickeau            } elseif (isset($this->requestedInstructions)) {
1405*04fd306cSNickeau                return "Markup Instructions Execution";
1406*04fd306cSNickeau            } else {
1407*04fd306cSNickeau                LogUtility::internalError("The name of the markup handler is unknown");
1408*04fd306cSNickeau                return "Markup Unknown Execution";
1409*04fd306cSNickeau
1410*04fd306cSNickeau            }
1411*04fd306cSNickeau        } else {
1412*04fd306cSNickeau            try {
1413*04fd306cSNickeau                return $this->getSourcePath()->toUriString();
1414*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
1415*04fd306cSNickeau                throw new ExceptionRuntimeInternal("A source path should be defined if it's not a markup string execution");
1416*04fd306cSNickeau            }
1417*04fd306cSNickeau        }
1418*04fd306cSNickeau    }
1419*04fd306cSNickeau
1420*04fd306cSNickeau
1421*04fd306cSNickeau    /**
1422*04fd306cSNickeau     * TODO: move to a FetcherMarkup by object type output and mime
1423*04fd306cSNickeau     * @return array
1424*04fd306cSNickeau     */
1425*04fd306cSNickeau    private function getProcessedInstructions(): array
1426*04fd306cSNickeau    {
1427*04fd306cSNickeau
1428*04fd306cSNickeau
1429*04fd306cSNickeau        if (!$this->shouldInstructionProcess()) {
1430*04fd306cSNickeau
1431*04fd306cSNickeau            $this->processedInstructions = $this->instructionsCache->retrieveCache();
1432*04fd306cSNickeau
1433*04fd306cSNickeau        } else {
1434*04fd306cSNickeau
1435*04fd306cSNickeau            $this->processInstructions();
1436*04fd306cSNickeau
1437*04fd306cSNickeau        }
1438*04fd306cSNickeau        return $this->processedInstructions;
1439*04fd306cSNickeau
1440*04fd306cSNickeau    }
1441*04fd306cSNickeau
1442*04fd306cSNickeau    /**
1443*04fd306cSNickeau     * @throws ExceptionNotFound
1444*04fd306cSNickeau     */
1445*04fd306cSNickeau    public function getParent(): FetcherMarkup
1446*04fd306cSNickeau    {
1447*04fd306cSNickeau        if (!isset($this->parentMarkupHandler)) {
1448*04fd306cSNickeau            throw new ExceptionNotFound();
1449*04fd306cSNickeau        }
1450*04fd306cSNickeau        return $this->parentMarkupHandler;
1451*04fd306cSNickeau    }
1452*04fd306cSNickeau
1453*04fd306cSNickeau
1454*04fd306cSNickeau}
1455