xref: /template/strap/ComboStrap/FetcherMarkup.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeau
404fd306cSNickeaunamespace ComboStrap;
504fd306cSNickeau
604fd306cSNickeau
704fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore;
804fd306cSNickeauuse ComboStrap\Web\Url;
904fd306cSNickeauuse ComboStrap\Web\UrlEndpoint;
1004fd306cSNickeauuse ComboStrap\Xml\XmlDocument;
1104fd306cSNickeauuse Doku_Renderer_metadata;
1204fd306cSNickeauuse dokuwiki\Cache\CacheInstructions;
1304fd306cSNickeauuse dokuwiki\Cache\CacheParser;
1404fd306cSNickeauuse dokuwiki\Cache\CacheRenderer;
1504fd306cSNickeauuse Exception;
1604fd306cSNickeau
1704fd306cSNickeau
1804fd306cSNickeau/**
1904fd306cSNickeau * A class that renders a markup fragment
2004fd306cSNickeau * This is the context object
2104fd306cSNickeau * during parsing and rendering is determined by {@link FetcherMarkup}
2204fd306cSNickeau *
2304fd306cSNickeau * You can get it in any place via {@link ExecutionContext::getExecutingMarkupHandler()}
2404fd306cSNickeau *
2504fd306cSNickeau * It:
2604fd306cSNickeau * * does not output any full page (HTML document) but only fragment.
2704fd306cSNickeau * * manage the dependencies (snippets, cache)
2804fd306cSNickeau *
2904fd306cSNickeau * This is not really a {@link IFetcher function} because it should not be called
3004fd306cSNickeau * from the outside but to be able to use the {@link FetcherCache} we need to.
3104fd306cSNickeau * (as fetcher cache uses the url as unique identifier)
3204fd306cSNickeau *
3304fd306cSNickeau *
3404fd306cSNickeau * TODO: {@link MarkupRenderer} could be one with {@link FetcherMarkup} ?
3504fd306cSNickeau *
3604fd306cSNickeau * Not all properties are public to support
3704fd306cSNickeau * the {@link FetcherMarkupBuilder} pattern.
3804fd306cSNickeau * Php does not support internal class and protected does not
3904fd306cSNickeau * work for class on the same namespace.
4004fd306cSNickeau */
4104fd306cSNickeauclass FetcherMarkup extends IFetcherAbs implements IFetcherSource, IFetcherString
4204fd306cSNickeau{
4304fd306cSNickeau
4404fd306cSNickeau
4504fd306cSNickeau    const XHTML_MODE = "xhtml";
4604fd306cSNickeau    const MAX_CACHE_AGE = 999999;
4704fd306cSNickeau
4804fd306cSNickeau    const CANONICAL = "markup-fragment-fetcher";
4904fd306cSNickeau
5004fd306cSNickeau    /**
5104fd306cSNickeau     * When the rendering is done from:
5204fd306cSNickeau     * * a string
5304fd306cSNickeau     * * or an instructions (template)
5404fd306cSNickeau     * but not from a file
5504fd306cSNickeau     */
5604fd306cSNickeau    public const MARKUP_DYNAMIC_EXECUTION_NAME = "markup-dynamic-execution";
5704fd306cSNickeau
5804fd306cSNickeau    /**
5904fd306cSNickeau     * @var array - toc in a dokuwiki format
6004fd306cSNickeau     */
6104fd306cSNickeau    public array $toc;
6204fd306cSNickeau
6304fd306cSNickeau    /**
6404fd306cSNickeau     * @var CacheParser cache file (may be not set if this is not a {@link self::isPathExecution() execution}
6504fd306cSNickeau     */
6604fd306cSNickeau    public CacheParser $contentCache;
6704fd306cSNickeau
6804fd306cSNickeau    /**
6904fd306cSNickeau     * @var string the type of object (known as renderer in Dokuwiki)
7004fd306cSNickeau     */
7104fd306cSNickeau    public string $builderName;
7204fd306cSNickeau
7304fd306cSNickeau    public array $requestedInstructions;
7404fd306cSNickeau    public array $contextData;
7504fd306cSNickeau
7604fd306cSNickeau    public CacheInstructions $instructionsCache;
7704fd306cSNickeau
7804fd306cSNickeau    /**
7904fd306cSNickeau     * @var CacheRenderer This cache file stores the last render timestamp (see {@link p_get_metadata()}
8004fd306cSNickeau     */
8104fd306cSNickeau    public CacheRenderer $metaCache;
8204fd306cSNickeau    public LocalPath $metaPath;
8304fd306cSNickeau
8404fd306cSNickeau    /**
8504fd306cSNickeau     * @var CacheParser
8604fd306cSNickeau     */
8704fd306cSNickeau    public CacheParser $snippetCache;
8804fd306cSNickeau
8904fd306cSNickeau    /**
9004fd306cSNickeau     * @var FetcherMarkup - the parent (a instructions run may run inside a path run, ie {@link \syntax_plugin_combo_iterator)
9104fd306cSNickeau     */
9204fd306cSNickeau    public FetcherMarkup $parentMarkupHandler;
9304fd306cSNickeau
9404fd306cSNickeau    /**
9504fd306cSNickeau     * @var bool threat the markup as a document (not as a fragment)
9604fd306cSNickeau     */
9704fd306cSNickeau    public bool $isDoc;
9804fd306cSNickeau
9904fd306cSNickeau
10004fd306cSNickeau    public Mime $mime;
10104fd306cSNickeau    private bool $cacheAfterRendering = true;
10204fd306cSNickeau    public MarkupCacheDependencies $outputCacheDependencies;
10304fd306cSNickeau
10404fd306cSNickeau
10504fd306cSNickeau    /**
10604fd306cSNickeau     * @var Snippet[]
10704fd306cSNickeau     */
10804fd306cSNickeau    private array $localSnippets = [];
10904fd306cSNickeau
11004fd306cSNickeau    public bool $deleteRootBlockElement = false;
11104fd306cSNickeau
11204fd306cSNickeau    /**
11304fd306cSNickeau     * @var WikiPath the context path, it's important to resolve relative link and to create cache for each context namespace for instance
11404fd306cSNickeau     */
11504fd306cSNickeau    public WikiPath $requestedContextPath;
11604fd306cSNickeau
11704fd306cSNickeau    /**
11804fd306cSNickeau     * @var Path the source path of the markup (may be not set if we render a markup string for instance)
11904fd306cSNickeau     */
12004fd306cSNickeau    public Path $markupSourcePath;
12104fd306cSNickeau
12204fd306cSNickeau
12304fd306cSNickeau    public string $markupString;
12404fd306cSNickeau
12504fd306cSNickeau    /**
12604fd306cSNickeau     * @var bool true if this fetcher has already run
12704fd306cSNickeau     * (
12804fd306cSNickeau     * Fighting file modified time, even if we cache has been stored,
12904fd306cSNickeau     * the modified time is not always good, this indicator will
13004fd306cSNickeau     * make the processing not run twice)
13104fd306cSNickeau     */
13204fd306cSNickeau    private bool $hasExecuted = false;
13304fd306cSNickeau
13404fd306cSNickeau    /**
13504fd306cSNickeau     * The result
13604fd306cSNickeau     * @var string
13704fd306cSNickeau     */
13804fd306cSNickeau    private string $fetchString;
13904fd306cSNickeau
14004fd306cSNickeau
14104fd306cSNickeau    /**
14204fd306cSNickeau     * @var array
14304fd306cSNickeau     */
14404fd306cSNickeau    private array $meta;
14504fd306cSNickeau
14604fd306cSNickeau    /**
14704fd306cSNickeau     * @var bool - when a execution is not a {@link self::isPathExecution()}, the snippet will not be stored automatically.
14804fd306cSNickeau     * To avoid this problem, a warning is send if the calling code does not set explicitly that this is specifically a
14904fd306cSNickeau     * standalone execution
15004fd306cSNickeau     */
15104fd306cSNickeau    public bool $isNonPathStandaloneExecution = false;
15204fd306cSNickeau    /**
15304fd306cSNickeau     * @var array
15404fd306cSNickeau     */
15504fd306cSNickeau    private array $processedInstructions;
156*70bbd7f1Sgerardnico    private MarkupDynamicRender $markupDynamicRender;
15704fd306cSNickeau
15804fd306cSNickeau
15904fd306cSNickeau    /**
16004fd306cSNickeau     * @param Path $executingPath - the path where we can find the markup
16104fd306cSNickeau     * @param ?WikiPath $contextPath - the context path, the requested path in the browser url (from where relative component are resolved (ie links, ...))
16204fd306cSNickeau     * @return FetcherMarkup
16304fd306cSNickeau     * @throws ExceptionNotExists
16404fd306cSNickeau     */
16504fd306cSNickeau    public static function createXhtmlMarkupFetcherFromPath(Path $executingPath, WikiPath $contextPath = null): FetcherMarkup
16604fd306cSNickeau    {
16704fd306cSNickeau        if ($contextPath === null) {
16804fd306cSNickeau            try {
16904fd306cSNickeau                $contextPath = $executingPath->toWikiPath();
17004fd306cSNickeau            } catch (ExceptionCast $e) {
17104fd306cSNickeau                /**
17204fd306cSNickeau                 * Not a wiki path, default to the default
17304fd306cSNickeau                 */
17404fd306cSNickeau                $contextPath = ExecutionContext::getActualOrCreateFromEnv()->getDefaultContextPath();
17504fd306cSNickeau            }
17604fd306cSNickeau        }
17704fd306cSNickeau        return FetcherMarkup::confRoot()
17804fd306cSNickeau            ->setRequestedExecutingPath($executingPath)
17904fd306cSNickeau            ->setRequestedContextPath($contextPath)
18004fd306cSNickeau            ->setRequestedMimeToXhtml()
18104fd306cSNickeau            ->build();
18204fd306cSNickeau    }
18304fd306cSNickeau
18404fd306cSNickeau
18504fd306cSNickeau    public static function confRoot(): FetcherMarkupBuilder
18604fd306cSNickeau    {
18704fd306cSNickeau        return new FetcherMarkupBuilder();
18804fd306cSNickeau    }
18904fd306cSNickeau
19004fd306cSNickeau    /**
19104fd306cSNickeau     * Use mostly in test
19204fd306cSNickeau     * The coutnerpart of {@link \ComboStrap\Test\TestUtility::renderText2XhtmlWithoutP()}
19304fd306cSNickeau     * @throws ExceptionNotExists
19404fd306cSNickeau     */
19504fd306cSNickeau    public static function createStandaloneExecutionFromStringMarkupToXhtml(string $markup): FetcherMarkup
19604fd306cSNickeau    {
19704fd306cSNickeau        return self::confRoot()
19804fd306cSNickeau            ->setRequestedMarkupString($markup)
19904fd306cSNickeau            ->setDeleteRootBlockElement(true)
20004fd306cSNickeau            ->setRequestedContextPathWithDefault()
20104fd306cSNickeau            ->setRequestedMimeToXhtml()
20204fd306cSNickeau            ->setIsStandAloneCodeExecution(true)
20304fd306cSNickeau            ->build();
20404fd306cSNickeau    }
20504fd306cSNickeau
20604fd306cSNickeau    /**
20704fd306cSNickeau     * Starts a child fetcher markup
20804fd306cSNickeau     * This is needed for instructions or markup run
20904fd306cSNickeau     * Why ? Because the snippets advertised during this run, need to be stored
21004fd306cSNickeau     * and we need to know the original request path that is in the parent run.
21104fd306cSNickeau     * @return FetcherMarkupBuilder
21204fd306cSNickeau     */
21304fd306cSNickeau    public static function confChild(): FetcherMarkupBuilder
21404fd306cSNickeau    {
21504fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
21604fd306cSNickeau        try {
21704fd306cSNickeau            $executing = $executionContext->getExecutingMarkupHandler();
21804fd306cSNickeau        } catch (ExceptionNotFound $e) {
21904fd306cSNickeau            if (PluginUtility::isDevOrTest() && $executionContext->getExecutingAction() !== ExecutionContext::PREVIEW_ACTION) {
22004fd306cSNickeau                LogUtility::warning("A markup handler is not running, we couldn't create a child.");
22104fd306cSNickeau            }
222d860c427Sgerardnico            $contextPath = $executionContext->getContextPath();
223d860c427Sgerardnico            return self::confRoot()
224d860c427Sgerardnico                ->setRequestedContextPath($contextPath);
22504fd306cSNickeau        }
22604fd306cSNickeau        return self::confRoot()
22704fd306cSNickeau            ->setParentMarkupHandler($executing)
22804fd306cSNickeau            ->setRequestedContextPath($executing->getRequestedContextPath());
22904fd306cSNickeau    }
23004fd306cSNickeau
23104fd306cSNickeau
23204fd306cSNickeau    /**
23304fd306cSNickeau     * Dokuwiki will wrap the markup in a p element
23404fd306cSNickeau     * if the first element is not a block
23504fd306cSNickeau     * This option permits to delete it. This is used mostly in test to get
23604fd306cSNickeau     * the generated html
23704fd306cSNickeau     */
23804fd306cSNickeau    public function deleteRootPElementsIfRequested(array &$instructions): void
23904fd306cSNickeau    {
24004fd306cSNickeau
24104fd306cSNickeau        if (!$this->deleteRootBlockElement) {
24204fd306cSNickeau            return;
24304fd306cSNickeau        }
24404fd306cSNickeau
24504fd306cSNickeau        /**
24604fd306cSNickeau         * Delete the p added by {@link Block::process()}
24704fd306cSNickeau         * if the plugin of the {@link SyntaxPlugin::getPType() normal} and not in a block
24804fd306cSNickeau         *
24904fd306cSNickeau         * p_open = document_start in renderer
25004fd306cSNickeau         */
25104fd306cSNickeau        if ($instructions[1][0] !== 'p_open') {
25204fd306cSNickeau            return;
25304fd306cSNickeau        }
25404fd306cSNickeau        unset($instructions[1]);
25504fd306cSNickeau
25604fd306cSNickeau        /**
25704fd306cSNickeau         * The last p position is not fix
25804fd306cSNickeau         * We may have other calls due for instance
25904fd306cSNickeau         * of {@link \action_plugin_combo_syntaxanalytics}
26004fd306cSNickeau         */
26104fd306cSNickeau        $n = 1;
26204fd306cSNickeau        while (($lastPBlockPosition = (sizeof($instructions) - $n)) >= 0) {
26304fd306cSNickeau
26404fd306cSNickeau            /**
26504fd306cSNickeau             * p_open = document_end in renderer
26604fd306cSNickeau             */
26704fd306cSNickeau            if ($instructions[$lastPBlockPosition][0] == 'p_close') {
26804fd306cSNickeau                unset($instructions[$lastPBlockPosition]);
26904fd306cSNickeau                break;
27004fd306cSNickeau            } else {
27104fd306cSNickeau                $n = $n + 1;
27204fd306cSNickeau            }
27304fd306cSNickeau        }
27404fd306cSNickeau
27504fd306cSNickeau    }
27604fd306cSNickeau
27704fd306cSNickeau    /**
27804fd306cSNickeau     *
27904fd306cSNickeau     * @param Url|null $url
28004fd306cSNickeau     * @return Url
28104fd306cSNickeau     *
28204fd306cSNickeau     * Note: The fetch url is the {@link FetcherCache keyCache}
28304fd306cSNickeau     */
28404fd306cSNickeau    function getFetchUrl(Url $url = null): Url
28504fd306cSNickeau    {
28604fd306cSNickeau        /**
28704fd306cSNickeau         * Overwrite default fetcher endpoint
28804fd306cSNickeau         * that is {@link UrlEndpoint::createFetchUrl()}
28904fd306cSNickeau         */
29004fd306cSNickeau        $url = UrlEndpoint::createDokuUrl();
29104fd306cSNickeau        $url = parent::getFetchUrl($url);
29204fd306cSNickeau        try {
29304fd306cSNickeau            $wikiPath = $this->getSourcePath()->toWikiPath();
29404fd306cSNickeau            $url->addQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $wikiPath->getWikiId());
29504fd306cSNickeau            $url->addQueryParameter(WikiPath::DRIVE_ATTRIBUTE, $wikiPath->getDrive());
29604fd306cSNickeau        } catch (ExceptionCast|ExceptionNotFound $e) {
29704fd306cSNickeau            // not an accessible source path
29804fd306cSNickeau        }
29904fd306cSNickeau        $url->addQueryParameter("context-id", $this->getRequestedContextPath()->getWikiId());
30004fd306cSNickeau        return $url;
30104fd306cSNickeau
30204fd306cSNickeau    }
30304fd306cSNickeau
30404fd306cSNickeau
30504fd306cSNickeau    /**
30604fd306cSNickeau     * @return Mime
30704fd306cSNickeau     */
30804fd306cSNickeau    public function getMime(): Mime
30904fd306cSNickeau    {
31004fd306cSNickeau        if (isset($this->mime)) {
31104fd306cSNickeau            return $this->mime;
31204fd306cSNickeau        }
31304fd306cSNickeau
31404fd306cSNickeau        // XHTML default
31504fd306cSNickeau        try {
31604fd306cSNickeau            return Mime::createFromExtension(self::XHTML_MODE);
31704fd306cSNickeau        } catch (ExceptionNotFound $e) {
31804fd306cSNickeau            // should not happen
31904fd306cSNickeau            throw new ExceptionRuntime("Internal error: The XHTML mime was not found.", self::CANONICAL, 1, $e);
32004fd306cSNickeau        }
32104fd306cSNickeau    }
32204fd306cSNickeau
32304fd306cSNickeau    /**
32404fd306cSNickeau     * TODO: split We should split fetcherMarkup by object type output and {@link Mime}
32504fd306cSNickeau     * @return bool
32604fd306cSNickeau     */
32704fd306cSNickeau    private function shouldInstructionProcess(): bool
32804fd306cSNickeau    {
32904fd306cSNickeau
33004fd306cSNickeau        if (!$this->isPathExecution()) {
33104fd306cSNickeau            return true;
33204fd306cSNickeau        }
33304fd306cSNickeau
33404fd306cSNickeau        if (isset($this->processedInstructions)) {
33504fd306cSNickeau            return false;
33604fd306cSNickeau        }
33704fd306cSNickeau
33804fd306cSNickeau        /**
33904fd306cSNickeau         * Edge Case
34004fd306cSNickeau         * (as dokuwiki starts the rendering process here
34104fd306cSNickeau         * we need to set the execution id)
34204fd306cSNickeau         */
34304fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()->setExecutingMarkupHandler($this);
34404fd306cSNickeau        try {
34504fd306cSNickeau            $useCache = $this->instructionsCache->useCache();
34604fd306cSNickeau        } finally {
34704fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
34804fd306cSNickeau        }
34904fd306cSNickeau        return ($useCache === false);
35004fd306cSNickeau    }
35104fd306cSNickeau
35204fd306cSNickeau    public function shouldProcess(): bool
35304fd306cSNickeau    {
35404fd306cSNickeau
35504fd306cSNickeau        if (!$this->isPathExecution()) {
35604fd306cSNickeau            return true;
35704fd306cSNickeau        }
35804fd306cSNickeau
35904fd306cSNickeau        if ($this->hasExecuted) {
36004fd306cSNickeau            return false;
36104fd306cSNickeau        }
36204fd306cSNickeau
36304fd306cSNickeau        /**
36404fd306cSNickeau         * The cache is stored by requested page scope
36504fd306cSNickeau         *
36604fd306cSNickeau         * We set the environment because
36704fd306cSNickeau         * {@link CacheParser::useCache()} may call a parsing of the markup fragment
36804fd306cSNickeau         * And the global environment are not always passed
36904fd306cSNickeau         * in all actions and is needed to log the {@link  CacheResult cache
37004fd306cSNickeau         * result}
37104fd306cSNickeau         *
37204fd306cSNickeau         * Use cache should be always called because it trigger
37304fd306cSNickeau         * the event coupled to the cache (ie PARSER_CACHE_USE)
37404fd306cSNickeau         */
37504fd306cSNickeau        $depends['age'] = $this->getCacheAge();
37604fd306cSNickeau        if ($this->isFragment()) {
37704fd306cSNickeau            /**
37804fd306cSNickeau             * Fragment may use variables of the requested page
37904fd306cSNickeau             * We have dependency on {@link MarkupCacheDependencies::PAGE_PRIMARY_META_DEPENDENCY}
38004fd306cSNickeau             * but as they may be derived such as the {@link PageTitle}
38104fd306cSNickeau             * comes from the H1 or the feature image comes from the first image in the section 1
38204fd306cSNickeau             * We can't really use this event.
38304fd306cSNickeau             */
38404fd306cSNickeau            try {
38504fd306cSNickeau                $depends['files'][] = FetcherMarkup::confRoot()
38604fd306cSNickeau                    ->setRequestedContextPath($this->getRequestedContextPath())
38704fd306cSNickeau                    ->setRequestedExecutingPath($this->getRequestedContextPath())
38804fd306cSNickeau                    ->setRequestedMimeToMetadata()
38904fd306cSNickeau                    ->build()
39004fd306cSNickeau                    ->getMetadataPath()
39104fd306cSNickeau                    ->toAbsoluteId();
39204fd306cSNickeau            } catch (ExceptionNotExists|ExceptionNotFound $e) {
39304fd306cSNickeau                /**
39404fd306cSNickeau                 * Computer are hard
39504fd306cSNickeau                 * At the beginning there is no markup path
39604fd306cSNickeau                 * We may get this error then
39704fd306cSNickeau                 *
39804fd306cSNickeau                 * We don't allow on test
39904fd306cSNickeau                 */
40004fd306cSNickeau                if (PluginUtility::isTest()) {
40104fd306cSNickeau                    /**
40204fd306cSNickeau                     * The first edit, the page does not exists
40304fd306cSNickeau                     */
40404fd306cSNickeau                    $executingAction = ExecutionContext::getActualOrCreateFromEnv()->getExecutingAction();
40504fd306cSNickeau                    if (!in_array($executingAction, [ExecutionContext::EDIT_ACTION, ExecutionContext::PREVIEW_ACTION])) {
40604fd306cSNickeau                        LogUtility::error("The metadata path should be known. " . $e->getMessage(), self::CANONICAL, $e);
40704fd306cSNickeau                    }
40804fd306cSNickeau                }
40904fd306cSNickeau            }
41004fd306cSNickeau        }
41104fd306cSNickeau        /**
41204fd306cSNickeau         * Edge Case
41304fd306cSNickeau         * (as dokuwiki starts the rendering process here
41404fd306cSNickeau         * we need to set the execution id)
41504fd306cSNickeau         */
41604fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()
41704fd306cSNickeau            ->setExecutingMarkupHandler($this);
41804fd306cSNickeau        try {
41904fd306cSNickeau            $useCache = $this->contentCache->useCache($depends);
42004fd306cSNickeau        } finally {
42104fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
42204fd306cSNickeau        }
42304fd306cSNickeau        return ($useCache === false);
42404fd306cSNickeau
42504fd306cSNickeau    }
42604fd306cSNickeau
42704fd306cSNickeau
42804fd306cSNickeau    public
42904fd306cSNickeau    function storeSnippets()
43004fd306cSNickeau    {
43104fd306cSNickeau
43204fd306cSNickeau        /**
43304fd306cSNickeau         * Snippet
43404fd306cSNickeau         */
43504fd306cSNickeau        $snippets = $this->getSnippets();
43604fd306cSNickeau        $jsonDecodeSnippets = SnippetSystem::toJsonArrayFromSlotSnippets($snippets);
43704fd306cSNickeau
43804fd306cSNickeau        /**
43904fd306cSNickeau         * Cache file
44004fd306cSNickeau         * Using a cache parser, set the page id and will trigger
44104fd306cSNickeau         * the parser cache use event in order to log/report the cache usage
44204fd306cSNickeau         * At {@link action_plugin_combo_cache::createCacheReport()}
44304fd306cSNickeau         */
44404fd306cSNickeau        $snippetCache = $this->getSnippetCacheStore();
44504fd306cSNickeau        $this->outputCacheDependencies->rerouteCacheDestination($snippetCache);
44604fd306cSNickeau
44704fd306cSNickeau        if (count($jsonDecodeSnippets) > 0) {
44804fd306cSNickeau            $data1 = json_encode($jsonDecodeSnippets);
44904fd306cSNickeau            $snippetCache->storeCache($data1);
45004fd306cSNickeau        } else {
45104fd306cSNickeau            $snippetCache->removeCache();
45204fd306cSNickeau        }
45304fd306cSNickeau
45404fd306cSNickeau    }
45504fd306cSNickeau
45604fd306cSNickeau    /**
45704fd306cSNickeau     * This functon loads the snippets in the global array
45804fd306cSNickeau     * by creating them. Not ideal but works for now.
45904fd306cSNickeau     * @return Snippet[]
46004fd306cSNickeau     */
46104fd306cSNickeau    public
46204fd306cSNickeau    function loadSnippets(): array
46304fd306cSNickeau    {
46404fd306cSNickeau
46504fd306cSNickeau        $snippetCacheStore = $this->getSnippetCacheStore();
46604fd306cSNickeau        $data = $snippetCacheStore->retrieveCache();
46704fd306cSNickeau        $nativeSnippets = [];
46804fd306cSNickeau        if (!empty($data)) {
46904fd306cSNickeau            $jsonDecodeSnippets = json_decode($data, true);
47004fd306cSNickeau            foreach ($jsonDecodeSnippets as $snippet) {
47104fd306cSNickeau                try {
47204fd306cSNickeau                    $nativeSnippets[] = Snippet::createFromJson($snippet);
47304fd306cSNickeau                } catch (ExceptionCompile $e) {
47404fd306cSNickeau                    LogUtility::error("The snippet json array cannot be build into a snippet object. " . $e->getMessage() . "\n" . ArrayUtility::formatAsString($snippet), LogUtility::SUPPORT_CANONICAL,);
47504fd306cSNickeau                }
47604fd306cSNickeau            }
47704fd306cSNickeau        }
47804fd306cSNickeau        return $nativeSnippets;
47904fd306cSNickeau
48004fd306cSNickeau    }
48104fd306cSNickeau
48204fd306cSNickeau    private
48304fd306cSNickeau    function removeSnippets()
48404fd306cSNickeau    {
48504fd306cSNickeau        $snippetCacheFile = $this->getSnippetCacheStore()->cache;
48604fd306cSNickeau        if ($snippetCacheFile !== null) {
48704fd306cSNickeau            if (file_exists($snippetCacheFile)) {
48804fd306cSNickeau                unlink($snippetCacheFile);
48904fd306cSNickeau            }
49004fd306cSNickeau        }
49104fd306cSNickeau    }
49204fd306cSNickeau
49304fd306cSNickeau    /**
49404fd306cSNickeau     * @return CacheParser - the cache where the snippets are stored
49504fd306cSNickeau     * Cache file
49604fd306cSNickeau     * Using a cache parser, set the page id and will trigger
49704fd306cSNickeau     * the parser cache use event in order to log/report the cache usage
49804fd306cSNickeau     * At {@link action_plugin_combo_cache::createCacheReport()}
49904fd306cSNickeau     */
50004fd306cSNickeau    public
50104fd306cSNickeau    function getSnippetCacheStore(): CacheParser
50204fd306cSNickeau    {
50304fd306cSNickeau        if (isset($this->snippetCache)) {
50404fd306cSNickeau            return $this->snippetCache;
50504fd306cSNickeau        }
50604fd306cSNickeau        if ($this->isPathExecution()) {
50704fd306cSNickeau            throw new ExceptionRuntimeInternal("A source path should be available as this is a path execution");
50804fd306cSNickeau        }
50904fd306cSNickeau        throw new ExceptionRuntime("There is no snippet cache store for a non-path execution");
51004fd306cSNickeau
51104fd306cSNickeau    }
51204fd306cSNickeau
51304fd306cSNickeau
51404fd306cSNickeau    public
51504fd306cSNickeau    function getDependenciesCacheStore(): CacheParser
51604fd306cSNickeau    {
51704fd306cSNickeau        return $this->outputCacheDependencies->getDependenciesCacheStore();
51804fd306cSNickeau    }
51904fd306cSNickeau
52004fd306cSNickeau    public
52104fd306cSNickeau    function getDependenciesCachePath(): LocalPath
52204fd306cSNickeau    {
52304fd306cSNickeau        $cachePath = $this->outputCacheDependencies->getDependenciesCacheStore()->cache;
52404fd306cSNickeau        return LocalPath::createFromPathString($cachePath);
52504fd306cSNickeau    }
52604fd306cSNickeau
52704fd306cSNickeau    /**
52804fd306cSNickeau     * @return LocalPath the fetch path - start the process and returns a path. If the cache is on, return the {@link FetcherMarkup::getContentCachePath()}
52904fd306cSNickeau     * @throws ExceptionCompile
53004fd306cSNickeau     */
53104fd306cSNickeau    function processIfNeededAndGetFetchPath(): LocalPath
53204fd306cSNickeau    {
53304fd306cSNickeau        $this->processIfNeeded();
53404fd306cSNickeau
53504fd306cSNickeau        /**
53604fd306cSNickeau         * The cache path may have change due to the cache key rerouting
53704fd306cSNickeau         * We should there always use the {@link FetcherMarkup::getContentCachePath()}
53804fd306cSNickeau         * as fetch path
53904fd306cSNickeau         */
54004fd306cSNickeau        return $this->getContentCachePath();
54104fd306cSNickeau
54204fd306cSNickeau    }
54304fd306cSNickeau
54404fd306cSNickeau
54504fd306cSNickeau    /**
54604fd306cSNickeau     * @return $this
54704fd306cSNickeau     * @throws ExceptionCompile
54804fd306cSNickeau     */
54904fd306cSNickeau    public function process(): FetcherMarkup
55004fd306cSNickeau    {
55104fd306cSNickeau
55204fd306cSNickeau        $this->hasExecuted = true;
55304fd306cSNickeau
55404fd306cSNickeau        /**
55504fd306cSNickeau         * Rendering
55604fd306cSNickeau         */
55704fd306cSNickeau        $executionContext = (ExecutionContext::getActualOrCreateFromEnv());
55804fd306cSNickeau
55904fd306cSNickeau        $extension = $this->getMime()->getExtension();
56004fd306cSNickeau        switch ($extension) {
56104fd306cSNickeau
56204fd306cSNickeau            case MarkupRenderer::METADATA_EXTENSION:
56304fd306cSNickeau                /**
56404fd306cSNickeau                 * The user may ask just for the metadata
56504fd306cSNickeau                 * and should then use the {@link self::getMetadata()}
56604fd306cSNickeau                 * function instead
56704fd306cSNickeau                 */
56804fd306cSNickeau                break;
56904fd306cSNickeau            case MarkupRenderer::INSTRUCTION_EXTENSION:
57004fd306cSNickeau                /**
57104fd306cSNickeau                 * The user may ask just for the instuctions
57204fd306cSNickeau                 * and should then use the {@link self::getInstructions()}
57304fd306cSNickeau                 * function to get the instructions
57404fd306cSNickeau                 */
57504fd306cSNickeau                return $this;
57604fd306cSNickeau            default:
57704fd306cSNickeau
57804fd306cSNickeau                $instructions = $this->getInstructions();
57904fd306cSNickeau
58004fd306cSNickeau                /**
58104fd306cSNickeau                 * Edge case: We delete here
58204fd306cSNickeau                 * because the instructions may have been created by dokuwiki
58304fd306cSNickeau                 * when we test for the cache with {@link CacheParser::useCache()}
58404fd306cSNickeau                 */
58504fd306cSNickeau                if ($this->deleteRootBlockElement) {
58604fd306cSNickeau                    self::deleteRootPElementsIfRequested($instructions);
58704fd306cSNickeau                }
58804fd306cSNickeau
58904fd306cSNickeau                if (!isset($this->builderName)) {
59004fd306cSNickeau                    $this->builderName = $this->getMime()->getExtension();
59104fd306cSNickeau                }
59204fd306cSNickeau
59304fd306cSNickeau                $executionContext->setExecutingMarkupHandler($this);
59404fd306cSNickeau                try {
59504fd306cSNickeau                    if ($this->isDocument()) {
59604fd306cSNickeau                        $markupRenderer = MarkupRenderer::createFromMarkupInstructions($instructions, $this)
59704fd306cSNickeau                            ->setRequestedMime($this->getMime())
59804fd306cSNickeau                            ->setRendererName($this->builderName);
59904fd306cSNickeau
60004fd306cSNickeau                        $output = $markupRenderer->getOutput();
60104fd306cSNickeau                        if ($output === null && !empty($instructions)) {
60204fd306cSNickeau                            LogUtility::error("The renderer ({$this->builderName}) seems to have been not found");
60304fd306cSNickeau                        }
60404fd306cSNickeau                        $this->cacheAfterRendering = $markupRenderer->getCacheAfterRendering();
60504fd306cSNickeau                    } else {
606*70bbd7f1Sgerardnico                        if (!isset($this->markupDynamicRender)) {
607*70bbd7f1Sgerardnico                            $this->markupDynamicRender = MarkupDynamicRender::create($this->builderName);
608*70bbd7f1Sgerardnico                        }
609*70bbd7f1Sgerardnico                        $output = $this->markupDynamicRender->processInstructions($instructions);
61004fd306cSNickeau                    }
61104fd306cSNickeau                } catch (\Exception $e) {
61204fd306cSNickeau                    /**
61304fd306cSNickeau                     * Example of errors;
61404fd306cSNickeau                     * method_exists() expects parameter 2 to be string, array given
61504fd306cSNickeau                     * inc\parserutils.php:672
61604fd306cSNickeau                     */
61704fd306cSNickeau                    throw new ExceptionCompile("An error has occurred while getting the output. Error: {$e->getMessage()}", self::CANONICAL, 1, $e);
61804fd306cSNickeau
61904fd306cSNickeau                } finally {
62004fd306cSNickeau                    $executionContext->closeExecutingMarkupHandler();
62104fd306cSNickeau                }
62204fd306cSNickeau                if (is_array($output)) {
62304fd306cSNickeau                    LogUtility::internalError("The output was an array", self::CANONICAL);
62404fd306cSNickeau                    $this->fetchString = serialize($output);
62504fd306cSNickeau                } else {
62604fd306cSNickeau                    $this->fetchString = $output;
62704fd306cSNickeau                }
62804fd306cSNickeau
62904fd306cSNickeau                break;
63004fd306cSNickeau        }
63104fd306cSNickeau
63204fd306cSNickeau        /**
63304fd306cSNickeau         * Storage of snippets or dependencies
63404fd306cSNickeau         * none if this is not a path execution
63504fd306cSNickeau         * and for now, metadata storage is done by dokuwiki
63604fd306cSNickeau         */
63704fd306cSNickeau        if (!$this->isPathExecution() || $this->mime->getExtension() === MarkupRenderer::METADATA_EXTENSION) {
63804fd306cSNickeau            return $this;
63904fd306cSNickeau        }
64004fd306cSNickeau
64104fd306cSNickeau
64204fd306cSNickeau        /**
64304fd306cSNickeau         * Snippets and cache dependencies are only for HTML rendering
64404fd306cSNickeau         * Otherwise, otherwise other type rendering may override them
64504fd306cSNickeau         * (such as analtyical json, ...)
64604fd306cSNickeau         */
64704fd306cSNickeau        if (in_array($this->getMime()->toString(), [Mime::XHTML, Mime::HTML])) {
64804fd306cSNickeau
64904fd306cSNickeau            /**
65004fd306cSNickeau             * We make the Snippet store to Html store an atomic operation
65104fd306cSNickeau             *
65204fd306cSNickeau             * Why ? Because if the rendering of the page is stopped,
65304fd306cSNickeau             * the cache of the HTML page may be stored but not the cache of the snippets
65404fd306cSNickeau             * leading to a bad page because the next rendering will see then no snippets.
65504fd306cSNickeau             */
65604fd306cSNickeau            try {
65704fd306cSNickeau                $this->storeSnippets();
65804fd306cSNickeau            } catch (Exception $e) {
65904fd306cSNickeau                // if any write os exception
66004fd306cSNickeau                LogUtility::msg("Error while storing the xhtml content: {$e->getMessage()}");
66104fd306cSNickeau                $this->removeSnippets();
66204fd306cSNickeau            }
66304fd306cSNickeau
66404fd306cSNickeau            /**
66504fd306cSNickeau             * Cache output dependencies
66604fd306cSNickeau             * Reroute the cache output by runtime dependencies
66704fd306cSNickeau             * set during processing
66804fd306cSNickeau             */
66904fd306cSNickeau            $this->outputCacheDependencies->storeDependencies();
67004fd306cSNickeau            $this->outputCacheDependencies->rerouteCacheDestination($this->contentCache);
67104fd306cSNickeau
67204fd306cSNickeau        }
67304fd306cSNickeau
67404fd306cSNickeau        /**
67504fd306cSNickeau         * We store always the output in the cache
67604fd306cSNickeau         * if the cache is not on, the file is just overwritten
67704fd306cSNickeau         *
67804fd306cSNickeau         * We don't use
67904fd306cSNickeau         * {{@link CacheParser::storeCache()}
68004fd306cSNickeau         * because it uses the protected parameter `__nocache`
68104fd306cSNickeau         * that will disallow the storage
68204fd306cSNickeau         */
68304fd306cSNickeau        io_saveFile($this->contentCache->cache, $this->fetchString);
68404fd306cSNickeau
68504fd306cSNickeau        return $this;
68604fd306cSNickeau    }
68704fd306cSNickeau
68804fd306cSNickeau
68904fd306cSNickeau    function getBuster(): string
69004fd306cSNickeau    {
69104fd306cSNickeau        // no buster
69204fd306cSNickeau        return "";
69304fd306cSNickeau    }
69404fd306cSNickeau
69504fd306cSNickeau    public
69604fd306cSNickeau    function getFetcherName(): string
69704fd306cSNickeau    {
69804fd306cSNickeau        return "markup-fetcher";
69904fd306cSNickeau    }
70004fd306cSNickeau
70104fd306cSNickeau
70204fd306cSNickeau    private function getCacheAge(): int
70304fd306cSNickeau    {
70404fd306cSNickeau
70504fd306cSNickeau        $extension = $this->getMime()->getExtension();
70604fd306cSNickeau        switch ($extension) {
70704fd306cSNickeau            case self::XHTML_MODE:
70804fd306cSNickeau                if (!Site::isHtmlRenderCacheOn()) {
70904fd306cSNickeau                    return 0;
71004fd306cSNickeau                }
71104fd306cSNickeau                break;
71204fd306cSNickeau            case MarkupRenderer::INSTRUCTION_EXTENSION:
71304fd306cSNickeau                // indefinitely
71404fd306cSNickeau                return self::MAX_CACHE_AGE;
71504fd306cSNickeau        }
71604fd306cSNickeau        try {
71704fd306cSNickeau            $requestedCache = $this->getRequestedCache();
71804fd306cSNickeau        } catch (ExceptionNotFound $e) {
71904fd306cSNickeau            $requestedCache = IFetcherAbs::RECACHE_VALUE;
72004fd306cSNickeau        }
72104fd306cSNickeau        $cacheAge = $this->getCacheMaxAgeInSec($requestedCache);
72204fd306cSNickeau        return $this->cacheAfterRendering ? $cacheAge : 0;
72304fd306cSNickeau
72404fd306cSNickeau    }
72504fd306cSNickeau
72604fd306cSNickeau
72704fd306cSNickeau    public function __toString()
72804fd306cSNickeau    {
72904fd306cSNickeau
73004fd306cSNickeau        return parent::__toString() . " ({$this->getSourceName()}, {$this->getMime()->toString()})";
73104fd306cSNickeau    }
73204fd306cSNickeau
73304fd306cSNickeau
73404fd306cSNickeau    /**
73504fd306cSNickeau     * @throws ExceptionBadArgument
73604fd306cSNickeau     */
73704fd306cSNickeau    public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherMarkup
73804fd306cSNickeau    {
73904fd306cSNickeau        parent::buildFromTagAttributes($tagAttributes);
74004fd306cSNickeau        return $this;
74104fd306cSNickeau    }
74204fd306cSNickeau
74304fd306cSNickeau
74404fd306cSNickeau    /**
74504fd306cSNickeau     * @return LocalPath - the cache path is where the result is stored if the cache is on
74604fd306cSNickeau     * The cache path may have change due to the cache key rerouting
74704fd306cSNickeau     * We should there always use the {@link FetcherMarkup::getContentCachePath()}
74804fd306cSNickeau     * as fetch path
74904fd306cSNickeau     */
75004fd306cSNickeau    public function getContentCachePath(): LocalPath
75104fd306cSNickeau    {
75204fd306cSNickeau        $path = $this->contentCache->cache;
75304fd306cSNickeau        return LocalPath::createFromPathString($path);
75404fd306cSNickeau    }
75504fd306cSNickeau
75604fd306cSNickeau
75704fd306cSNickeau    public function getOutputCacheDependencies(): MarkupCacheDependencies
75804fd306cSNickeau    {
75904fd306cSNickeau        return $this->outputCacheDependencies;
76004fd306cSNickeau    }
76104fd306cSNickeau
76204fd306cSNickeau
76304fd306cSNickeau    /**
76404fd306cSNickeau     * @return string - with replacement if any
76504fd306cSNickeau     * TODO: edit button replacement could be a script tag with a json, permits to do DOM manipulation
76604fd306cSNickeau     * @throws ExceptionCompile - if any processing error occurs
76704fd306cSNickeau     */
76804fd306cSNickeau    public function getFetchString(): string
76904fd306cSNickeau    {
77004fd306cSNickeau        $this->processIfNeeded();
77104fd306cSNickeau
77204fd306cSNickeau        if (!$this->isPathExecution()) {
77304fd306cSNickeau            return $this->fetchString;
77404fd306cSNickeau        }
77504fd306cSNickeau
77604fd306cSNickeau        /**
77704fd306cSNickeau         * Source path execution
77804fd306cSNickeau         * The cache path may have change due to the cache key rerouting
77904fd306cSNickeau         * We should there always use the {@link FetcherMarkup::getContentCachePath()}
78004fd306cSNickeau         * as fetch path
78104fd306cSNickeau         */
78204fd306cSNickeau        $path = $this->getContentCachePath();
78304fd306cSNickeau        try {
78404fd306cSNickeau            $text = FileSystems::getContent($path);
78504fd306cSNickeau        } catch (ExceptionNotFound $e) {
78604fd306cSNickeau            throw new ExceptionRuntime("Internal error: The fetch path should exists.", self::CANONICAL, 1, $e);
78704fd306cSNickeau        }
78804fd306cSNickeau
78904fd306cSNickeau        /**
79004fd306cSNickeau         * Edit button Processing for XHtml
79104fd306cSNickeau         * (Path is mandatory to create the buttons)
79204fd306cSNickeau         */
79304fd306cSNickeau        if (!in_array($this->getMime()->getExtension(), ["html", "xhtml"])) {
79404fd306cSNickeau            return $text;
79504fd306cSNickeau        }
79604fd306cSNickeau        try {
79704fd306cSNickeau            if ($this->getSourcePath()->toWikiPath()->getDrive() !== WikiPath::MARKUP_DRIVE) {
79804fd306cSNickeau                // case when this is a default page in the resource/template directory
79904fd306cSNickeau                return EditButton::deleteAll($text);
80004fd306cSNickeau            }
80104fd306cSNickeau        } catch (ExceptionNotFound|ExceptionCast $e) {
80204fd306cSNickeau            // not a wiki path
80304fd306cSNickeau        }
80404fd306cSNickeau        return EditButton::replaceOrDeleteAll($text);
80504fd306cSNickeau
80604fd306cSNickeau    }
80704fd306cSNickeau
80804fd306cSNickeau
80904fd306cSNickeau    public function getLabel(): string
81004fd306cSNickeau    {
81104fd306cSNickeau        try {
81204fd306cSNickeau            $sourcePath = $this->getSourcePath();
81304fd306cSNickeau        } catch (ExceptionNotFound $e) {
81404fd306cSNickeau            return self::MARKUP_DYNAMIC_EXECUTION_NAME;
81504fd306cSNickeau        }
81604fd306cSNickeau        return ResourceName::getFromPath($sourcePath);
81704fd306cSNickeau    }
81804fd306cSNickeau
81904fd306cSNickeau
82004fd306cSNickeau    public function getRequestedContextPath(): WikiPath
82104fd306cSNickeau    {
82204fd306cSNickeau        return $this->requestedContextPath;
82304fd306cSNickeau    }
82404fd306cSNickeau
82504fd306cSNickeau
82604fd306cSNickeau    /**
82704fd306cSNickeau     * @throws ExceptionNotFound
82804fd306cSNickeau     */
82904fd306cSNickeau    public function getSourcePath(): Path
83004fd306cSNickeau    {
83104fd306cSNickeau        if (isset($this->markupSourcePath)) {
83204fd306cSNickeau            return $this->markupSourcePath;
83304fd306cSNickeau        }
83404fd306cSNickeau        throw new ExceptionNotFound("No source path for this markup");
83504fd306cSNickeau    }
83604fd306cSNickeau
83704fd306cSNickeau    /**
83804fd306cSNickeau     * Utility class that return the source path
83904fd306cSNickeau     * @return Path
84004fd306cSNickeau     * @throws ExceptionNotFound
84104fd306cSNickeau     */
84204fd306cSNickeau    public function getRequestedExecutingPath(): Path
84304fd306cSNickeau    {
84404fd306cSNickeau        return $this->getSourcePath();
84504fd306cSNickeau    }
84604fd306cSNickeau
84704fd306cSNickeau    /**
84804fd306cSNickeau     * @return Path|null - utility class to get the source markup path or null (if this is a markup snippet/string rendering)
84904fd306cSNickeau     */
85004fd306cSNickeau    public function getExecutingPathOrNull(): ?Path
85104fd306cSNickeau    {
85204fd306cSNickeau        try {
85304fd306cSNickeau            return $this->getSourcePath();
85404fd306cSNickeau        } catch (ExceptionNotFound $e) {
85504fd306cSNickeau            return null;
85604fd306cSNickeau        }
85704fd306cSNickeau    }
85804fd306cSNickeau
85904fd306cSNickeau
86004fd306cSNickeau    /**
86104fd306cSNickeau     * @param string $componentId
86204fd306cSNickeau     * @return Snippet[]
86304fd306cSNickeau     */
86404fd306cSNickeau    public function getSnippetsForComponent(string $componentId): array
86504fd306cSNickeau    {
86604fd306cSNickeau
86704fd306cSNickeau        $snippets = $this->getSnippets();
86804fd306cSNickeau        $snippetsForComponent = [];
86904fd306cSNickeau        foreach ($snippets as $snippet) {
87004fd306cSNickeau            try {
87104fd306cSNickeau                if ($snippet->getComponentId() === $componentId) {
87204fd306cSNickeau                    $snippetsForComponent[] = $snippet;
87304fd306cSNickeau                }
87404fd306cSNickeau            } catch (ExceptionNotFound $e) {
87504fd306cSNickeau                //
87604fd306cSNickeau            }
87704fd306cSNickeau        }
87804fd306cSNickeau        return $snippetsForComponent;
87904fd306cSNickeau
88004fd306cSNickeau    }
88104fd306cSNickeau
88204fd306cSNickeau    /**
88304fd306cSNickeau     * @return Snippet[]
88404fd306cSNickeau     */
88504fd306cSNickeau    public function getSnippets(): array
88604fd306cSNickeau    {
88704fd306cSNickeau
88804fd306cSNickeau        $snippets = $this->localSnippets;
88904fd306cSNickeau
89004fd306cSNickeau        /**
89104fd306cSNickeau         * Old ways where snippets were added to the global scope
89204fd306cSNickeau         * and not to the fetcher markup via {@link self::addSnippet()}
89304fd306cSNickeau         *
89404fd306cSNickeau         * During the transition, we support the two
89504fd306cSNickeau         *
89604fd306cSNickeau         * Note that with the new system where render code
89704fd306cSNickeau         * can access this object via {@link ExecutionContext::getExecutingMarkupHandler()}
89804fd306cSNickeau         * the code may had snippets without any id
89904fd306cSNickeau         * (For the time being, not yet)
90004fd306cSNickeau         */
90104fd306cSNickeau        try {
90204fd306cSNickeau            $slotId = $this->getSourcePath()->toWikiPath()->getWikiId();
90304fd306cSNickeau        } catch (ExceptionNotFound $e) {
90404fd306cSNickeau            // a markup string run
90504fd306cSNickeau            return $snippets;
90604fd306cSNickeau        } catch (ExceptionCast $e) {
90704fd306cSNickeau            // not a wiki path
90804fd306cSNickeau            return $snippets;
90904fd306cSNickeau        }
91004fd306cSNickeau
91104fd306cSNickeau        $snippetManager = PluginUtility::getSnippetManager();
91204fd306cSNickeau        $oldWaySnippets = $snippetManager->getSnippetsForSlot($slotId);
91304fd306cSNickeau        return array_merge($oldWaySnippets, $snippets);
91404fd306cSNickeau
91504fd306cSNickeau    }
91604fd306cSNickeau
91704fd306cSNickeau    /**
91804fd306cSNickeau     * @param Snippet $snippet
91904fd306cSNickeau     * @return FetcherMarkup
92004fd306cSNickeau     */
92104fd306cSNickeau    public function addSnippet(Snippet $snippet): FetcherMarkup
92204fd306cSNickeau    {
92304fd306cSNickeau        /**
92404fd306cSNickeau         * Snippet should be added only when they can be store
92504fd306cSNickeau         * (ie when this is path execution)
92604fd306cSNickeau         * If this is not a path execution, the snippet cannot be
92704fd306cSNickeau         * stored in a cache and are therefore lost if not used
92804fd306cSNickeau         */
92904fd306cSNickeau
93004fd306cSNickeau        /**
93104fd306cSNickeau         * If there is a parent markup handler
93204fd306cSNickeau         * Store the snippets there
93304fd306cSNickeau         */
93404fd306cSNickeau        if (isset($this->parentMarkupHandler)) {
93504fd306cSNickeau            $this->parentMarkupHandler->addSnippet($snippet);
93604fd306cSNickeau            return $this;
93704fd306cSNickeau        }
93804fd306cSNickeau
93904fd306cSNickeau        if (!$this->isPathExecution()
94004fd306cSNickeau            && !$this->isNonPathStandaloneExecution
94104fd306cSNickeau            // In preview, there is no parent handler because we didn't take over
94204fd306cSNickeau            && ExecutionContext::getActualOrCreateFromEnv()->getExecutingAction() !== ExecutionContext::PREVIEW_ACTION
94304fd306cSNickeau        ) {
94404fd306cSNickeau            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.");
94504fd306cSNickeau        }
94604fd306cSNickeau        if (!in_array($this->getMime()->toString(), [Mime::XHTML, Mime::HTML])) {
94704fd306cSNickeau            LogUtility::warning("The execution ($this) is not a HTML execution. The snippet $snippet will not be preserved because they are reserved for XHMTL execution");
94804fd306cSNickeau        }
94904fd306cSNickeau
95004fd306cSNickeau        $snippetGuid = $snippet->getPath()->toUriString();
95104fd306cSNickeau        $this->localSnippets[$snippetGuid] = $snippet;
95204fd306cSNickeau        return $this;
95304fd306cSNickeau
95404fd306cSNickeau
95504fd306cSNickeau    }
95604fd306cSNickeau
95704fd306cSNickeau    /**
95804fd306cSNickeau     * @return bool true if the markup string comes from a path
95904fd306cSNickeau     * This is motsly important for cache as we use the path as the cache key
96004fd306cSNickeau     * (Cache:
96104fd306cSNickeau     * * of the {@link self::getInstructions() instructions},
96204fd306cSNickeau     * * of the {@link self::getOutputCacheDependencies() output dependencies}
96304fd306cSNickeau     * * of the {@link self::getSnippets() snippets}
96404fd306cSNickeau     * * of the {@link self::processMetadataIfNotYetDone() metadata}
96504fd306cSNickeau     *
96604fd306cSNickeau     * The rule is this is a path execution of the {@link self::$markupSourcePath executing source path} is set.
96704fd306cSNickeau     *
96804fd306cSNickeau     * Ie this is not a path execution, if the input is:
96904fd306cSNickeau     * * {@link self::$requestedInstructions} (used for templating)
97004fd306cSNickeau     * * a {@link self::$markupString} (used for test or webcode)
97104fd306cSNickeau     *
97204fd306cSNickeau     */
97304fd306cSNickeau    public function isPathExecution(): bool
97404fd306cSNickeau    {
97504fd306cSNickeau        if (isset($this->markupSourcePath)) {
97604fd306cSNickeau            return true;
97704fd306cSNickeau        }
97804fd306cSNickeau        return false;
97904fd306cSNickeau    }
98004fd306cSNickeau
98104fd306cSNickeau    /**
98204fd306cSNickeau     * @throws ExceptionCompile - if any processing errors occurs
98304fd306cSNickeau     */
98404fd306cSNickeau    public function processIfNeeded(): FetcherMarkup
98504fd306cSNickeau    {
98604fd306cSNickeau
98704fd306cSNickeau        if (!$this->shouldProcess()) {
98804fd306cSNickeau            return $this;
98904fd306cSNickeau        }
99004fd306cSNickeau
99104fd306cSNickeau        $this->process();
99204fd306cSNickeau        return $this;
99304fd306cSNickeau
99404fd306cSNickeau    }
99504fd306cSNickeau
99604fd306cSNickeau
99704fd306cSNickeau    /**
99804fd306cSNickeau     * @return array - the markup instructions
99904fd306cSNickeau     * @throws ExceptionNotExists - if the executing markup file does not exist
100004fd306cSNickeau     */
100104fd306cSNickeau    public function getInstructions(): array
100204fd306cSNickeau    {
100304fd306cSNickeau
100404fd306cSNickeau        if (isset($this->requestedInstructions)) {
100504fd306cSNickeau
100604fd306cSNickeau            return $this->requestedInstructions;
100704fd306cSNickeau
100804fd306cSNickeau        }
100904fd306cSNickeau
101004fd306cSNickeau        /**
101104fd306cSNickeau         * We create a fetcher markup to not have the same {@link self::getId()}
101204fd306cSNickeau         * on execution
101304fd306cSNickeau         */
101404fd306cSNickeau        if (ExecutionContext::getActualOrCreateFromEnv()->hasExecutingMarkupHandler()) {
101504fd306cSNickeau            $fetcherMarkupBuilder = FetcherMarkup::confChild();
101604fd306cSNickeau        } else {
101704fd306cSNickeau            $fetcherMarkupBuilder = FetcherMarkup::confRoot();
101804fd306cSNickeau        }
101904fd306cSNickeau        $fetcherMarkupBuilder = $fetcherMarkupBuilder
102004fd306cSNickeau            ->setRequestedMime(Mime::create(Mime::INSTRUCTIONS))
102104fd306cSNickeau            ->setRequestedRenderer(FetcherMarkupInstructions::NAME)
102204fd306cSNickeau            ->setIsDocument($this->isDoc)
102304fd306cSNickeau            ->setRequestedContextPath($this->getRequestedContextPath());
102404fd306cSNickeau        if ($this->isPathExecution()) {
102504fd306cSNickeau            $fetcherMarkupBuilder->setRequestedExecutingPath($this->getExecutingPathOrFail());
102604fd306cSNickeau        } else {
102704fd306cSNickeau            $fetcherMarkupBuilder->setRequestedMarkupString($this->markupString);
102804fd306cSNickeau        }
102904fd306cSNickeau        $fetcherMarkup = $fetcherMarkupBuilder->build();
103004fd306cSNickeau        return $fetcherMarkup->getProcessedInstructions();
103104fd306cSNickeau
103204fd306cSNickeau
103304fd306cSNickeau    }
103404fd306cSNickeau
103504fd306cSNickeau
103604fd306cSNickeau    /**
103704fd306cSNickeau     * @return bool - a document
103804fd306cSNickeau     *
103904fd306cSNickeau     * A document will get an {@link Outline} processing
104004fd306cSNickeau     * while a {@link self::isFragment() fragment} will not.
104104fd306cSNickeau     */
104204fd306cSNickeau    public function isDocument(): bool
104304fd306cSNickeau    {
104404fd306cSNickeau
104504fd306cSNickeau        return $this->isDoc;
104604fd306cSNickeau
104704fd306cSNickeau    }
104804fd306cSNickeau
104904fd306cSNickeau    public function getSnippetManager(): SnippetSystem
105004fd306cSNickeau    {
105104fd306cSNickeau        return PluginUtility::getSnippetManager();
105204fd306cSNickeau    }
105304fd306cSNickeau
105404fd306cSNickeau    /**
105504fd306cSNickeau     * @throws ExceptionBadSyntax
105604fd306cSNickeau     * @throws ExceptionCompile
105704fd306cSNickeau     */
105804fd306cSNickeau    public function getFetchStringAsDom(): XmlDocument
105904fd306cSNickeau    {
106004fd306cSNickeau        return XmlDocument::createXmlDocFromMarkup($this->getFetchString());
106104fd306cSNickeau    }
106204fd306cSNickeau
106304fd306cSNickeau    public function getSnippetsAsHtmlString(): string
106404fd306cSNickeau    {
106504fd306cSNickeau
106604fd306cSNickeau        try {
106704fd306cSNickeau            $globalSnippets = SnippetSystem::getFromContext()->getSnippetsForSlot($this->getRequestedExecutingPath()->toAbsoluteId());
106804fd306cSNickeau        } catch (ExceptionNotFound $e) {
106904fd306cSNickeau            // string execution
107004fd306cSNickeau            $globalSnippets = [];
107104fd306cSNickeau        }
107204fd306cSNickeau        $allSnippets = array_merge($globalSnippets, $this->localSnippets);
107304fd306cSNickeau        return SnippetSystem::toHtmlFromSnippetArray($allSnippets);
107404fd306cSNickeau
107504fd306cSNickeau    }
107604fd306cSNickeau
107704fd306cSNickeau    public function isFragment(): bool
107804fd306cSNickeau    {
107904fd306cSNickeau        return $this->isDocument() === false;
108004fd306cSNickeau    }
108104fd306cSNickeau
108204fd306cSNickeau    private function getMarkupStringToExecute(): string
108304fd306cSNickeau    {
108404fd306cSNickeau        if (isset($this->markupString)) {
108504fd306cSNickeau            return $this->markupString;
108604fd306cSNickeau        } else {
108704fd306cSNickeau            try {
108804fd306cSNickeau                $sourcePath = $this->getSourcePath();
108904fd306cSNickeau            } catch (ExceptionNotFound $e) {
109004fd306cSNickeau                throw new ExceptionRuntimeInternal("A markup or a source markup path should be specified.");
109104fd306cSNickeau            }
109204fd306cSNickeau            try {
109304fd306cSNickeau                return FileSystems::getContent($sourcePath);
109404fd306cSNickeau            } catch (ExceptionNotFound $e) {
109504fd306cSNickeau                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);
109604fd306cSNickeau                return "";
109704fd306cSNickeau            }
109804fd306cSNickeau        }
109904fd306cSNickeau    }
110004fd306cSNickeau
110104fd306cSNickeau    public function getContextData(): array
110204fd306cSNickeau    {
110304fd306cSNickeau        if (isset($this->contextData)) {
110404fd306cSNickeau            return $this->contextData;
110504fd306cSNickeau        }
110604fd306cSNickeau        $this->contextData = MarkupPath::createPageFromPathObject($this->getRequestedContextPath())->getMetadataForRendering();
110704fd306cSNickeau        return $this->contextData;
110804fd306cSNickeau    }
110904fd306cSNickeau
111004fd306cSNickeau
111104fd306cSNickeau    public function getToc(): array
111204fd306cSNickeau    {
111304fd306cSNickeau
111404fd306cSNickeau        if (isset($this->toc)) {
111504fd306cSNickeau            return $this->toc;
111604fd306cSNickeau        }
111704fd306cSNickeau        try {
111804fd306cSNickeau            return TOC::createForPage($this->getRequestedExecutingPath())->getValue();
111904fd306cSNickeau        } catch (ExceptionNotFound $e) {
112004fd306cSNickeau            // no executing page or no value
112104fd306cSNickeau        }
112204fd306cSNickeau        /**
112304fd306cSNickeau         * Derived TOC from instructions
112404fd306cSNickeau         */
112504fd306cSNickeau        return Outline::createFromCallStack(CallStack::createFromInstructions($this->getInstructions()))->toTocDokuwikiFormat();
112604fd306cSNickeau
112704fd306cSNickeau
112804fd306cSNickeau    }
112904fd306cSNickeau
113004fd306cSNickeau    public function getInstructionsPath(): LocalPath
113104fd306cSNickeau    {
113204fd306cSNickeau        $path = $this->instructionsCache->cache;
113304fd306cSNickeau        return LocalPath::createFromPathString($path);
113404fd306cSNickeau    }
113504fd306cSNickeau
113604fd306cSNickeau    public function getOutline(): Outline
113704fd306cSNickeau    {
113804fd306cSNickeau        $instructions = $this->getInstructions();
113904fd306cSNickeau        $callStack = CallStack::createFromInstructions($instructions);
114004fd306cSNickeau        try {
114104fd306cSNickeau            $markupPath = MarkupPath::createPageFromPathObject($this->getRequestedExecutingPath());
114204fd306cSNickeau        } catch (ExceptionNotFound $e) {
114304fd306cSNickeau            $markupPath = null;
114404fd306cSNickeau        }
114504fd306cSNickeau        return Outline::createFromCallStack($callStack, $markupPath);
114604fd306cSNickeau    }
114704fd306cSNickeau
114804fd306cSNickeau
114904fd306cSNickeau    public function getMetadata(): array
115004fd306cSNickeau    {
115104fd306cSNickeau
115204fd306cSNickeau        $this->processMetadataIfNotYetDone();
115304fd306cSNickeau        return $this->meta;
115404fd306cSNickeau
115504fd306cSNickeau    }
115604fd306cSNickeau
115704fd306cSNickeau
115804fd306cSNickeau    /**
115904fd306cSNickeau     * Adaptation of {@link p_get_metadata()}
116004fd306cSNickeau     * to take into account {@link self::getInstructions()}
116104fd306cSNickeau     * where we can just pass our own instructions.
116204fd306cSNickeau     *
116304fd306cSNickeau     * And yes, adaptation of {@link p_get_metadata()}
116404fd306cSNickeau     * that process the metadata. Yeah, it calls {@link p_render_metadata()}
116504fd306cSNickeau     * and save them
116604fd306cSNickeau     *
116704fd306cSNickeau     */
116804fd306cSNickeau    public function processMetadataIfNotYetDone(): FetcherMarkup
116904fd306cSNickeau    {
117004fd306cSNickeau
117104fd306cSNickeau        /**
117204fd306cSNickeau         * Already set ?
117304fd306cSNickeau         */
117404fd306cSNickeau        if (isset($this->meta)) {
117504fd306cSNickeau            return $this;
117604fd306cSNickeau        }
117704fd306cSNickeau
117804fd306cSNickeau        $actualMeta = [];
117904fd306cSNickeau
118004fd306cSNickeau        /**
118104fd306cSNickeau         * We wrap the whole block
118204fd306cSNickeau         * because {@link CacheRenderer::useCache()}
118304fd306cSNickeau         * and the renderer needs it
118404fd306cSNickeau         */
118504fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()->setExecutingMarkupHandler($this);
118604fd306cSNickeau        try {
118704fd306cSNickeau
118804fd306cSNickeau            /**
118904fd306cSNickeau             * Can we read from the meta file
119004fd306cSNickeau             */
119104fd306cSNickeau
119204fd306cSNickeau
119304fd306cSNickeau            if ($this->isPathExecution()) {
119404fd306cSNickeau
119504fd306cSNickeau                /**
119604fd306cSNickeau                 * If the meta file exists
119704fd306cSNickeau                 */
119804fd306cSNickeau                if (FileSystems::exists($this->getMetaPathOrFail())) {
119904fd306cSNickeau
120004fd306cSNickeau                    $executingPath = $this->getExecutingPathOrFail();
120104fd306cSNickeau                    $actualMeta = MetadataDokuWikiStore::getOrCreateFromResource(MarkupPath::createPageFromPathObject($executingPath))
120204fd306cSNickeau                        ->getDataCurrentAndPersistent();
120304fd306cSNickeau
120404fd306cSNickeau                    /**
120504fd306cSNickeau                     * The metadata useCache function has side effect
120604fd306cSNickeau                     * and triggers a render that fails if the wiki file does not exists
120704fd306cSNickeau                     */
120804fd306cSNickeau                    $depends['files'][] = $this->instructionsCache->cache;
120904fd306cSNickeau                    $depends['files'][] = $executingPath->toAbsolutePath()->toAbsoluteId();
121004fd306cSNickeau                    $useCache = $this->metaCache->useCache($depends);
121104fd306cSNickeau                    if ($useCache) {
121204fd306cSNickeau                        $this->meta = $actualMeta;
121304fd306cSNickeau                        return $this;
121404fd306cSNickeau                    }
121504fd306cSNickeau                }
121604fd306cSNickeau            }
121704fd306cSNickeau
121804fd306cSNickeau            /**
121904fd306cSNickeau             * Process and derived meta
122004fd306cSNickeau             */
122104fd306cSNickeau            try {
122204fd306cSNickeau                $wikiId = $this->getRequestedExecutingPath()->toWikiPath()->getWikiId();
122304fd306cSNickeau            } catch (ExceptionCast|ExceptionNotFound $e) {
122404fd306cSNickeau                // not a wiki path execution
122504fd306cSNickeau                $wikiId = null;
122604fd306cSNickeau            }
122704fd306cSNickeau
122804fd306cSNickeau            /**
122904fd306cSNickeau             * Dokuwiki global variable used to see if the process is in rendering mode
123004fd306cSNickeau             * See {@link p_get_metadata()}
123104fd306cSNickeau             * Store the original metadata in the global $METADATA_RENDERERS
123204fd306cSNickeau             * ({@link p_set_metadata()} use it)
123304fd306cSNickeau             */
123404fd306cSNickeau            global $METADATA_RENDERERS;
123504fd306cSNickeau            $METADATA_RENDERERS[$wikiId] =& $actualMeta;
123604fd306cSNickeau
123704fd306cSNickeau            // add an extra key for the event - to tell event handlers the page whose metadata this is
123804fd306cSNickeau            $actualMeta['page'] = $wikiId;
123904fd306cSNickeau            $evt = new \dokuwiki\Extension\Event('PARSER_METADATA_RENDER', $actualMeta);
124004fd306cSNickeau            if ($evt->advise_before()) {
124104fd306cSNickeau
124204fd306cSNickeau                // get instructions (from string or file)
124304fd306cSNickeau                $instructions = $this->getInstructions();
124404fd306cSNickeau
124504fd306cSNickeau                // set up the renderer
124604fd306cSNickeau                $renderer = new Doku_Renderer_metadata();
124704fd306cSNickeau
124804fd306cSNickeau
124904fd306cSNickeau                /**
125004fd306cSNickeau                 * Runtime/ Derived metadata
125104fd306cSNickeau                 * The runtime meta are not even deleted
125204fd306cSNickeau                 * (See {@link p_render_metadata()}
125304fd306cSNickeau                 */
125404fd306cSNickeau                $renderer->meta =& $actualMeta['current'];
125504fd306cSNickeau
125604fd306cSNickeau                /**
125704fd306cSNickeau                 * The {@link Doku_Renderer_metadata}
125804fd306cSNickeau                 * will fail if the file and the date modified property does not exist
125904fd306cSNickeau                 */
126004fd306cSNickeau                try {
126104fd306cSNickeau                    $path = $this->getRequestedExecutingPath();
126204fd306cSNickeau                    if (!FileSystems::exists($path)) {
126304fd306cSNickeau                        $renderer->meta['date']['modified'] = null;
126404fd306cSNickeau                    }
126504fd306cSNickeau                } catch (ExceptionNotFound $e) {
126604fd306cSNickeau                    // ok
126704fd306cSNickeau                }
126804fd306cSNickeau
126904fd306cSNickeau                /**
127004fd306cSNickeau                 * The persistent data are now available
127104fd306cSNickeau                 */
127204fd306cSNickeau                $renderer->persistent =& $actualMeta['persistent'];
127304fd306cSNickeau
127404fd306cSNickeau                // Loop through the instructions
127504fd306cSNickeau                foreach ($instructions as $instruction) {
127604fd306cSNickeau                    // execute the callback against the renderer
127704fd306cSNickeau                    call_user_func_array(array(&$renderer, $instruction[0]), (array)$instruction[1]);
127804fd306cSNickeau                }
127904fd306cSNickeau
128004fd306cSNickeau                $evt->result = array('current' => &$renderer->meta, 'persistent' => &$renderer->persistent);
128104fd306cSNickeau
128204fd306cSNickeau            }
128304fd306cSNickeau            $evt->advise_after();
128404fd306cSNickeau
128504fd306cSNickeau            $this->meta = $evt->result;
128604fd306cSNickeau
128704fd306cSNickeau            /**
128804fd306cSNickeau             * Dokuwiki global variable
128904fd306cSNickeau             * See {@link p_get_metadata()}
129004fd306cSNickeau             */
129104fd306cSNickeau            unset($METADATA_RENDERERS[$wikiId]);
129204fd306cSNickeau
129304fd306cSNickeau            /**
129404fd306cSNickeau             * Storage
129504fd306cSNickeau             */
129604fd306cSNickeau            if ($wikiId !== null) {
129704fd306cSNickeau                p_save_metadata($wikiId, $this->meta);
129804fd306cSNickeau                $this->metaCache->storeCache(time());
129904fd306cSNickeau            }
130004fd306cSNickeau
130104fd306cSNickeau        } finally {
130204fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
130304fd306cSNickeau        }
130404fd306cSNickeau        return $this;
130504fd306cSNickeau
130604fd306cSNickeau    }
130704fd306cSNickeau
130804fd306cSNickeau    /**
130904fd306cSNickeau     * @throws ExceptionNotFound
131004fd306cSNickeau     */
131104fd306cSNickeau    public function getMetadataPath(): LocalPath
131204fd306cSNickeau    {
131304fd306cSNickeau        if (isset($this->metaPath)) {
131404fd306cSNickeau            return $this->metaPath;
131504fd306cSNickeau        }
131604fd306cSNickeau        throw new ExceptionNotFound("No meta path for this markup");
131704fd306cSNickeau    }
131804fd306cSNickeau
131904fd306cSNickeau    /**
132004fd306cSNickeau     * A wrapper from when we are in a code block
132104fd306cSNickeau     * were we expect to be a {@link self::isPathExecution()}
132204fd306cSNickeau     * All path should then be available
132304fd306cSNickeau     * @return Path
132404fd306cSNickeau     */
132504fd306cSNickeau    private
132604fd306cSNickeau    function getExecutingPathOrFail(): Path
132704fd306cSNickeau    {
132804fd306cSNickeau        try {
132904fd306cSNickeau            return $this->getRequestedExecutingPath();
133004fd306cSNickeau        } catch (ExceptionNotFound $e) {
133104fd306cSNickeau            throw new ExceptionRuntime($e);
133204fd306cSNickeau        }
133304fd306cSNickeau    }
133404fd306cSNickeau
133504fd306cSNickeau    /**
133604fd306cSNickeau     * A wrapper from when we are in a code block
133704fd306cSNickeau     * were we expect to be a {@link self::isPathExecution()}
133804fd306cSNickeau     * All path should then be available
133904fd306cSNickeau     * @return Path
134004fd306cSNickeau     */
134104fd306cSNickeau    private
134204fd306cSNickeau    function getMetaPathOrFail()
134304fd306cSNickeau    {
134404fd306cSNickeau        try {
134504fd306cSNickeau            return $this->getMetadataPath();
134604fd306cSNickeau        } catch (ExceptionNotFound $e) {
134704fd306cSNickeau            throw new ExceptionRuntime($e);
134804fd306cSNickeau        }
134904fd306cSNickeau    }
135004fd306cSNickeau
135104fd306cSNickeau    /**
135204fd306cSNickeau     * Process the instructions
135304fd306cSNickeau     * TODO: move to a FetcherMarkup by tree instruction and array/encoding mime
135404fd306cSNickeau     * @return $this
135504fd306cSNickeau     */
135604fd306cSNickeau    public function processInstructions(): FetcherMarkup
135704fd306cSNickeau    {
135804fd306cSNickeau        if (isset($this->processedInstructions)) {
135904fd306cSNickeau            return $this;
136004fd306cSNickeau        }
136104fd306cSNickeau
136204fd306cSNickeau        $markup = $this->getMarkupStringToExecute();
136304fd306cSNickeau        $executionContext = ExecutionContext::getActualOrCreateFromEnv()
136404fd306cSNickeau            ->setExecutingMarkupHandler($this);
136504fd306cSNickeau        try {
136604fd306cSNickeau            $markupRenderer = MarkupRenderer::createFromMarkup($markup, $this->getExecutingPathOrNull(), $this->getRequestedContextPath())
136704fd306cSNickeau                ->setRequestedMimeToInstruction();
136804fd306cSNickeau            $instructions = $markupRenderer->getOutput();
136904fd306cSNickeau            if (isset($this->instructionsCache)) {
137004fd306cSNickeau                /**
137104fd306cSNickeau                 * Not a string execution, ie {@link self::isPathExecution()}
137204fd306cSNickeau                 * a path execution
137304fd306cSNickeau                 */
137404fd306cSNickeau                $this->instructionsCache->storeCache($instructions);
137504fd306cSNickeau            }
137604fd306cSNickeau            $this->processedInstructions = $instructions;
137704fd306cSNickeau            return $this;
137804fd306cSNickeau        } catch (\Exception $e) {
1379edc35203Sgerardnico            throw new ExceptionRuntimeInternal("An error has occurred while processing the instructions. Error: {$e->getMessage()}", self::CANONICAL, 1, $e);
138004fd306cSNickeau        } finally {
138104fd306cSNickeau            $executionContext->closeExecutingMarkupHandler();
138204fd306cSNickeau        }
138304fd306cSNickeau    }
138404fd306cSNickeau
138504fd306cSNickeau    public function getSnippetCachePath(): LocalPath
138604fd306cSNickeau    {
138704fd306cSNickeau        $cache = $this->getSnippetCacheStore()->cache;
138804fd306cSNickeau        return LocalPath::createFromPathString($cache);
138904fd306cSNickeau
139004fd306cSNickeau    }
139104fd306cSNickeau
139204fd306cSNickeau    /**
139304fd306cSNickeau     * @return string - an execution id to be sure that we don't execute the same twice in recursion
139404fd306cSNickeau     */
139504fd306cSNickeau    public function getId(): string
139604fd306cSNickeau    {
139704fd306cSNickeau
139804fd306cSNickeau        return "({$this->getSourceName()}) to ({$this->builderName} - {$this->getMime()}) with context ({$this->getRequestedContextPath()->toUriString()})";
139904fd306cSNickeau    }
140004fd306cSNickeau
140104fd306cSNickeau    /**
140204fd306cSNickeau     * @return string - a name for the source (used in {@link self::__toString()} and {@link self::getId() identification})
140304fd306cSNickeau     */
140404fd306cSNickeau    private function getSourceName(): string
140504fd306cSNickeau    {
140604fd306cSNickeau        if (!$this->isPathExecution()) {
140704fd306cSNickeau            if (isset($this->markupString)) {
140804fd306cSNickeau                $md5 = md5($this->markupString);
140904fd306cSNickeau                return "Markup String Execution ($md5)";
141004fd306cSNickeau            } elseif (isset($this->requestedInstructions)) {
141104fd306cSNickeau                return "Markup Instructions Execution";
141204fd306cSNickeau            } else {
141304fd306cSNickeau                LogUtility::internalError("The name of the markup handler is unknown");
141404fd306cSNickeau                return "Markup Unknown Execution";
141504fd306cSNickeau
141604fd306cSNickeau            }
141704fd306cSNickeau        } else {
141804fd306cSNickeau            try {
141904fd306cSNickeau                return $this->getSourcePath()->toUriString();
142004fd306cSNickeau            } catch (ExceptionNotFound $e) {
142104fd306cSNickeau                throw new ExceptionRuntimeInternal("A source path should be defined if it's not a markup string execution");
142204fd306cSNickeau            }
142304fd306cSNickeau        }
142404fd306cSNickeau    }
142504fd306cSNickeau
142604fd306cSNickeau
142704fd306cSNickeau    /**
142804fd306cSNickeau     * TODO: move to a FetcherMarkup by object type output and mime
142904fd306cSNickeau     * @return array
143004fd306cSNickeau     */
143104fd306cSNickeau    private function getProcessedInstructions(): array
143204fd306cSNickeau    {
143304fd306cSNickeau
143404fd306cSNickeau
143504fd306cSNickeau        if (!$this->shouldInstructionProcess()) {
143604fd306cSNickeau
143704fd306cSNickeau            $this->processedInstructions = $this->instructionsCache->retrieveCache();
143804fd306cSNickeau
143904fd306cSNickeau        } else {
144004fd306cSNickeau
144104fd306cSNickeau            $this->processInstructions();
144204fd306cSNickeau
144304fd306cSNickeau        }
144404fd306cSNickeau        return $this->processedInstructions;
144504fd306cSNickeau
144604fd306cSNickeau    }
144704fd306cSNickeau
144804fd306cSNickeau    /**
144904fd306cSNickeau     * @throws ExceptionNotFound
145004fd306cSNickeau     */
145104fd306cSNickeau    public function getParent(): FetcherMarkup
145204fd306cSNickeau    {
145304fd306cSNickeau        if (!isset($this->parentMarkupHandler)) {
145404fd306cSNickeau            throw new ExceptionNotFound();
145504fd306cSNickeau        }
145604fd306cSNickeau        return $this->parentMarkupHandler;
145704fd306cSNickeau    }
145804fd306cSNickeau
1459*70bbd7f1Sgerardnico    /**
1460*70bbd7f1Sgerardnico     * Hack to not have the {@link MarkupDynamicRender}
1461*70bbd7f1Sgerardnico     * stored in {@link FetcherMarkup::$markupDynamicRenderer}
1462*70bbd7f1Sgerardnico     * to reset
1463*70bbd7f1Sgerardnico     * @param array $requestedInstructions
1464*70bbd7f1Sgerardnico     * @param array|null $row
1465*70bbd7f1Sgerardnico     * @return FetcherMarkup
1466*70bbd7f1Sgerardnico     */
1467*70bbd7f1Sgerardnico    public function setNextIteratorInstructionsWithContext(array $requestedInstructions, array $row = null): FetcherMarkup
1468*70bbd7f1Sgerardnico    {
1469*70bbd7f1Sgerardnico        $this->hasExecuted = false;
1470*70bbd7f1Sgerardnico        if ($row === null) {
1471*70bbd7f1Sgerardnico            unset($this->contextData);
1472*70bbd7f1Sgerardnico        } else {
1473*70bbd7f1Sgerardnico            $this->contextData = $row;
1474*70bbd7f1Sgerardnico        }
1475*70bbd7f1Sgerardnico        $this->requestedInstructions = $requestedInstructions;
1476*70bbd7f1Sgerardnico        return $this;
1477*70bbd7f1Sgerardnico    }
1478*70bbd7f1Sgerardnico
147904fd306cSNickeau
148004fd306cSNickeau}
1481