1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\Meta\Store\MetadataDbStore;
7use ComboStrap\Meta\Store\MetadataDokuWikiStore;
8use ComboStrap\Tag\WebCodeTag;
9use ComboStrap\Web\Url;
10use dokuwiki\ActionRouter;
11use dokuwiki\Extension\EventHandler;
12use TestRequest;
13
14
15/**
16 * An execution object permits to manage the variable state for
17 * an execution (ie one HTTP request)
18 *
19 *
20 * Note that normally every page has a page context
21 * meaning that you can go from an admin page to show the page.
22 *
23 *
24 * When an execution context has finished, it should be {@link ExecutionContext::close() closed}
25 * or destroyed
26 *
27 * You can get the actual execution context with {@link ExecutionContext::getActualOrCreateFromEnv()}
28 *
29 *
30 * Same concept than [routing context](https://vertx.io/docs/apidocs/index.html?io/vertx/ext/web/RoutingContext.html)
31 * (Not yet fully implemented)
32 * ```java
33 * if (pet.isPresent())
34 *    routingContext
35 *     .response()
36 *      .setStatusCode(200)
37 *       .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
38 *      .end(pet.get().encode()); // (4)
39 * else
40 *    routingContext.fail(404, new Exception("Pet not found"));
41 * }
42 * ```
43 */
44class ExecutionContext
45{
46
47    /**
48     * Dokuwiki do attribute
49     */
50    const DO_ATTRIBUTE = "do";
51
52
53    const CANONICAL = "execution-context";
54
55    /**
56     * All action (handler)
57     * That's what you will found in the `do` parameters
58     */
59    const SHOW_ACTION = "show";
60    const EDIT_ACTION = "edit";
61    /**
62     * Preview is also used to
63     * set the {@link FetcherMarkup::isFragment()}
64     * processing to fragment
65     */
66    const PREVIEW_ACTION = "preview";
67    const ADMIN_ACTION = "admin";
68    const DRAFT_ACTION = "draft";
69    const SEARCH_ACTION = "search";
70    const LOGIN_ACTION = "login";
71    const SAVE_ACTION = "save";
72    const DRAFT_DEL_ACTION = "draftdel";
73    const REDIRECT_ACTION = "redirect";
74
75    /**
76     * private actions does not render a page to be indexed
77     * by a search engine (ie no redirect)
78     * May be easier, if not `show`, not public
79     */
80    const PRIVATES_ACTION_NO_REDIRECT = [
81        self::EDIT_ACTION,
82        self::PREVIEW_ACTION,
83        self::ADMIN_ACTION,
84        self::DRAFT_ACTION,
85        self::DRAFT_DEL_ACTION,
86        self::SEARCH_ACTION,
87        self::LOGIN_ACTION,
88        self::SAVE_ACTION,
89        self::REDIRECT_ACTION,
90        self::REGISTER_ACTION,
91        self::RESEND_PWD_ACTION,
92        self::PROFILE_ACTION,
93    ];
94    const REGISTER_ACTION = "register";
95    const RESEND_PWD_ACTION = "resendpwd";
96    const PROFILE_ACTION = "profile";
97    const REVISIONS_ACTION = "revisions";
98    const DIFF_ACTION = "diff";
99    const INDEX_ACTION = "index";
100
101    /**
102     * The media manager
103     */
104    const MEDIA_ACTION = "media";
105    /**
106     * The recent changes
107     */
108    const RECENT_ACTION = "recent";
109
110
111    /**
112     * @var array of objects that are scoped to this request
113     */
114    private array $executionScopedVariables = [];
115
116    private CacheManager $cacheManager;
117
118    private IdManager $idManager;
119
120    private Site $app;
121
122    /**
123     * A root execution context if any
124     * Null because you can not unset a static variable
125     */
126    private static ?ExecutionContext $actualExecutionContext = null;
127
128    private ?string $capturedGlobalId;
129    /**
130     * It may be an array when preview/save/cancel
131     * @var array|string|null
132     */
133    private $capturedAct;
134
135
136    private Url $url;
137
138
139    public HttpResponse $response;
140
141    /**
142     * @var IFetcher - the fetcher that takes into account the HTTP request
143     */
144    private IFetcher $executingMainFetcher;
145
146    /**
147     * @var array - a stack of:
148     *   * markup handler executing (ie handler that is taking a markup (file, string) and making it a HTML, pdf, ...)
149     *   * and old global environement, $executingId, $contextExecutingId, $act
150     *
151     * This fetcher is called by the main fetcher or by the {@link self::setExecutingMarkupHandler()}
152     */
153    private array $executingMarkupHandlerStack = [];
154
155    /**
156     * @var TemplateForWebPage - the page template fetcher running (when a fetcher creates a page, it would uses this fetcher)
157     * This class is called by the main fetcher to create a page
158     */
159    private TemplateForWebPage $executingPageTemplate;
160    private string $creationTime;
161
162
163    public function __construct()
164    {
165
166        $this->creationTime = Iso8601Date::createFromNow()->toIsoStringMs();
167
168        $this->url = Url::createFromGetOrPostGlobalVariable();
169
170        $this->response = HttpResponse::createFromExecutionContext($this);
171
172        /**
173         * The requested action
174         */
175        global $ACT;
176        $this->capturedAct = $ACT;
177        try {
178            $urlAct = $this->url->getQueryPropertyValue(self::DO_ATTRIBUTE);
179        } catch (ExceptionNotFound $e) {
180            /**
181             * The value is unknown
182             * (in doku.php, the default is `show`,
183             * we take the dokuwiki value because the execution context may be
184             * created after the dokuwiki init)
185             */
186            $urlAct = $ACT;
187        }
188        $ACT = $urlAct;
189
190        /**
191         * The requested id
192         */
193        global $ID;
194        $this->capturedGlobalId = $ID;
195        try {
196
197            $urlId = $this->url->getQueryPropertyValue(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE);
198            if (is_array($urlId)) {
199                /**
200                 * hack because the dokuwiki request global object as `ID` and `id` as array
201                 * but our own {@link Url object} don't allow that and makes an array instead
202                 * We don't use this data anyway, anymore ...
203                 */
204                $urlId = $urlId[0];
205            }
206            $ID = $urlId;
207
208        } catch (ExceptionNotFound $e) {
209            // none
210            $ID = null;
211        }
212
213
214    }
215
216
217    /**
218     * @throws ExceptionNotFound
219     */
220    public static function getExecutionContext(): ExecutionContext
221    {
222        if (!isset(self::$actualExecutionContext)) {
223            throw new ExceptionNotFound("No root context");
224        }
225        return self::$actualExecutionContext;
226    }
227
228    /**
229     * Utility class to set the requested id (used only in test,
230     * normally the environment is set from global PHP environment variable
231     * that get the HTTP request
232     * @param string $requestedId
233     * @return ExecutionContext
234     * @deprecated use {@link self::setDefaultContextPath()} if you want to set a context path
235     * without using a {@link TemplateForWebPage} or {@link FetcherMarkup}
236     */
237    public static function getOrCreateFromRequestedWikiId(string $requestedId): ExecutionContext
238    {
239
240        return self::getActualOrCreateFromEnv()
241            ->setDefaultContextPath(WikiPath::createMarkupPathFromId($requestedId));
242
243    }
244
245
246    /**
247     * @return ExecutionContext
248     * @deprecated uses {@link self::createBlank()} instead
249     */
250    public static function createFromEnvironmentVariable(): ExecutionContext
251    {
252        return self::createBlank();
253    }
254
255
256    public static function createBlank(): ExecutionContext
257    {
258
259        if (self::$actualExecutionContext !== null) {
260            throw new ExceptionRuntimeInternal("The previous root context should be closed first");
261        }
262        $rootExecutionContext = (new ExecutionContext());
263        self::$actualExecutionContext = $rootExecutionContext;
264        return $rootExecutionContext;
265
266    }
267
268    /**
269     * @return ExecutionContext - return the actual context or create a new one from the environment
270     */
271    public static function getActualOrCreateFromEnv(): ExecutionContext
272    {
273        try {
274            return self::getExecutionContext();
275        } catch (ExceptionNotFound $e) {
276            return self::createBlank();
277        }
278    }
279
280    /**
281     * We create the id manager in the execution
282     * context
283     * (because in case a user choose to not use templating, the {@link FetcherMarkup}
284     * is not available)
285     * And all dynamic component such as {@link \syntax_plugin_combo_dropdown} would not
286     * work anymore.
287     *
288     * @return IdManager
289     */
290    public function getIdManager(): IdManager
291    {
292        if (!isset($this->idManager)) {
293            $this->idManager = new IdManager($this);
294        }
295        return $this->idManager;
296    }
297
298    /**
299     * Return the actual context path
300     */
301    public function getContextNamespacePath(): WikiPath
302    {
303        $requestedPath = $this->getContextPath();
304        try {
305            return $requestedPath->getParent();
306        } catch (ExceptionNotFound $e) {
307            // root
308            return $requestedPath;
309        }
310
311    }
312
313
314    /**
315     * @throws ExceptionNotFound
316     */
317    public function getExecutingWikiId(): string
318    {
319        global $ID;
320        if (empty($ID)) {
321            throw new ExceptionNotFound("No executing id was found");
322        }
323        return $ID;
324    }
325
326
327    /**
328     * @return void close the execution context
329     */
330    public function close()
331    {
332
333        /**
334         * Check that this execution context was not closed
335         */
336        if (self::$actualExecutionContext->creationTime !== $this->creationTime) {
337            throw new ExceptionRuntimeInternal("This execution context was already closed");
338        }
339
340        /**
341         * Restore the global $conf of dokuwiki
342         */
343        $this->getApp()->getConfig()->restoreConfigState();
344
345        /** global dokuwiki messages variable */
346        global $MSG;
347        unset($MSG);
348
349        /**
350         * Environment restoration
351         * Execution context, change for now only this
352         * global variables
353         */
354        global $ACT;
355        $ACT = $this->getCapturedAct();
356        global $ID;
357        $ID = $this->getCapturedRunningId();
358        global $TOC;
359        unset($TOC);
360
361        // global scope store
362        MetadataDbStore::resetAll();
363        MetadataDokuWikiStore::unsetGlobalVariables();
364
365        // Router: dokuwiki global
366        // reset event handler
367        global $EVENT_HANDLER;
368        $EVENT_HANDLER = new EventHandler();
369        /**
370         * We can't give the getInstance, a true value
371         * because it will otherwise start the routing process
372         * {@link ActionRouter::getInstance()}
373         */
374
375
376        /**
377         * Close execution variables
378         * (and therefore also {@link Sqlite}
379         */
380        $this->closeExecutionVariables();
381
382        /**
383         * Is this really needed ?
384         * as we unset the execution context below
385         */
386        unset($this->executingMainFetcher);
387        unset($this->executingMarkupHandlerStack);
388        unset($this->cacheManager);
389        unset($this->idManager);
390
391        /**
392         * Deleting
393         */
394        self::$actualExecutionContext = null;
395
396
397    }
398
399
400    public function getCapturedRunningId(): ?string
401    {
402        return $this->capturedGlobalId;
403    }
404
405    public function getCapturedAct()
406    {
407        return $this->capturedAct;
408    }
409
410    public function getCacheManager(): CacheManager
411    {
412        $root = self::$actualExecutionContext;
413        if (!isset($root->cacheManager)) {
414            $root->cacheManager = new CacheManager($this);
415        }
416        return $root->cacheManager;
417
418    }
419
420    /**
421     * Return the root path if nothing is found
422     */
423    public function getRequestedPath(): WikiPath
424    {
425        /**
426         * Do we have a template page executing ?
427         */
428        try {
429            return $this->getExecutingPageTemplate()
430                ->getRequestedContextPath();
431        } catch (ExceptionNotFound $e) {
432            try {
433                /**
434                 * Case when the main handler
435                 * run the main content before
436                 * to inject it in the template page
437                 * {@link TemplateForWebPage::render()}
438                 */
439                return $this->getExecutingMarkupHandler()
440                    ->getRequestedContextPath();
441            } catch (ExceptionNotFound $e) {
442
443
444                /**
445                 * not a template engine running
446                 * The id notion is a little bit everywhere
447                 * That's why we just don't check the action ($ACT)
448                 *
449                 * Example:
450                 * * `id` may be asked by acl to determine the right
451                 * * ...
452                 */
453                global $INPUT;
454                $inputId = $INPUT->str("id");
455                if (!empty($inputId)) {
456                    return WikiPath::createMarkupPathFromId($inputId);
457                }
458
459                global $ID;
460                if (!empty($ID)) {
461                    return WikiPath::createMarkupPathFromId($ID);
462                }
463
464                /**
465                 * This should be less used
466                 * but shows where the requested id is spilled in dokuwiki
467                 *
468                 * If the component is in a sidebar, we don't want the ID of the sidebar
469                 * but the ID of the page.
470                 */
471                global $INFO;
472                if ($INFO !== null) {
473                    $callingId = $INFO['id'] ?? null;
474                    if (!empty($callingId)) {
475                        return WikiPath::createMarkupPathFromId($callingId);
476                    }
477                }
478
479                /**
480                 * This is the case with event triggered
481                 * before DokuWiki such as
482                 * https://www.dokuwiki.org/devel:event:init_lang_load
483                 * REQUEST is a mixed of post and get parameters
484                 */
485                global $_REQUEST;
486                if (isset($_REQUEST[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE])) {
487                    $requestId = $_REQUEST[DokuwikiId::DOKUWIKI_ID_ATTRIBUTE];
488                    if (!empty($requestId)) {
489                        return WikiPath::createMarkupPathFromId($requestId);
490                    }
491                }
492
493                // not that show action is the default even if it's not set
494                // we can't then control if the id should exists or not
495                // markup based on string (test) or snippet of code
496                // return the default context path (ie the root page)
497                return $this->getConfig()->getDefaultContextPath();
498            }
499
500        }
501
502    }
503
504    /**
505     * @throws ExceptionNotFound
506     */
507    public function &getRuntimeObject(string $objectIdentifier)
508    {
509        if (isset($this->executionScopedVariables[$objectIdentifier])) {
510            return $this->executionScopedVariables[$objectIdentifier];
511        }
512        throw new ExceptionNotFound("No object $objectIdentifier found");
513    }
514
515    public function setRuntimeObject($objectIdentifier, &$object): ExecutionContext
516    {
517        $this->executionScopedVariables[$objectIdentifier] = &$object;
518        return $this;
519    }
520
521    public function getUrl(): Url
522    {
523        return $this->url;
524    }
525
526
527    /**
528     * @param string $key
529     * @param $value
530     * @param string|null $pluginNamespace - if null, stored in the global conf namespace
531     * @return $this
532     * @deprecated use {@link SiteConfig::setConf()} instead
533     */
534    public function setConf(string $key, $value, ?string $pluginNamespace = PluginUtility::PLUGIN_BASE_NAME): ExecutionContext
535    {
536        $this->getApp()->getConfig()->setConf($key, $value, $pluginNamespace);
537        return $this;
538    }
539
540    /**
541     * @param string $key
542     * @param string|null $default
543     * @return mixed|null
544     * @deprecated use
545     */
546    public function getConfValue(string $key, string $default = null)
547    {
548        return $this->getApp()->getConfig()->getValue($key, $default);
549    }
550
551    public function setRuntimeBoolean(string $key, bool $b): ExecutionContext
552    {
553        $this->executionScopedVariables[$key] = $b;
554        return $this;
555    }
556
557    /**
558     * @throws ExceptionNotFound
559     */
560    public function getRuntimeBoolean(string $name): bool
561    {
562        $var = $this->executionScopedVariables[$name] ?? null;
563        if (!isset($var)) {
564            throw new ExceptionNotFound("No $name runtime env was found");
565        }
566        return DataType::toBoolean($var);
567    }
568
569    /**
570     * @return $this
571     * @deprecated uses {@link SiteConfig::setCacheXhtmlOn()}
572     */
573    public function setCacheXhtmlOn(): ExecutionContext
574    {
575        $this->getApp()->getConfig()->setCacheXhtmlOn();
576        return $this;
577    }
578
579    /**
580     *
581     * @return $this
582     * @deprecated use the {@link SiteConfig::setConsoleOn} instead
583     */
584    public function setConsoleOn(): ExecutionContext
585    {
586        $this->getApp()->getConfig()->setCacheXhtmlOn();
587        return $this;
588    }
589
590    public function setConsoleOff(): ExecutionContext
591    {
592        $this->getConfig()->setConsoleOff();
593        return $this;
594    }
595
596    /**
597     * @return $this
598     * @deprecated use {@link SiteConfig::setDisableThemeSystem()}
599     */
600    public function setDisableTemplating(): ExecutionContext
601    {
602        $this->getApp()->getConfig()->setDisableThemeSystem();
603        return $this;
604    }
605
606
607    /**
608     * @return bool
609     * @deprecated use the {@link SiteConfig::isConsoleOn()} instead
610     */
611    public function isConsoleOn(): bool
612    {
613        return $this->getApp()->getConfig()->isConsoleOn();
614    }
615
616
617    /**
618     * Dokuwiki handler name
619     * @return array|mixed|string
620     */
621    public function getExecutingAction()
622    {
623        global $ACT;
624        return $ACT;
625    }
626
627    public function setLogExceptionToError(): ExecutionContext
628    {
629        $this->getConfig()->setLogExceptionToError();
630        return $this;
631    }
632
633    /**
634     * @return SnippetSystem
635     * It's not attached to the {@link FetcherMarkup}
636     * because the user may choose to not use it (ie {@link SiteConfig::isThemeSystemEnabled()}
637     */
638    public function getSnippetSystem(): SnippetSystem
639    {
640        return SnippetSystem::getFromContext();
641    }
642
643    /**
644     * @return bool - does the action create a publication (render a page)
645     */
646    public function isPublicationAction(): bool
647    {
648
649        $act = $this->getExecutingAction();
650        if (in_array($act, self::PRIVATES_ACTION_NO_REDIRECT)) {
651            return false;
652        }
653
654        return true;
655
656    }
657
658    public function setEnableSectionEditing(): ExecutionContext
659    {
660        $this->setConf('maxseclevel', 999, null);
661        return $this;
662    }
663
664    /**
665     * @param string $value
666     * @return $this
667     * @deprecated use the {@link SiteConfig::setCanonicalUrlType()} instead
668     */
669    public function setCanonicalUrlType(string $value): ExecutionContext
670    {
671        $this->getConfig()->setCanonicalUrlType($value);
672        return $this;
673    }
674
675    public function setUseHeadingAsTitle(): ExecutionContext
676    {
677        // https://www.dokuwiki.org/config:useheading
678        $this->setConf('useheading', 1, null);
679        return $this;
680    }
681
682    public function response(): HttpResponse
683    {
684        return $this->response;
685    }
686
687    /**
688     * @param string|null $executingId
689     * @return void
690     */
691    private function setExecutingId(?string $executingId): void
692    {
693        global $ID;
694        if ($executingId == null) {
695            // ID should not be null
696            // to be able to check the ACL
697            return;
698        }
699        $executingId = WikiPath::removeRootSepIfPresent($executingId);
700        $ID = $executingId;
701    }
702
703    public function setConfGlobal(string $key, string $value): ExecutionContext
704    {
705        $this->setConf($key, $value, null);
706        return $this;
707    }
708
709    /**
710     * @return bool - if this execution is a test running
711     */
712    public function isTestRun(): bool
713    {
714        /**
715         * Test Requested is loaded only in a test run
716         * Does not exist in a normal installation
717         * and is not found, triggering an exception
718         */
719        if (class_exists('TestRequest')) {
720            $testRequest = TestRequest::getRunning();
721            return $testRequest !== null;
722        }
723        return false;
724
725    }
726
727    private function setExecutingAction(?string $runningAct): ExecutionContext
728    {
729        global $ACT;
730        $ACT = $runningAct;
731        return $this;
732    }
733
734
735    /**
736     * Set the main fetcher, the entry point of the request (ie the url of the browser)
737     * that will return a string
738     * @throws ExceptionBadArgument
739     * @throws ExceptionInternal
740     * @throws ExceptionNotFound
741     */
742    public function createStringMainFetcherFromRequestedUrl(Url $fetchUrl): IFetcherString
743    {
744        $this->executingMainFetcher = FetcherSystem::createFetcherStringFromUrl($fetchUrl);
745        return $this->executingMainFetcher;
746    }
747
748
749    /**
750     * Set the main fetcher (with the url of the browser)
751     * that will return a path (image, ...)
752     * @throws ExceptionBadArgument
753     * @throws ExceptionInternal
754     * @throws ExceptionNotFound
755     */
756    public function createPathMainFetcherFromUrl(Url $fetchUrl): IFetcherPath
757    {
758        $this->executingMainFetcher = FetcherSystem::createPathFetcherFromUrl($fetchUrl);
759        return $this->executingMainFetcher;
760    }
761
762    public function closeMainExecutingFetcher(): ExecutionContext
763    {
764        unset($this->executingMainFetcher);
765        /**
766         * Snippet are not yet fully coupled to the {@link FetcherMarkup}
767         */
768        $this->closeAndRemoveRuntimeVariableIfExists(Snippet::CANONICAL);
769        return $this;
770    }
771
772    /**
773     * This function sets the markup running context object globally,
774     * so that code may access it via this global variable
775     * (Fighting dokuwiki global scope)
776     * @param FetcherMarkup $markupHandler
777     * @return $this
778     */
779    public function setExecutingMarkupHandler(FetcherMarkup $markupHandler): ExecutionContext
780    {
781
782
783        /**
784         * Act
785         */
786        $oldAct = $this->getExecutingAction();
787        if (!$markupHandler->isPathExecution() && $oldAct !== ExecutionContext::PREVIEW_ACTION) {
788            /**
789             * Not sure that is is still needed
790             * as we have now the notion of document/fragment
791             * {@link FetcherMarkup::isDocument()}
792             */
793            $runningAct = FetcherMarkup::MARKUP_DYNAMIC_EXECUTION_NAME;
794            $this->setExecutingAction($runningAct);
795        }
796
797        /**
798         * Id
799         */
800        try {
801            $oldExecutingId = $this->getExecutingWikiId();
802        } catch (ExceptionNotFound $e) {
803            $oldExecutingId = null;
804        }
805        try {
806
807            $executingPath = $markupHandler->getRequestedExecutingPath();
808            $executingId = $executingPath->toAbsoluteId();
809            $this->setExecutingId($executingId);
810        } catch (ExceptionNotFound $e) {
811            // no executing path dynamic markup execution
812        }
813
814        /**
815         * $INFO  (Fragment run, ...)
816         * We don't use {@link pageinfo()} for now
817         * We just advertise if this is a fragment run
818         * via the `id`
819         */
820        global $INFO;
821        $oldContextId = $INFO['id'] ?? null;
822        if ($markupHandler->isFragment()) {
823            $contextPath = $markupHandler->getRequestedContextPath();
824            $wikiId = $contextPath->getWikiId();
825            $INFO['id'] = $wikiId;
826            $INFO['namespace'] = getNS($wikiId); // php8 Undefined array key
827        }
828
829        /**
830         * Call to Fetcher Markup can be recursive,
831         * we try to break a loop
832         *
833         * Note that the same object may call recursively:
834         * * the {@link FetcherMarkup::processMetaEventually()} metadata may call the {@link FetcherMarkup::getInstructions() instructions},
835         */
836        $id = $markupHandler->getId();
837        if (array_key_exists($id, $this->executingMarkupHandlerStack)) {
838            LogUtility::internalError("The markup ($id) is already executing");
839            $id = "$id-already-in-stack";
840        }
841        $this->executingMarkupHandlerStack[$id] = [$markupHandler, $oldExecutingId, $oldContextId, $oldAct];
842        return $this;
843    }
844
845    public
846    function closeExecutingMarkupHandler(): ExecutionContext
847    {
848        /** @noinspection PhpUnusedLocalVariableInspection */
849        [$markupHandler, $oldExecutingId, $oldContextId, $oldAct] = array_pop($this->executingMarkupHandlerStack);
850
851        $this
852            ->setExecutingAction($oldAct)
853            ->setExecutingId($oldExecutingId);
854
855        global $INFO;
856        if ($oldExecutingId === null) {
857            unset($INFO['id']);
858        } else {
859            $INFO['id'] = $oldContextId;
860            $INFO['namespace'] = getNS($oldContextId);
861        }
862        return $this;
863    }
864
865
866    /**
867     * @throws ExceptionNotFound - if there is no markup handler execution running
868     */
869    public
870    function getExecutingMarkupHandler(): FetcherMarkup
871    {
872        $count = count($this->executingMarkupHandlerStack);
873        if ($count >= 1) {
874            return $this->executingMarkupHandlerStack[array_key_last($this->executingMarkupHandlerStack)][0];
875        }
876        throw new ExceptionNotFound("No markup handler running");
877    }
878
879    /**
880     * @throws ExceptionNotFound - if there is no parent markup handler execution found
881     */
882    public
883    function getExecutingParentMarkupHandler(): FetcherMarkup
884    {
885        return $this->getExecutingMarkupHandler()->getParent();
886    }
887
888    /**
889     * This function sets the default context path.
890     *
891     * Mostly used in test, to determine relative path
892     * when testing {@link LinkMarkup} and {@link WikiPath}
893     * and not use a {@link FetcherMarkup}
894     *
895     * @param WikiPath $contextPath - a markup file context path used (not a namespace)
896     * @return $this
897     * @deprecated
898     */
899    public
900    function setDefaultContextPath(WikiPath $contextPath): ExecutionContext
901    {
902        $this->getConfig()->setDefaultContextPath($contextPath);
903        return $this;
904    }
905
906
907    /**
908     * @return WikiPath - the context path is a markup file that gives context.
909     * Ie this is the equivalent of the current directory.
910     * When a link/path is empty or relative, the program will check for the context path
911     * to calculate the absolute path
912     */
913    public
914    function getContextPath(): WikiPath
915    {
916
917        try {
918
919            /**
920             * Do we a fetcher markup running ?
921             * (It's first as we may change it
922             * for a slot for instance)
923             */
924            return $this
925                ->getExecutingMarkupHandler()
926                ->getRequestedContextPath();
927
928        } catch (ExceptionNotFound $e) {
929            try {
930
931                /**
932                 * Do we have a template page executing ?
933                 */
934                return $this->getExecutingPageTemplate()
935                    ->getRequestedContextPath();
936
937            } catch (ExceptionNotFound $e) {
938
939                /**
940                 * Hack, hack, hack
941                 * In  preview mode, the context path is the last visited page
942                 * for a slot
943                 */
944                global $ACT;
945                if ($ACT === ExecutionContext::PREVIEW_ACTION) {
946                    global $ID;
947                    if (!empty($ID)) {
948                        try {
949                            $markupPath = MarkupPath::createMarkupFromId($ID);
950                            if ($markupPath->isSlot()) {
951                                return SlotSystem::getContextPath()->toWikiPath();
952                            }
953                        } catch (ExceptionCast|ExceptionNotFound $e) {
954                            // ok
955                        }
956                    }
957                }
958
959                /**
960                 * Nope ? This is a dokuwiki run (admin page, ...)
961                 */
962                return $this->getConfig()->getDefaultContextPath();
963
964            }
965
966        }
967
968
969    }
970
971
972    /**
973     * @return WikiPath
974     * @deprecated uses {@link SiteConfig::getDefaultContextPath()}
975     */
976    public
977    function getDefaultContextPath(): WikiPath
978    {
979        return $this->getConfig()->getDefaultContextPath();
980    }
981
982    /**
983     * The page global context object
984     * @throws ExceptionNotFound
985     */
986    public
987    function getExecutingPageTemplate(): TemplateForWebPage
988    {
989        if (isset($this->executingPageTemplate)) {
990            return $this->executingPageTemplate;
991        }
992        throw new ExceptionNotFound("No page template execution running");
993    }
994
995    /**
996     * Set the page template that is executing.
997     * It's the context object for all page related
998     * (mostly header event)
999     * @param TemplateForWebPage $pageTemplate
1000     * @return $this
1001     */
1002    public
1003    function setExecutingPageTemplate(TemplateForWebPage $pageTemplate): ExecutionContext
1004    {
1005        $this->executingPageTemplate = $pageTemplate;
1006        return $this;
1007    }
1008
1009    public
1010    function closeExecutingPageTemplate(): ExecutionContext
1011    {
1012        unset($this->executingPageTemplate);
1013        return $this;
1014    }
1015
1016    public
1017    function getApp(): Site
1018    {
1019        if (isset($this->app)) {
1020            return $this->app;
1021        }
1022        $this->app = new Site($this);
1023        return $this->app;
1024    }
1025
1026    /**
1027     * @return SiteConfig short utility function to get access to the global app config
1028     */
1029    public
1030    function getConfig(): SiteConfig
1031    {
1032        return $this->getApp()->getConfig();
1033    }
1034
1035    /**
1036     * @throws ExceptionNotFound - when there is no executing id (markup execution)
1037     */
1038    public
1039    function getExecutingWikiPath(): WikiPath
1040    {
1041        try {
1042            return $this->getExecutingMarkupHandler()
1043                ->getRequestedExecutingPath()
1044                ->toWikiPath();
1045        } catch (ExceptionCast|ExceptionNotFound $e) {
1046            // Execution without templating (ie without fetcher markup)
1047            return WikiPath::createMarkupPathFromId($this->getExecutingWikiId());
1048        }
1049
1050    }
1051
1052    /**
1053     * @return array - data in context
1054     * This is the central point to get data in context as there is no
1055     * content object in dokuwiki
1056     *
1057     * It takes care of returning the context path
1058     * (in case of slot via the {@link self::getContextPath()}
1059     */
1060    public
1061    function getContextData(): array
1062    {
1063
1064        try {
1065
1066            /**
1067             * Context data may be dynamically given
1068             * by the {@link \syntax_plugin_combo_iterator}
1069             */
1070            return $this
1071                ->getExecutingMarkupHandler()
1072                ->getContextData();
1073
1074        } catch (ExceptionNotFound $e) {
1075
1076            /**
1077             * Preview / slot
1078             */
1079            return MarkupPath::createPageFromPathObject($this->getContextPath())->getMetadataForRendering();
1080
1081        }
1082
1083    }
1084
1085    /**
1086     * This method will delete the global identifier
1087     * and call the 'close' method if the method exists.
1088     * @param string $globalObjectIdentifier
1089     * @return void
1090     */
1091    public
1092    function closeAndRemoveRuntimeVariableIfExists(string $globalObjectIdentifier)
1093    {
1094
1095        if (!isset($this->executionScopedVariables[$globalObjectIdentifier])) {
1096            return;
1097        }
1098
1099        /**
1100         * Get the object references
1101         */
1102        $object = &$this->executionScopedVariables[$globalObjectIdentifier];
1103
1104
1105        /**
1106         * Call the close method
1107         */
1108        if (is_object($object)) {
1109            if (method_exists($object, 'close')) {
1110                $object->close();
1111            }
1112        }
1113
1114        /**
1115         * Close it really by setting null
1116         *
1117         * (Forwhatever reason, sqlite closing in php
1118         * is putting the variable to null)
1119         */
1120        $object = null;
1121
1122        /**
1123         * Delete it from the array
1124         */
1125        unset($this->executionScopedVariables[$globalObjectIdentifier]);
1126
1127    }
1128
1129    /**
1130     * Close all execution variables
1131     */
1132    public
1133    function closeExecutionVariables(): ExecutionContext
1134    {
1135        $scopedVariables = array_keys($this->executionScopedVariables);
1136        foreach ($scopedVariables as $executionScopedVariableKey) {
1137            $this->closeAndRemoveRuntimeVariableIfExists($executionScopedVariableKey);
1138        }
1139        return $this;
1140    }
1141
1142    public
1143    function __toString()
1144    {
1145        return $this->creationTime;
1146    }
1147
1148    public
1149    function isExecutingPageTemplate(): bool
1150    {
1151        try {
1152            $this->getExecutingPageTemplate();
1153            return true;
1154        } catch (ExceptionNotFound $e) {
1155            return false;
1156        }
1157    }
1158
1159    public
1160    function hasExecutingMarkupHandler(): bool
1161    {
1162        try {
1163            $this->getExecutingMarkupHandler();
1164            return true;
1165        } catch (ExceptionNotFound $e) {
1166            return false;
1167        }
1168    }
1169
1170
1171}
1172