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