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