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 /** 774 * Act 775 */ 776 $oldAct = $this->getExecutingAction(); 777 if (!$markupHandler->isPathExecution() && $oldAct !== ExecutionContext::PREVIEW_ACTION) { 778 /** 779 * Not sure that is is still needed 780 * as we have now the notion of document/fragment 781 * {@link FetcherMarkup::isDocument()} 782 */ 783 $runningAct = FetcherMarkup::MARKUP_DYNAMIC_EXECUTION_NAME; 784 $this->setExecutingAction($runningAct); 785 } 786 787 /** 788 * Id 789 */ 790 try { 791 $oldExecutingId = $this->getExecutingWikiId(); 792 } catch (ExceptionNotFound $e) { 793 $oldExecutingId = null; 794 } 795 try { 796 797 $executingPath = $markupHandler->getRequestedExecutingPath(); 798 $executingId = $executingPath->toAbsoluteId(); 799 $this->setExecutingId($executingId); 800 } catch (ExceptionNotFound $e) { 801 // no executing path dynamic markup execution 802 } 803 804 /** 805 * $INFO (Fragment run, ...) 806 * We don't use {@link pageinfo()} for now 807 * We just advertise if this is a fragment run 808 * via the `id` 809 */ 810 global $INFO; 811 $oldContextId = $INFO['id']; 812 if ($markupHandler->isFragment()) { 813 $contextPath = $markupHandler->getRequestedContextPath(); 814 $INFO['id'] = $contextPath->getWikiId(); 815 } 816 817 /** 818 * Call to Fetcher Markup can be recursive, 819 * we try to break a loop 820 * 821 * Note that the same object may call recursively: 822 * * the {@link FetcherMarkup::processMetaEventually()} metadata may call the {@link FetcherMarkup::getInstructions() instructions}, 823 */ 824 $id = $markupHandler->getId(); 825 if(array_key_exists($id,$this->executingMarkupHandlerStack)){ 826 LogUtility::internalError("The markup ($id) is already executing"); 827 $id = "$id-already-in-stack"; 828 } 829 $this->executingMarkupHandlerStack[$id] = [$markupHandler, $oldExecutingId, $oldContextId, $oldAct]; 830 return $this; 831 } 832 833 public 834 function closeExecutingMarkupHandler(): ExecutionContext 835 { 836 /** @noinspection PhpUnusedLocalVariableInspection */ 837 [$markupHandler, $oldExecutingId, $oldContextId, $oldAct] = array_pop($this->executingMarkupHandlerStack); 838 839 $this 840 ->setExecutingAction($oldAct) 841 ->setExecutingId($oldExecutingId); 842 843 global $INFO; 844 if ($oldExecutingId === null) { 845 unset($INFO['id']); 846 } else { 847 $INFO['id'] = $oldContextId; 848 } 849 return $this; 850 } 851 852 853 /** 854 * @throws ExceptionNotFound - if there is no markup handler execution running 855 */ 856 public 857 function getExecutingMarkupHandler(): FetcherMarkup 858 { 859 $count = count($this->executingMarkupHandlerStack); 860 if ($count >= 1) { 861 return $this->executingMarkupHandlerStack[array_key_last($this->executingMarkupHandlerStack)][0]; 862 } 863 throw new ExceptionNotFound("No markup handler running"); 864 } 865 866 /** 867 * @throws ExceptionNotFound - if there is no parent markup handler execution found 868 */ 869 public 870 function getExecutingParentMarkupHandler(): FetcherMarkup 871 { 872 return $this->getExecutingMarkupHandler()->getParent(); 873 } 874 875 /** 876 * This function sets the default context path. 877 * 878 * Mostly used in test, to determine relative path 879 * when testing {@link LinkMarkup} and {@link WikiPath} 880 * and not use a {@link FetcherMarkup} 881 * 882 * @param WikiPath $contextPath - a markup file context path used (not a namespace) 883 * @return $this 884 * @deprecated 885 */ 886 public 887 function setDefaultContextPath(WikiPath $contextPath): ExecutionContext 888 { 889 $this->getConfig()->setDefaultContextPath($contextPath); 890 return $this; 891 } 892 893 894 /** 895 * @return WikiPath - the context path is a markup file that gives context. 896 * Ie this is the equivalent of the current directory. 897 * When a link/path is empty or relative, the program will check for the context path 898 * to calculate the absolute path 899 */ 900 public 901 function getContextPath(): WikiPath 902 { 903 904 try { 905 906 /** 907 * Do we a fetcher markup running ? 908 * (It's first as we may change it 909 * for a slot for instance) 910 */ 911 return $this 912 ->getExecutingMarkupHandler() 913 ->getRequestedContextPath(); 914 915 } catch (ExceptionNotFound $e) { 916 try { 917 918 /** 919 * Do we have a template page executing ? 920 */ 921 return $this->getExecutingPageTemplate() 922 ->getRequestedContextPath(); 923 924 } catch (ExceptionNotFound $e) { 925 926 /** 927 * Hack, hack, hack 928 * In preview mode, the context path is the last visited page 929 * for a slot 930 */ 931 global $ACT; 932 if ($ACT === ExecutionContext::PREVIEW_ACTION) { 933 global $ID; 934 if (!empty($ID)) { 935 try { 936 $markupPath = MarkupPath::createMarkupFromId($ID); 937 if ($markupPath->isSlot()) { 938 return SlotSystem::getContextPath()->toWikiPath(); 939 } 940 } catch (ExceptionCast|ExceptionNotFound $e) { 941 // ok 942 } 943 } 944 } 945 946 /** 947 * Nope ? This is a dokuwiki run (admin page, ...) 948 */ 949 return $this->getConfig()->getDefaultContextPath(); 950 951 } 952 953 } 954 955 956 } 957 958 959 /** 960 * @return WikiPath 961 * @deprecated uses {@link SiteConfig::getDefaultContextPath()} 962 */ 963 public 964 function getDefaultContextPath(): WikiPath 965 { 966 return $this->getConfig()->getDefaultContextPath(); 967 } 968 969 /** 970 * The page global context object 971 * @throws ExceptionNotFound 972 */ 973 public 974 function getExecutingPageTemplate(): TemplateForWebPage 975 { 976 if (isset($this->executingPageTemplate)) { 977 return $this->executingPageTemplate; 978 } 979 throw new ExceptionNotFound("No page template execution running"); 980 } 981 982 /** 983 * Set the page template that is executing. 984 * It's the context object for all page related 985 * (mostly header event) 986 * @param TemplateForWebPage $pageTemplate 987 * @return $this 988 */ 989 public 990 function setExecutingPageTemplate(TemplateForWebPage $pageTemplate): ExecutionContext 991 { 992 $this->executingPageTemplate = $pageTemplate; 993 return $this; 994 } 995 996 public 997 function closeExecutingPageTemplate(): ExecutionContext 998 { 999 unset($this->executingPageTemplate); 1000 return $this; 1001 } 1002 1003 public 1004 function getApp(): Site 1005 { 1006 if (isset($this->app)) { 1007 return $this->app; 1008 } 1009 $this->app = new Site($this); 1010 return $this->app; 1011 } 1012 1013 /** 1014 * @return SiteConfig short utility function to get access to the global app config 1015 */ 1016 public 1017 function getConfig(): SiteConfig 1018 { 1019 return $this->getApp()->getConfig(); 1020 } 1021 1022 /** 1023 * @throws ExceptionNotFound - when there is no executing id (markup execution) 1024 */ 1025 public 1026 function getExecutingWikiPath(): WikiPath 1027 { 1028 try { 1029 return $this->getExecutingMarkupHandler() 1030 ->getRequestedExecutingPath() 1031 ->toWikiPath(); 1032 } catch (ExceptionCast|ExceptionNotFound $e) { 1033 // Execution without templating (ie without fetcher markup) 1034 return WikiPath::createMarkupPathFromId($this->getExecutingWikiId()); 1035 } 1036 1037 } 1038 1039 /** 1040 * @return array - data in context 1041 * This is the central point to get data in context as there is no 1042 * content object in dokuwiki 1043 * 1044 * It takes care of returning the context path 1045 * (in case of slot via the {@link self::getContextPath()} 1046 */ 1047 public 1048 function getContextData(): array 1049 { 1050 1051 try { 1052 1053 /** 1054 * Context data may be dynamically given 1055 * by the {@link \syntax_plugin_combo_iterator} 1056 */ 1057 return $this 1058 ->getExecutingMarkupHandler() 1059 ->getContextData(); 1060 1061 } catch (ExceptionNotFound $e) { 1062 1063 /** 1064 * Preview / slot 1065 */ 1066 return MarkupPath::createPageFromPathObject($this->getContextPath())->getMetadataForRendering(); 1067 1068 } 1069 1070 } 1071 1072 /** 1073 * This method will delete the global identifier 1074 * and call the 'close' method if the method exists. 1075 * @param string $globalObjectIdentifier 1076 * @return void 1077 */ 1078 public 1079 function closeAndRemoveRuntimeVariableIfExists(string $globalObjectIdentifier) 1080 { 1081 1082 if (!isset($this->executionScopedVariables[$globalObjectIdentifier])) { 1083 return; 1084 } 1085 1086 /** 1087 * Get the object references 1088 */ 1089 $object = &$this->executionScopedVariables[$globalObjectIdentifier]; 1090 1091 1092 /** 1093 * Call the close method 1094 */ 1095 if (is_object($object)) { 1096 if (method_exists($object, 'close')) { 1097 $object->close(); 1098 } 1099 } 1100 1101 /** 1102 * Close it really by setting null 1103 * 1104 * (Forwhatever reason, sqlite closing in php 1105 * is putting the variable to null) 1106 */ 1107 $object = null; 1108 1109 /** 1110 * Delete it from the array 1111 */ 1112 unset($this->executionScopedVariables[$globalObjectIdentifier]); 1113 1114 } 1115 1116 /** 1117 * Close all execution variables 1118 */ 1119 public 1120 function closeExecutionVariables(): ExecutionContext 1121 { 1122 $scopedVariables = array_keys($this->executionScopedVariables); 1123 foreach ($scopedVariables as $executionScopedVariableKey) { 1124 $this->closeAndRemoveRuntimeVariableIfExists($executionScopedVariableKey); 1125 } 1126 return $this; 1127 } 1128 1129 public 1130 function __toString() 1131 { 1132 return $this->creationTime; 1133 } 1134 1135 public 1136 function isExecutingPageTemplate(): bool 1137 { 1138 try { 1139 $this->getExecutingPageTemplate(); 1140 return true; 1141 } catch (ExceptionNotFound $e) { 1142 return false; 1143 } 1144 } 1145 1146 public 1147 function hasExecutingMarkupHandler(): bool 1148 { 1149 try { 1150 $this->getExecutingMarkupHandler(); 1151 return true; 1152 } catch (ExceptionNotFound $e) { 1153 return false; 1154 } 1155 } 1156 1157 1158} 1159