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