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