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