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