1<?php 2 3namespace ComboStrap; 4 5 6use ComboStrap\Api\QualityMessageHandler; 7use ComboStrap\Meta\Api\Metadata; 8use ComboStrap\Meta\Api\MetadataBoolean; 9use ComboStrap\Meta\Api\MetadataStore; 10use ComboStrap\Meta\Api\MetadataStoreAbs; 11use ComboStrap\Meta\Field\Alias; 12use ComboStrap\Meta\Field\Aliases; 13use ComboStrap\Meta\Field\AliasType; 14use ComboStrap\Meta\Field\PageH1; 15use ComboStrap\Meta\Field\PageImage; 16use ComboStrap\Meta\Field\PageImages; 17use ComboStrap\Meta\Field\PageTemplateName; 18use ComboStrap\Meta\Field\Region; 19use ComboStrap\Meta\Store\MetadataDokuWikiStore; 20use ComboStrap\Web\Url; 21use ComboStrap\Web\UrlEndpoint; 22use DateTime; 23use dokuwiki\ChangeLog\ChangeLog; 24use Exception; 25use renderer_plugin_combo_analytics; 26 27 28/** 29 * 30 * A markup is a logical unit that represents a markup file. 31 * 32 * It has its own file system {@link MarkupFileSystem} explained in the 33 * https://combostrap.com/page/system (or system.txt file). 34 * ie the {@link Path::getParent()} is not the same than on an normal file system. 35 * 36 * This should be an extension of {@link WikiPath} but for now, we are not extending {@link WikiPath} 37 * for the following old reasons: 38 * * we want to be able to return a {@link MarkupPath} in the {@link MarkupPath::getParent()} function 39 * otherwise if we do, we get a hierarchical error. 40 * * we can then accepts also {@link LocalPath} 41 * 42 * But because this is a {@link ResourceCombo}, we see tht this is part of the {@link WikiPath} 43 * system with an {@link ResourceCombo::getUid()} unique uid. 44 * 45 * We should find a way to be able to create a wiki path with a {@link LocalPath} 46 * via the {@link WikiPath::getDrive()} ? 47 * 48 */ 49class MarkupPath extends PathAbs implements ResourceCombo, Path 50{ 51 52 const CANONICAL_PAGE = "markup"; 53 54 55 const TYPE = "page"; 56 57 /** 58 * @var Canonical 59 */ 60 private $canonical; 61 /** 62 * @var PageH1 63 */ 64 private $h1; 65 /** 66 * @var ResourceName 67 */ 68 private $pageName; 69 /** 70 * @var PageType 71 */ 72 private $type; 73 /** 74 * @var PageTitle $title 75 */ 76 private $title; 77 78 private $uidObject; 79 80 private LowQualityPageOverwrite $canBeOfLowQuality; 81 /** 82 * @var Region 83 */ 84 private $region; 85 /** 86 * @var Lang 87 */ 88 private $lang; 89 /** 90 * @var PageId 91 */ 92 private $pageId; 93 94 /** 95 * @var LowQualityCalculatedIndicator 96 */ 97 private $lowQualityIndicatorCalculated; 98 99 /** 100 * @var PageTemplateName 101 */ 102 private $layout; 103 /** 104 * @var Aliases 105 */ 106 private $aliases; 107 /** 108 * @var Slug a slug path 109 */ 110 private $slug; 111 112 113 /** 114 * @var QualityDynamicMonitoringOverwrite 115 */ 116 private $qualityMonitoringIndicator; 117 118 /** 119 * @var string the alias used to build this page 120 */ 121 private $buildAliasPath; 122 /** 123 * @var PagePublicationDate 124 */ 125 private $publishedDate; 126 /** 127 * @var StartDate 128 */ 129 private $startDate; 130 /** 131 * @var EndDate 132 */ 133 private $endDate; 134 /** 135 * @var PageImages 136 */ 137 private $pageImages; 138 139 private PageKeywords $keywords; 140 /** 141 * @var CacheExpirationFrequency 142 */ 143 private $cacheExpirationFrequency; 144 /** 145 * @var CacheExpirationDate 146 */ 147 private $cacheExpirationDate; 148 /** 149 * 150 * @var LdJson 151 */ 152 private $ldJson; 153 154 155 /** 156 * @var PageDescription $description 157 */ 158 private $description; 159 /** 160 * @var CreationDate 161 */ 162 private $creationTime; 163 /** 164 * @var Locale 165 */ 166 private $locale; 167 /** 168 * @var ModificationDate 169 */ 170 private $modifiedTime; 171 /** 172 * @var PageUrlPath 173 */ 174 private $pageUrlPath; 175 /** 176 * @var MetadataStore|string 177 */ 178 private $readStore; 179 180 /** 181 * @var Path - {@link MarkupPath} has other hierachy system in regards with parent 182 * May be we just should extends {@link WikiPath} but it was a way to be able to locate 183 * default markup path file that were not in any drive 184 * TODO: Just extends WikiPath and add private drive when data should be accessed locally ? 185 */ 186 private Path $path; 187 188 /** 189 * Page constructor. 190 * 191 */ 192 public function __construct(Path $path) 193 { 194 195 $this->path = $path; 196 if (FileSystems::isDirectory($path)) { 197 $this->setCorrectPathForDirectoryToIndexPage(); 198 } 199 $this->buildPropertiesFromFileSystem(); 200 201 } 202 203 /** 204 * The current running rendering markup 205 * @throws ExceptionNotFound 206 */ 207 public static function createPageFromExecutingId(): MarkupPath 208 { 209 $wikiPath = WikiPath::createExecutingMarkupWikiPath(); 210 return self::createPageFromPathObject($wikiPath); 211 } 212 213 214 public static function createMarkupFromId($id): MarkupPath 215 { 216 return new MarkupPath(WikiPath::createMarkupPathFromId($id)); 217 } 218 219 /** 220 * @param string $path - relative or absolute 221 * @return MarkupPath 222 */ 223 public static function createMarkupFromStringPath(string $path): MarkupPath 224 { 225 $wikiPath = WikiPath::createMarkupPathFromPath($path); 226 return new MarkupPath($wikiPath); 227 228 } 229 230 /** 231 * @return MarkupPath - the requested page 232 * @throws ExceptionNotFound 233 */ 234 public static function createFromRequestedPage(): MarkupPath 235 { 236 $path = WikiPath::createRequestedPagePathFromRequest(); 237 return MarkupPath::createPageFromPathObject($path); 238 } 239 240 241 public static function createPageFromPathObject(Path $path): MarkupPath 242 { 243 if ($path instanceof MarkupPath) { 244 return $path; 245 } 246 return new MarkupPath($path); 247 } 248 249 250 /** 251 * 252 * @throws ExceptionBadSyntax - if this is not a 253 * @deprecated just pass a namespace path to the page creation and you will get the index page in return 254 */ 255 public static function getIndexPageFromNamespace(string $namespacePath): MarkupPath 256 { 257 WikiPath::checkNamespacePath($namespacePath); 258 259 return MarkupPath::createMarkupFromId($namespacePath); 260 } 261 262 263 static function createPageFromAbsoluteId($qualifiedPath): MarkupPath 264 { 265 $path = WikiPath::createMarkupPathFromId($qualifiedPath); 266 return new MarkupPath($path); 267 } 268 269 270 /** 271 * 272 * @throws ExceptionCompile 273 */ 274 public 275 function setCanonical($canonical): MarkupPath 276 { 277 $this->canonical 278 ->setValue($canonical) 279 ->sendToWriteStore(); 280 return $this; 281 } 282 283 284 /** 285 * @return bool true if this is a fragment markup 286 */ 287 public function isSlot(): bool 288 { 289 $slotNames = SlotSystem::getSlotNames(); 290 try { 291 $name = $this->getPathObject()->getLastNameWithoutExtension(); 292 } catch (ExceptionNotFound $e) { 293 // root case 294 return false; 295 } 296 return in_array($name, $slotNames, true); 297 } 298 299 /** 300 * @return bool true if this is the side slot 301 */ 302 public function isSideSlot(): bool 303 { 304 $slotNames = SlotSystem::getSidebarName(); 305 try { 306 $name = $this->getPathObject()->getLastNameWithoutExtension(); 307 } catch (ExceptionNotFound $e) { 308 // root case 309 return false; 310 } 311 return $name === $slotNames; 312 } 313 314 /** 315 * @return bool true if this is the main 316 */ 317 public function isMainHeaderFooterSlot(): bool 318 { 319 320 $slotNames = [SlotSystem::getMainHeaderSlotName(), SlotSystem::getMainFooterSlotName()]; 321 try { 322 $name = $this->getPathObject()->getLastNameWithoutExtension(); 323 } catch (ExceptionNotFound $e) { 324 // root case 325 return false; 326 } 327 328 return in_array($name, $slotNames, true); 329 } 330 331 332 /** 333 * Return a canonical if set 334 * otherwise derive it from the id 335 * by taking the last two parts 336 * 337 * @return WikiPath 338 * @throws ExceptionNotFound 339 * @deprecated for {@link Canonical::getValueOrDefault()} 340 */ 341 public 342 function getCanonicalOrDefault(): WikiPath 343 { 344 return $this->canonical->getValueFromStoreOrDefault(); 345 346 } 347 348 349 /** 350 * Rebuild the page 351 * (refresh from disk, reset object to null) 352 * @return $this 353 */ 354 public 355 function rebuild(): MarkupPath 356 { 357 $this->readStore = null; 358 $this->buildPropertiesFromFileSystem(); 359 return $this; 360 } 361 362 /** 363 * 364 * @return MarkupPath[]|null the internal links or null 365 */ 366 public 367 function getLinkReferences(): ?array 368 { 369 $store = $this->getReadStoreOrDefault(); 370 if (!($store instanceof MetadataDokuWikiStore)) { 371 return null; 372 } 373 $metadata = $store->getCurrentFromName('relation'); 374 if ($metadata === null) { 375 /** 376 * Happens when no rendering has been made 377 */ 378 return null; 379 } 380 if (!key_exists('references', $metadata)) { 381 return null; 382 } 383 384 $pages = []; 385 foreach (array_keys($metadata['references']) as $referencePageId) { 386 $pages[$referencePageId] = MarkupPath::createMarkupFromId($referencePageId); 387 } 388 return $pages; 389 390 } 391 392 393 /** 394 * 395 * @throws ExceptionNotExists - if the path does not exists 396 * @throws ExceptionCast - if the path is not a wiki path which is mandatory for the context 397 */ 398 public function createHtmlFetcherWithItselfAsContextPath(): FetcherMarkup 399 { 400 $path = $this->getPathObject(); 401 return FetcherMarkup::createXhtmlMarkupFetcherFromPath($path, $path->toWikiPath()); 402 } 403 404 /** 405 * @throws ExceptionCompile 406 */ 407 public function getHtmlPath(): LocalPath 408 { 409 410 $fetcher = $this->createHtmlFetcherWithItselfAsContextPath(); 411 return $fetcher->processIfNeededAndGetFetchPath(); 412 413 } 414 415 /** 416 * Set the page quality 417 * @param boolean $value true if this is a low quality page rank false otherwise 418 * @throws ExceptionCompile 419 */ 420 public 421 function setCanBeOfLowQuality(bool $value): MarkupPath 422 { 423 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->canBeOfLowQuality, $value); 424 } 425 426 /** 427 * @return MarkupPath[] the backlinks 428 * Duplicate of related 429 * 430 * Same as {@link WikiPath::getReferencedBy()} ? 431 */ 432 public 433 function getBacklinks(): array 434 { 435 $backlinks = array(); 436 /** 437 * Same as 438 * idx_get_indexer()->lookupKey('relation_references', $ID); 439 */ 440 $ft_backlinks = ft_backlinks($this->getWikiId()); 441 foreach ($ft_backlinks as $backlinkId) { 442 $backlinks[$backlinkId] = MarkupPath::createMarkupFromId($backlinkId); 443 } 444 return $backlinks; 445 } 446 447 448 /** 449 * Low page quality 450 * @return bool true if this is a low quality page 451 */ 452 function isLowQualityPage(): bool 453 { 454 455 456 if (!$this->getCanBeOfLowQuality()) { 457 return false; 458 } 459 460 if (!Site::isLowQualityProtectionEnable()) { 461 return false; 462 } 463 try { 464 return $this->getLowQualityIndicatorCalculated(); 465 } catch (ExceptionNotFound $e) { 466 // We were returning null but null used in a condition is falsy 467 // we return false 468 return false; 469 } 470 471 } 472 473 474 /** 475 * 476 */ 477 public function getCanBeOfLowQuality(): bool 478 { 479 480 return $this->canBeOfLowQuality->getValueOrDefault(); 481 482 } 483 484 485 /** 486 * Return the Title 487 * @deprecated for {@link PageTitle::getValue()} 488 */ 489 public 490 function getTitle(): ?string 491 { 492 return $this->title->getValueFromStore(); 493 } 494 495 /** 496 * If true, the page is quality monitored (a note is shown to the writer) 497 * @return null|bool 498 */ 499 public 500 function getQualityMonitoringIndicator(): ?bool 501 { 502 return $this->qualityMonitoringIndicator->getValueFromStore(); 503 } 504 505 /** 506 * @return string the title, or h1 if empty or the id if empty 507 * Shortcut to {@link PageTitle::getValueOrDefault()} 508 * 509 */ 510 public 511 function getTitleOrDefault(): string 512 { 513 try { 514 return $this->title->getValueOrDefault(); 515 } catch (ExceptionNotFound $e) { 516 LogUtility::internalError("Internal Error: The page ($this) does not have any default title"); 517 return $this->getPathObject()->getLastNameWithoutExtension(); 518 } 519 520 } 521 522 523 public function getH1OrDefault(): string 524 { 525 526 return $this->h1->getValueOrDefault(); 527 528 } 529 530 /** 531 * @return string 532 * @throws ExceptionNotFound 533 */ 534 public 535 function getDescription(): string 536 { 537 return $this->description->getValue(); 538 } 539 540 541 /** 542 * @return string - the description or the dokuwiki generated description 543 */ 544 public 545 function getDescriptionOrElseDokuWiki(): string 546 { 547 return $this->description->getValueOrDefault(); 548 } 549 550 551 /** 552 * @return string 553 * The content / markup that should be parsed by the parser 554 */ 555 public 556 function getMarkup(): string 557 { 558 559 try { 560 return FileSystems::getContent($this->getPathObject()); 561 } catch (ExceptionNotFound $e) { 562 LogUtility::msg("The page ($this) was not found"); 563 return ""; 564 } 565 566 } 567 568 569 public 570 function isInIndex(): bool 571 { 572 $Indexer = idx_get_indexer(); 573 $pages = $Indexer->getPages(); 574 $return = array_search($this->getPathObject()->getWikiId(), $pages, true); 575 return $return !== false; 576 } 577 578 579 /** 580 * Save the content with the {@link ChangeLog} 581 * @param string $content 582 * @param string $summary 583 * @return $this 584 * Use {@link FileSystems::setContent()} if you don't want any log 585 * This function wraps {@link saveWikiText()} it implements the events system and may have side-effects 586 */ 587 public 588 function setContentWithLog(string $content, string $summary = "Default"): MarkupPath 589 { 590 $path = $this->getPathObject(); 591 if (!($path instanceof WikiPath)) { 592 throw new ExceptionRuntime("The path of this markup is not a wiki path"); 593 } 594 saveWikiText($path->getWikiId(), $content, $summary); 595 return $this; 596 } 597 598 public 599 function addToIndex() 600 { 601 /** 602 * Add to index check the metadata cache 603 * Because we log the cache at the requested page level, we need to 604 * set the global ID 605 */ 606 global $ID; 607 $keep = $ID; 608 global $ACT; 609 $keepACT = $ACT; 610 try { 611 $ACT = "show"; 612 $ID = $this->getPathObject()->toWikiPath()->getWikiId(); 613 idx_addPage($ID); 614 } finally { 615 $ID = $keep; 616 $ACT = $keepACT; 617 } 618 return $this; 619 620 } 621 622 /** 623 * @return mixed 624 */ 625 public 626 function getTypeOrDefault() 627 { 628 return $this->type->getValueFromStoreOrDefault(); 629 } 630 631 632 /** 633 * @throws ExceptionNotFound 634 */ 635 public 636 function getFirstImage(): IFetcherLocalImage 637 { 638 try { 639 return IFetcherLocalImage::createImageFetchFromPath(FirstRasterImage::createForPage($this)->getValue()); 640 } catch (ExceptionBadSyntax|ExceptionBadArgument $e) { 641 LogUtility::error("First Raster Image error. Error: " . $e->getMessage(), self::CANONICAL_PAGE, $e); 642 throw new ExceptionNotFound(); 643 } catch (ExceptionNotExists $e) { 644 throw new ExceptionNotFound(); 645 } 646 647 } 648 649 /** 650 * Return the media stored during parsing 651 * 652 * They are saved via the function {@link \Doku_Renderer_metadata::_recordMediaUsage()} 653 * called by the {@link \Doku_Renderer_metadata::internalmedia()} 654 * 655 * 656 * {@link \Doku_Renderer_metadata::externalmedia()} does not save them 657 */ 658 public 659 function getMediasMetadata(): ?array 660 { 661 662 $store = $this->getReadStoreOrDefault(); 663 if (!($store instanceof MetadataDokuWikiStore)) { 664 return null; 665 } 666 $medias = []; 667 668 $relation = $store->getCurrentFromName('relation'); 669 if (isset($relation['media'])) { 670 /** 671 * The relation is 672 * $this->meta['relation']['media'][$src] = $exists; 673 * 674 */ 675 foreach ($relation['media'] as $src => $exists) { 676 if ($exists) { 677 $medias[] = $src; 678 } 679 } 680 } 681 return $medias; 682 } 683 684 685 /** 686 * Get author name 687 * 688 * @return string 689 */ 690 public 691 function getAuthor(): ?string 692 { 693 $store = $this->getReadStoreOrDefault(); 694 if (!($store instanceof MetadataDokuWikiStore)) { 695 return null; 696 } 697 698 return $store->getFromName('creator'); 699 } 700 701 /** 702 * Get author ID 703 * 704 * @return string 705 */ 706 public 707 function getAuthorID(): ?string 708 { 709 710 $store = $this->getReadStoreOrDefault(); 711 if (!($store instanceof MetadataDokuWikiStore)) { 712 return null; 713 } 714 715 return $store->getFromName('user'); 716 717 } 718 719 720 /** 721 * Get the create date of page 722 * 723 * @return DateTime 724 * @throws ExceptionNotFound 725 */ 726 public 727 function getCreatedTime(): ?DateTime 728 { 729 return $this->creationTime->getValue(); 730 } 731 732 733 /** 734 * 735 * @return DateTime 736 */ 737 public 738 function getModifiedTime(): DateTime 739 { 740 return $this->modifiedTime->getValueFromStore(); 741 } 742 743 /** 744 * @throws ExceptionNotFound 745 */ 746 public 747 function getModifiedTimeOrDefault(): DateTime 748 { 749 return $this->modifiedTime->getValueFromStoreOrDefault(); 750 } 751 752 753 /** 754 * Utility class, refresh the metadata (used only in test) 755 * @deprecated if possible used {@link FetcherMarkup} instead 756 */ 757 public function renderMetadataAndFlush(): MarkupPath 758 { 759 760 if (!FileSystems::exists($this)) { 761 if (PluginUtility::isDevOrTest()) { 762 LogUtility::msg("You can't render the metadata of a markup path that does not exist ($this)"); 763 } 764 return $this; 765 } 766 767 try { 768 $wikiPath = $this->getPathObject()->toWikiPath(); 769 FetcherMarkup::confRoot() 770 ->setRequestedContextPath($wikiPath) 771 ->setRequestedExecutingPath($wikiPath) 772 ->setRequestedMimeToMetadata() 773 ->build() 774 ->processMetadataIfNotYetDone(); 775 } catch (ExceptionCast|ExceptionNotExists $e) { 776 // not a wiki path, no meta 777 } 778 779 780 return $this; 781 782 } 783 784 /** 785 * @return string|null 786 * @deprecated for {@link Region} 787 */ 788 public 789 function getLocaleRegion(): ?string 790 { 791 return $this->region->getValueFromStore(); 792 } 793 794 public 795 function getRegionOrDefault() 796 { 797 798 return $this->region->getValueFromStoreOrDefault(); 799 800 } 801 802 public 803 function getLang(): ?string 804 { 805 return $this->lang->getValueFromStore(); 806 } 807 808 public function getLangOrDefault(): string 809 { 810 return $this->lang->getValueOrDefault(); 811 } 812 813 /** 814 * The home page is an index page 815 * Adapted from {@link FsWikiUtility::getHomePagePath()} 816 * @return bool 817 */ 818 public function isIndexPage(): bool 819 { 820 821 $startPageName = Site::getIndexPageName(); 822 try { 823 if ($this->getPathObject()->getLastNameWithoutExtension() === $startPageName) { 824 return true; 825 } 826 } catch (ExceptionNotFound $e) { 827 // ok 828 } 829 830 try { 831 /** 832 * page named like the NS inside the NS 833 * ie ns:ns 834 */ 835 $objectPath = $this->path; 836 $parentPath = $this->path->getParent(); 837 if (!($parentPath instanceof WikiPath)) { 838 return false; 839 } 840 if ($parentPath->getLastNameWithoutExtension() === $objectPath->getLastNameWithoutExtension()) { 841 /** 842 * If the start page does not exists, this is the index page 843 */ 844 $startPage = $parentPath->resolveId($startPageName); 845 if (!FileSystems::exists($startPage)) { 846 return true; 847 } 848 } 849 } catch (ExceptionNotFound $e) { 850 // no parent, no last name, etc 851 } 852 853 return false; 854 } 855 856 857 /** 858 * @throws ExceptionNotFound 859 */ 860 public 861 function getPublishedTime(): DateTime 862 { 863 return $this->publishedDate->getValueFromStore(); 864 } 865 866 /** 867 * @return bool 868 * @deprecated for {@link FileSystems::exists()} 869 */ 870 public function exists(): bool 871 { 872 return FileSystems::exists($this); 873 } 874 875 /** 876 * @return DateTime 877 * @throws ExceptionNotFound 878 */ 879 public 880 function getPublishedElseCreationTime(): DateTime 881 { 882 return $this->publishedDate->getValueFromStoreOrDefault(); 883 } 884 885 886 public 887 function isLatePublication(): bool 888 { 889 try { 890 $dateTime = $this->getPublishedElseCreationTime(); 891 } catch (ExceptionNotFound $e) { 892 return false; 893 } 894 return $dateTime > new DateTime('now'); 895 } 896 897 /** 898 * The unique page Url (also known as Canonical URL) used: 899 * * in the link 900 * * in the canonical ref 901 * * in the site map 902 * @return Url 903 */ 904 public 905 function getCanonicalUrl(): Url 906 { 907 908 /** 909 * Dokuwiki Methodology Taken from {@link tpl_metaheaders()} 910 */ 911 if ($this->isRootHomePage()) { 912 return UrlEndpoint::createBaseUrl()->toAbsoluteUrl(); 913 } 914 915 try { 916 return UrlEndpoint::createDokuUrl() 917 ->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->getWikiId()) 918 ->toAbsoluteUrl(); 919 } catch (ExceptionBadArgument $e) { 920 LogUtility::error("This markup path ($this) can not be accessed externaly"); 921 return UrlEndpoint::createBaseUrl(); 922 } 923 924 925 } 926 927 928 929 /** 930 * 931 * @return string|null - the locale facebook way 932 * @throws ExceptionNotFound 933 * @deprecated for {@link Locale} 934 */ 935 public 936 function getLocale($default = null): ?string 937 { 938 $value = $this->locale->getValueFromStore(); 939 if ($value === null) { 940 return $default; 941 } 942 return $value; 943 } 944 945 946 /** 947 * 948 * @deprecated use a {@link FetcherMarkup::getFetchString()} instead 949 */ 950 public function toXhtml(): string 951 { 952 953 $fetcherMarkup = $this->createHtmlFetcherWithItselfAsContextPath(); 954 return $fetcherMarkup->getFetchString(); 955 956 957 } 958 959 960 public 961 function getHtmlAnchorLink($logicalTag = null): string 962 { 963 $id = $this->getPathObject()->getWikiId(); 964 try { 965 return LinkMarkup::createFromPageIdOrPath($id) 966 ->toAttributes($logicalTag) 967 ->toHtmlEnterTag("a") 968 . $this->getNameOrDefault() 969 . "</a>"; 970 } catch (ExceptionCompile $e) { 971 LogUtility::msg("The markup ref returns an error for the creation of the page anchor html link ($this). Error: {$e->getMessage()}"); 972 return "<a href=\"{$this->getCanonicalUrl()}\" data-wiki-id=\"$id\">{$this->getNameOrDefault()}</a>"; 973 } 974 } 975 976 977 /** 978 * Without the `:` at the end 979 * @return string 980 * @throws ExceptionNotFound 981 * @deprecated / shortcut for {@link WikiPath::getParent()} 982 * Because a page has always a parent, the string is never null. 983 */ 984 public function getNamespacePath(): string 985 { 986 987 return $this->getParent()->toAbsoluteId(); 988 989 } 990 991 992 /** 993 * @return $this 994 * @deprecated use {@link MetadataDokuWikiStore::deleteAndFlush()} 995 */ 996 public 997 function deleteMetadatasAndFlush(): MarkupPath 998 { 999 MetadataDokuWikiStore::getOrCreateFromResource($this) 1000 ->deleteAndFlush(); 1001 return $this; 1002 } 1003 1004 /** 1005 * @throws ExceptionNotFound 1006 */ 1007 public 1008 function getName(): string 1009 { 1010 1011 return $this->pageName->getValue(); 1012 1013 } 1014 1015 public 1016 function getNameOrDefault(): string 1017 { 1018 1019 return ResourceName::createForResource($this)->getValueOrDefault(); 1020 1021 1022 } 1023 1024 /** 1025 * @param $property 1026 */ 1027 public 1028 function unsetMetadata($property) 1029 { 1030 $meta = p_read_metadata($this->getPathObject()->getWikiId()); 1031 if (isset($meta['persistent'][$property])) { 1032 unset($meta['persistent'][$property]); 1033 } 1034 p_save_metadata($this->getPathObject()->getWikiId(), $meta); 1035 1036 } 1037 1038 /** 1039 * @return array - return the standard / generated metadata 1040 * used to create a variable environment (context) in rendering 1041 */ 1042 public 1043 function getMetadataForRendering(): array 1044 { 1045 1046 $metadataNames = [ 1047 PageH1::PROPERTY_NAME, 1048 PageTitle::PROPERTY_NAME, 1049 Lead::PROPERTY_NAME, 1050 Canonical::PROPERTY_NAME, 1051 PagePath::PROPERTY_NAME, 1052 Label::PROPERTY_NAME, 1053 PageDescription::PROPERTY_NAME, 1054 ResourceName::PROPERTY_NAME, 1055 PageType::PROPERTY_NAME, 1056 Slug::PROPERTY_NAME, 1057 PageTemplateName::PROPERTY_NAME, 1058 DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, // Dokuwiki id is deprecated for path 1059 PageLevel::PROPERTY_NAME, 1060 PageKeywords::PROPERTY_NAME 1061 ]; 1062 1063 /** 1064 * The metadata that works only 1065 * if the file exists 1066 */ 1067 if (FileSystems::exists($this)) { 1068 $metadataThatNeedsExistingFile = [ 1069 PageId::PROPERTY_NAME, 1070 CreationDate::PROPERTY_NAME, 1071 ModificationDate::PROPERTY_NAME, 1072 PagePublicationDate::PROPERTY_NAME, 1073 StartDate::PROPERTY_NAME, 1074 EndDate::PROPERTY_NAME, 1075 ]; 1076 $metadataNames = array_merge($metadataNames, $metadataThatNeedsExistingFile); 1077 } 1078 1079 1080 foreach ($metadataNames as $metadataName) { 1081 try { 1082 $metadata = Meta\Api\MetadataSystem::getForName($metadataName); 1083 } catch (ExceptionNotFound $e) { 1084 LogUtility::msg("The metadata ($metadataName) should be defined"); 1085 continue; 1086 } 1087 /** 1088 * The Value or Default is returned 1089 * 1090 * Because the title/h1 should never be null 1091 * otherwise a template link such as [[$path|$title]] will return a link without an description 1092 * and therefore will be not visible 1093 * 1094 * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility} 1095 * If we want the native value, we need to change the pipeline 1096 */ 1097 $value = $metadata 1098 ->setResource($this) 1099 ->setReadStore(MetadataDokuWikiStore::class) 1100 ->setWriteStore(TemplateStore::class) 1101 ->buildFromReadStore() 1102 ->toStoreValueOrDefault(); 1103 $array[$metadataName] = $value; 1104 } 1105 1106 $array["url"] = $this->getCanonicalUrl()->toAbsoluteUrl()->toString(); 1107 $array["now"] = Iso8601Date::createFromNow()->toString(); 1108 return $array; 1109 1110 } 1111 1112 1113 public 1114 function getPublishedTimeAsString(): ?string 1115 { 1116 return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null; 1117 } 1118 1119 1120 /** 1121 * @throws ExceptionNotFound 1122 */ 1123 public 1124 function getEndDate(): DateTime 1125 { 1126 return $this->endDate->getValue(); 1127 } 1128 1129 1130 1131 /** 1132 * @throws ExceptionNotFound 1133 */ 1134 public 1135 function getStartDate(): DateTime 1136 { 1137 return $this->startDate->getValue(); 1138 } 1139 1140 /** 1141 * A page id 1142 * @return string 1143 * @throws ExceptionNotFound - when the page does not exist 1144 */ 1145 public 1146 function getPageId(): string 1147 { 1148 return PageId::createForPage($this)->getValue(); 1149 } 1150 1151 1152 /** 1153 * @throws ExceptionNotExists 1154 */ 1155 public 1156 function fetchAnalyticsDocument(): FetcherMarkup 1157 { 1158 return renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this); 1159 } 1160 1161 /** 1162 * @throws ExceptionCompile 1163 * @throws ExceptionNotExists 1164 */ 1165 public 1166 function fetchAnalyticsPath(): Path 1167 { 1168 $fetcher = renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this); 1169 return $fetcher->processIfNeededAndGetFetchPath(); 1170 1171 } 1172 1173 /** 1174 */ 1175 public 1176 function getDatabasePage(): DatabasePageRow 1177 { 1178 1179 return DatabasePageRow::getFromPageObject($this); 1180 1181 } 1182 1183 /** 1184 * @throws ExceptionSqliteNotAvailable 1185 */ 1186 public 1187 function getOrCreateDatabasePage(): DatabasePageRow 1188 { 1189 1190 return DatabasePageRow::getOrCreateFromPageObject($this); 1191 1192 } 1193 1194 public 1195 function canBeUpdatedByCurrentUser(): bool 1196 { 1197 return Identity::isWriter($this->getWikiId()); 1198 } 1199 1200 1201 public 1202 function isRootHomePage(): bool 1203 { 1204 global $conf; 1205 $startPageName = $conf['start']; 1206 return $this->getPathObject()->toAbsoluteId() === ":$startPageName"; 1207 1208 } 1209 1210 1211 /** 1212 * @throws ExceptionNotFound 1213 */ 1214 public 1215 function getPageType(): string 1216 { 1217 return $this->type->getValueFromStore(); 1218 } 1219 1220 /** 1221 * @throws ExceptionNotFound 1222 * @deprecated 1223 */ 1224 public 1225 function getCanonical(): WikiPath 1226 { 1227 return $this->canonical->getValue(); 1228 } 1229 1230 /** 1231 * Create a canonical from the last page path part. 1232 * 1233 * @return string|null 1234 * @throws ExceptionNotFound 1235 */ 1236 public 1237 function getDefaultCanonical(): ?string 1238 { 1239 return $this->canonical->getDefaultValue(); 1240 } 1241 1242 /** 1243 * @throws ExceptionNotFound 1244 */ 1245 public 1246 function getLayout() 1247 { 1248 return $this->layout->getValueFromStore(); 1249 } 1250 1251 /** 1252 * @throws ExceptionNotFound 1253 */ 1254 public 1255 function getDefaultPageName(): string 1256 { 1257 return $this->pageName->getDefaultValue(); 1258 } 1259 1260 public 1261 function getDefaultTitle(): string 1262 { 1263 return $this->title->getDefaultValue(); 1264 } 1265 1266 /** 1267 * @throws ExceptionNotFound 1268 */ 1269 public 1270 function getDefaultH1() 1271 { 1272 return $this->h1->getValueOrDefault(); 1273 } 1274 1275 public 1276 function getDefaultType(): string 1277 { 1278 return $this->type->getDefaultValue(); 1279 } 1280 1281 public 1282 function getDefaultLayout(): string 1283 { 1284 return $this->layout->getDefaultValue(); 1285 } 1286 1287 1288 /** 1289 * 1290 * @throws ExceptionCompile 1291 */ 1292 public 1293 function setLowQualityIndicatorCalculation($bool): MarkupPath 1294 { 1295 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool); 1296 } 1297 1298 1299 /** 1300 * Change the quality indicator 1301 * and if the quality level has become low 1302 * and that the protection is on, delete the cache 1303 * @param MetadataBoolean $lowQualityAttributeName 1304 * @param bool $value 1305 * @return MarkupPath 1306 * @throws ExceptionBadArgument - if the value cannot be persisted 1307 */ 1308 private 1309 function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): MarkupPath 1310 { 1311 try { 1312 $actualValue = $lowQualityAttributeName->getValue(); 1313 } catch (ExceptionNotFound $e) { 1314 $actualValue = null; 1315 } 1316 if ($value !== $actualValue) { 1317 $lowQualityAttributeName 1318 ->setValue($value) 1319 ->persist(); 1320 } 1321 return $this; 1322 } 1323 1324 1325 /** 1326 * @throws ExceptionNotFound 1327 */ 1328 public 1329 function getLowQualityIndicatorCalculated() 1330 { 1331 1332 return $this->lowQualityIndicatorCalculated->getValueOrDefault(); 1333 1334 } 1335 1336 /** 1337 * @return PageImage[] 1338 * @deprecated 1339 */ 1340 public 1341 function getPageMetadataImages(): array 1342 { 1343 return $this->pageImages->getValueAsPageImages(); 1344 } 1345 1346 1347 /** 1348 * @param array|string $jsonLd 1349 * @return $this 1350 * @throws ExceptionCompile 1351 * @deprecated for {@link LdJson} 1352 */ 1353 public 1354 function setJsonLd($jsonLd): MarkupPath 1355 { 1356 $this->ldJson 1357 ->setValue($jsonLd) 1358 ->sendToWriteStore(); 1359 return $this; 1360 } 1361 1362 /** 1363 * @throws ExceptionCompile 1364 */ 1365 public 1366 function setPageType(string $value): MarkupPath 1367 { 1368 $this->type 1369 ->setValue($value) 1370 ->sendToWriteStore(); 1371 return $this; 1372 } 1373 1374 1375 /** 1376 * @param $aliasPath 1377 * @param string $aliasType 1378 * @return Alias 1379 * @deprecated for {@link Aliases} 1380 */ 1381 public 1382 function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias 1383 { 1384 1385 return $this->aliases->addAndGetAlias($aliasPath, $aliasType); 1386 1387 } 1388 1389 1390 /** 1391 * @return Alias[] 1392 * @throws ExceptionNotFound 1393 */ 1394 public 1395 function getAliases(): array 1396 { 1397 return $this->aliases->getValueAsAlias(); 1398 } 1399 1400 /** 1401 * @return string 1402 */ 1403 public 1404 function getSlugOrDefault(): string 1405 { 1406 try { 1407 return $this->getSlug(); 1408 } catch (ExceptionNotFound $e) { 1409 return $this->getDefaultSlug(); 1410 } 1411 1412 } 1413 1414 /** 1415 * 1416 * @return string 1417 * 1418 */ 1419 public 1420 function getDefaultSlug(): string 1421 { 1422 return $this->slug->getDefaultValue(); 1423 } 1424 1425 /** 1426 * The parent page is the parent in the page tree directory 1427 * 1428 * If the page is at the root, the parent page is the root home 1429 * Only the root home does not have any parent page and return null. 1430 * 1431 * @return MarkupPath 1432 * @throws ExceptionNotFound 1433 */ 1434 public function getParent(): MarkupPath 1435 { 1436 1437 $names = $this->getNames(); 1438 if (sizeof($names) == 0) { 1439 throw new ExceptionNotFound("No parent page"); 1440 } 1441 $slice = 1; 1442 if ($this->isIndexPage()) { 1443 /** 1444 * The parent of a home page 1445 * is in the parent directory 1446 */ 1447 $slice = 2; 1448 } 1449 /** 1450 * Delete the last or the two last parts 1451 */ 1452 if (sizeof($names) < $slice) { 1453 throw new ExceptionNotFound("No parent page"); 1454 } 1455 /** 1456 * Get the actual directory for a page 1457 * or the parent directory for a home page 1458 */ 1459 $parentNames = array_slice($names, 0, sizeof($names) - $slice); 1460 /** 1461 * Create the parent namespace id 1462 */ 1463 $parentNamespaceId = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $parentNames) . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT; 1464 try { 1465 return self::getIndexPageFromNamespace($parentNamespaceId); 1466 } catch (ExceptionBadSyntax $e) { 1467 $message = "Error on getParentPage, null returned - Error: {$e->getMessage()}"; 1468 LogUtility::internalError($message); 1469 throw new ExceptionNotFound($message); 1470 } 1471 1472 } 1473 1474 /** 1475 * @throws ExceptionCompile 1476 */ 1477 public 1478 function setDescription($description): MarkupPath 1479 { 1480 1481 $this->description 1482 ->setValue($description) 1483 ->sendToWriteStore(); 1484 return $this; 1485 } 1486 1487 /** 1488 * @throws ExceptionCompile 1489 * @deprecated uses {@link EndDate} instead 1490 */ 1491 public 1492 function setEndDate($value): MarkupPath 1493 { 1494 $this->endDate 1495 ->setFromStoreValue($value) 1496 ->sendToWriteStore(); 1497 return $this; 1498 } 1499 1500 /** 1501 * @throws ExceptionCompile 1502 * @deprecated uses {@link StartDate} instead 1503 */ 1504 public 1505 function setStartDate($value): MarkupPath 1506 { 1507 $this->startDate 1508 ->setFromStoreValue($value) 1509 ->sendToWriteStore(); 1510 return $this; 1511 } 1512 1513 /** 1514 * @throws ExceptionCompile 1515 */ 1516 public 1517 function setPublishedDate($value): MarkupPath 1518 { 1519 $this->publishedDate 1520 ->setFromStoreValue($value) 1521 ->sendToWriteStore(); 1522 return $this; 1523 } 1524 1525 /** 1526 * Utility to {@link ResourceName::setValue()} 1527 * Used mostly to create page in test 1528 * @throws ExceptionCompile 1529 * @deprecated use not persist 1530 */ 1531 public 1532 function setPageName($value): MarkupPath 1533 { 1534 $this->pageName 1535 ->setValue($value) 1536 ->sendToWriteStore(); 1537 return $this; 1538 } 1539 1540 1541 /** 1542 * @throws ExceptionCompile 1543 */ 1544 public 1545 function setTitle($value): MarkupPath 1546 { 1547 $this->title 1548 ->setValue($value) 1549 ->sendToWriteStore(); 1550 return $this; 1551 } 1552 1553 /** 1554 * @throws ExceptionCompile 1555 */ 1556 public 1557 function setH1($value): MarkupPath 1558 { 1559 $this->h1 1560 ->setValue($value) 1561 ->sendToWriteStore(); 1562 return $this; 1563 } 1564 1565 /** 1566 * @throws Exception 1567 */ 1568 public 1569 function setRegion($value): MarkupPath 1570 { 1571 $this->region 1572 ->setFromStoreValue($value) 1573 ->sendToWriteStore(); 1574 return $this; 1575 } 1576 1577 /** 1578 * @throws ExceptionCompile 1579 */ 1580 public 1581 function setLang($value): MarkupPath 1582 { 1583 1584 $this->lang 1585 ->setFromStoreValue($value) 1586 ->sendToWriteStore(); 1587 return $this; 1588 } 1589 1590 /** 1591 * @throws ExceptionCompile 1592 */ 1593 public 1594 function setLayout($value): MarkupPath 1595 { 1596 $this->layout 1597 ->setValue($value) 1598 ->sendToWriteStore(); 1599 return $this; 1600 } 1601 1602 1603 /** 1604 * 1605 * We manage the properties by setter and getter 1606 * 1607 * Why ? 1608 * * Because we can capture the updates 1609 * * Because setter are the entry point to good quality data 1610 * * Because dokuwiki may cache the metadata (see below) 1611 * 1612 * Note all properties have been migrated 1613 * but they should be initialized below 1614 * 1615 * Dokuwiki cache: the data may be cached without our consent 1616 * The method {@link p_get_metadata()} does it with this logic 1617 * ``` 1618 * $cache = ($ID == $id); 1619 * $meta = p_read_metadata($id, $cache); 1620 * ``` 1621 */ 1622 private 1623 function buildPropertiesFromFileSystem() 1624 { 1625 1626 /** 1627 * New meta system 1628 * Even if it does not exist, the metadata object should be instantiated 1629 * otherwise, there is a null exception 1630 */ 1631 $this->cacheExpirationDate = CacheExpirationDate::createForPage($this); 1632 $this->aliases = Aliases::createForPage($this); 1633 $this->pageImages = PageImages::createForPage($this); 1634 $this->pageName = ResourceName::createForResource($this); 1635 $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this); 1636 $this->ldJson = LdJson::createForPage($this); 1637 $this->canonical = Canonical::createForPage($this); 1638 $this->description = PageDescription::createForPage($this); 1639 $this->h1 = PageH1::createForPage($this); 1640 $this->type = PageType::createForPage($this); 1641 $this->creationTime = CreationDate::createForPage($this); 1642 $this->title = PageTitle::createForMarkup($this); 1643 $this->keywords = PageKeywords::createForPage($this); 1644 $this->publishedDate = PagePublicationDate::createFromPage($this); 1645 $this->startDate = StartDate::createFromPage($this); 1646 $this->endDate = EndDate::createFromPage($this); 1647 $this->locale = Locale::createForPage($this); 1648 $this->lang = Lang::createForMarkup($this); 1649 $this->region = Region::createForPage($this); 1650 $this->slug = \ComboStrap\Slug::createForPage($this); 1651 $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this); 1652 $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this); 1653 $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this); 1654 $this->modifiedTime = ModificationDate::createForPage($this); 1655 $this->pageUrlPath = PageUrlPath::createForPage($this); 1656 $this->layout = PageTemplateName::createFromPage($this); 1657 1658 } 1659 1660 1661 /** 1662 * @throws ExceptionNotFound 1663 */ 1664 function getPageIdAbbr() 1665 { 1666 1667 return PageId::getAbbreviated($this->getPageId()); 1668 1669 } 1670 1671 public 1672 function setDatabasePage(DatabasePageRow $databasePage): MarkupPath 1673 { 1674 $this->databasePage = $databasePage; 1675 return $this; 1676 } 1677 1678 1679 /** 1680 * @return string|null 1681 * 1682 * @throws ExceptionNotFound 1683 */ 1684 public 1685 function getSlug(): string 1686 { 1687 return $this->slug->getValue(); 1688 } 1689 1690 1691 /** 1692 * @throws ExceptionCompile 1693 */ 1694 public 1695 function setSlug($slug): MarkupPath 1696 { 1697 $this->slug 1698 ->setFromStoreValue($slug) 1699 ->sendToWriteStore(); 1700 return $this; 1701 } 1702 1703 1704 /** 1705 * @return string - the id in the Url 1706 */ 1707 public function getUrlId(): string 1708 { 1709 return $this->pageUrlPath->getValueOrDefaultAsWikiId(); 1710 } 1711 1712 1713 /** 1714 * @throws ExceptionCompile 1715 */ 1716 public 1717 function setQualityMonitoringIndicator($boolean): MarkupPath 1718 { 1719 $this->qualityMonitoringIndicator 1720 ->setFromStoreValue($boolean) 1721 ->sendToWriteStore(); 1722 return $this; 1723 } 1724 1725 /** 1726 * 1727 * @param $aliasPath - third information - the alias used to build this page 1728 */ 1729 public 1730 function setBuildAliasPath($aliasPath) 1731 { 1732 $this->buildAliasPath = $aliasPath; 1733 } 1734 1735 public 1736 function getBuildAlias(): ?Alias 1737 { 1738 if ($this->buildAliasPath === null) return null; 1739 try { 1740 $aliases = $this->getAliases(); 1741 } catch (ExceptionNotFound $e) { 1742 // should not 1743 return null; 1744 } 1745 foreach ($aliases as $alias) { 1746 if ($alias->getPath() === $this->buildAliasPath) { 1747 return $alias; 1748 } 1749 } 1750 return null; 1751 } 1752 1753 public 1754 function isDynamicQualityMonitored(): bool 1755 { 1756 if ($this->getQualityMonitoringIndicator() !== null) { 1757 return $this->getQualityMonitoringIndicator(); 1758 } 1759 return $this->getDefaultQualityMonitoring(); 1760 } 1761 1762 public 1763 function getDefaultQualityMonitoring(): bool 1764 { 1765 if (SiteConfig::getConfValue(QualityMessageHandler::CONF_DISABLE_QUALITY_MONITORING) === 1) { 1766 return false; 1767 } else { 1768 return true; 1769 } 1770 } 1771 1772 /** 1773 * @param MetadataStore|string $store 1774 * @return $this 1775 */ 1776 public 1777 function setReadStore($store): MarkupPath 1778 { 1779 $this->readStore = $store; 1780 return $this; 1781 } 1782 1783 1784 /** 1785 * @param array $usages 1786 * @return IFetcherLocalImage[] 1787 */ 1788 public 1789 function getImagesForTheFollowingUsages(array $usages): array 1790 { 1791 $usages = array_merge($usages, [PageImageUsage::ALL]); 1792 $images = []; 1793 foreach ($this->getPageMetadataImages() as $pageImage) { 1794 foreach ($usages as $usage) { 1795 if (in_array($usage, $pageImage->getUsages())) { 1796 $path = $pageImage->getImagePath(); 1797 try { 1798 $images[] = IFetcherLocalImage::createImageFetchFromPath($path); 1799 } catch (ExceptionBadArgument $e) { 1800 LogUtility::error(`The page image $path of the page $this is not an image`); 1801 } catch (ExceptionBadSyntax $e) { 1802 LogUtility::error(`The page image $path has a bad syntax`); 1803 } catch (ExceptionNotExists $e) { 1804 LogUtility::error(`The page image $path does not exists`); 1805 } 1806 continue 2; 1807 } 1808 } 1809 } 1810 return $images; 1811 1812 } 1813 1814 1815 /** 1816 * @throws ExceptionNotFound 1817 */ 1818 public 1819 function getKeywords(): array 1820 { 1821 return $this->keywords->getValue(); 1822 } 1823 1824 /** 1825 * @throws ExceptionNotFound 1826 */ 1827 public function getKeywordsOrDefault(): array 1828 { 1829 return $this->keywords->getValueOrDefaults(); 1830 } 1831 1832 1833 /** 1834 * @throws ExceptionCompile 1835 */ 1836 public 1837 function setKeywords($value): MarkupPath 1838 { 1839 $this->keywords 1840 ->setFromStoreValue($value) 1841 ->sendToWriteStore(); 1842 return $this; 1843 } 1844 1845 /** 1846 * @return DateTime|null 1847 * @throws ExceptionNotFound 1848 * @deprecated for {@link CacheExpirationDate} 1849 */ 1850 public 1851 function getCacheExpirationDate(): ?DateTime 1852 { 1853 return $this->cacheExpirationDate->getValue(); 1854 } 1855 1856 /** 1857 * @return DateTime|null 1858 * @throws ExceptionNotFound 1859 * @deprecated for {@link CacheExpirationDate} 1860 */ 1861 public 1862 function getDefaultCacheExpirationDate(): ?DateTime 1863 { 1864 return $this->cacheExpirationDate->getDefaultValue(); 1865 } 1866 1867 /** 1868 * @return string|null 1869 * @throws ExceptionNotFound 1870 * @deprecated for {@link CacheExpirationFrequency} 1871 */ 1872 public 1873 function getCacheExpirationFrequency(): string 1874 { 1875 return $this->cacheExpirationFrequency->getValue(); 1876 } 1877 1878 1879 /** 1880 * @param DateTime $cacheExpirationDate 1881 * @return $this 1882 * @deprecated for {@link CacheExpirationDate} 1883 */ 1884 public 1885 function setCacheExpirationDate(DateTime $cacheExpirationDate): MarkupPath 1886 { 1887 $this->cacheExpirationDate->setValue($cacheExpirationDate); 1888 return $this; 1889 } 1890 1891 1892 /** 1893 * Utility class 1894 * Get the instructions document as if it was the main page. 1895 * Ie the context path is: 1896 * * the markup path itself) 1897 * * or the default context path if the path cannot be transformed as wiki path. 1898 */ 1899 public function getInstructionsDocument(): FetcherMarkup 1900 { 1901 1902 $path = $this->getPathObject(); 1903 try { 1904 $contextPath = $path->toWikiPath(); 1905 } catch (ExceptionCast $e) { 1906 $contextPath = ExecutionContext::getActualOrCreateFromEnv() 1907 ->getDefaultContextPath(); 1908 } 1909 return FetcherMarkup::confRoot() 1910 ->setRequestedExecutingPath($path) 1911 ->setRequestedContextPath($contextPath) 1912 ->setRequestedMimeToInstructions() 1913 ->build(); 1914 1915 } 1916 1917 public 1918 function delete() 1919 { 1920 1921 Index::getOrCreate()->deletePage($this); 1922 saveWikiText($this->getWikiId(), "", "Delete"); 1923 1924 } 1925 1926 /** 1927 * @return Url -the absolute canonical url 1928 */ 1929 public 1930 function getAbsoluteCanonicalUrl(): Url 1931 { 1932 return $this->getCanonicalUrl()->toAbsoluteUrl(); 1933 } 1934 1935 1936 public 1937 function getReadStoreOrDefault(): MetadataStore 1938 { 1939 if ($this->readStore === null) { 1940 /** 1941 * No cache please if not set 1942 * Cache should be in the MetadataDokuWikiStore 1943 * that is page requested scoped and not by slot 1944 */ 1945 return MetadataDokuWikiStore::getOrCreateFromResource($this); 1946 } 1947 if (!($this->readStore instanceof MetadataStore)) { 1948 $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this); 1949 } 1950 return $this->readStore; 1951 } 1952 1953 /** 1954 * @return Path 1955 * A markup path wraps a path 1956 */ 1957 public function getPathObject(): Path 1958 { 1959 return $this->path; 1960 } 1961 1962 1963 /** 1964 * A shortcut for {@link MarkupPath::getPathObject()::getDokuwikiId()} 1965 * 1966 * @throws ExceptionBadArgument - if the markup path is not a {@link WikiPath} 1967 */ 1968 public 1969 function getWikiId(): string 1970 { 1971 $path = $this->getPathObject(); 1972 return WikiPath::createFromPathObject($path)->getWikiId(); 1973 } 1974 1975 public 1976 function getUid(): Metadata 1977 { 1978 return PageId::createForPage($this); 1979 } 1980 1981 1982 public 1983 function getAbsolutePath(): string 1984 { 1985 return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId(); 1986 } 1987 1988 /** 1989 * Todo, it should be a property of the markup not every markup file are main page markup. 1990 * @return string 1991 */ 1992 function getType(): string 1993 { 1994 return self::TYPE; 1995 } 1996 1997 /** 1998 * @return PageUrlPath 1999 * @deprecated use {@link PageUrlPath} instead 2000 */ 2001 public 2002 function getUrlPathObject(): PageUrlPath 2003 { 2004 return $this->pageUrlPath; 2005 } 2006 2007 2008 public function getSideSlot(): ?MarkupPath 2009 { 2010 2011 /** 2012 * Only primary slot have a side slot 2013 * Root Home page does not have one either 2014 */ 2015 if ($this->isSlot()) { 2016 return null; 2017 } 2018 2019 $nearestMainFooter = $this->findNearest(SlotSystem::getSidebarName()); 2020 if ($nearestMainFooter === false) { 2021 return null; 2022 } 2023 return MarkupPath::createMarkupFromId($nearestMainFooter); 2024 2025 2026 } 2027 2028 /** 2029 * @param $pageName 2030 * @return false|string 2031 */ 2032 private function findNearest($pageName) 2033 { 2034 global $ID; 2035 $keep = $ID; 2036 try { 2037 $ID = $this->getWikiId(); 2038 return page_findnearest($pageName); 2039 } finally { 2040 $ID = $keep; 2041 } 2042 2043 } 2044 2045 /** 2046 * The slots that are independent from the primary slot 2047 * 2048 * @return MarkupPath[] 2049 * @deprecated should be {@link TemplateForWebPage} based 2050 */ 2051 public function getPrimaryIndependentSlots(): array 2052 { 2053 $secondarySlots = []; 2054 $sideSlot = $this->getSideSlot(); 2055 if ($sideSlot !== null) { 2056 $secondarySlots[] = $sideSlot; 2057 } 2058 return $secondarySlots; 2059 } 2060 2061 2062 public function isHidden(): bool 2063 { 2064 return isHiddenPage($this->getWikiId()); 2065 } 2066 2067 2068 public function getPrimaryHeaderPage(): ?MarkupPath 2069 { 2070 $nearest = page_findnearest(SlotSystem::getMainHeaderSlotName()); 2071 if ($nearest === false) { 2072 return null; 2073 } 2074 return MarkupPath::createMarkupFromId($nearest); 2075 } 2076 2077 public function createPageFetcherHtml(): FetcherPage 2078 { 2079 return FetcherPage::createPageFetcherFromMarkupPath($this); 2080 } 2081 2082 public function getHttpResponse(): HttpResponse 2083 { 2084 return HttpRequest::fetchXhtmlPageResponse($this->getWikiId()); 2085 } 2086 2087 /** 2088 * @return Outline 2089 * @deprecated uses {@link FetcherMarkup::getOutline()} instead 2090 */ 2091 public function getOutline(): Outline 2092 { 2093 2094 return $this->getInstructionsDocument()->getOutline(); 2095 2096 } 2097 2098 2099 public function persistToDefaultMetaStore(): MarkupPath 2100 { 2101 $this->getReadStoreOrDefault()->persist(); 2102 return $this; 2103 } 2104 2105 public function getInstructionsPath(): LocalPath 2106 { 2107 2108 $instructionsDocument = $this->getInstructionsDocument(); 2109 return $instructionsDocument->getInstructionsPath(); 2110 2111 } 2112 2113 public function setContent(string $textContent): MarkupPath 2114 { 2115 FileSystems::setContent($this, $textContent); 2116 return $this; 2117 } 2118 2119 /** 2120 * @throws ExceptionNotExists - if the path does not exist 2121 */ 2122 public function createHtmlFetcherWithRequestedPathAsContextPath(): FetcherMarkup 2123 { 2124 $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 2125 $executingPath = $this->getPathObject(); 2126 $requestedPath = $executionContext->getRequestedPath(); 2127 $requestedMarkupPath = MarkupPath::createPageFromPathObject($requestedPath); 2128 2129 if ($requestedMarkupPath->isSlot()) { 2130 try { 2131 $markupContextPath = SlotSystem::getContextPath(); 2132 SlotSystem::sendContextPathMessage($markupContextPath); 2133 $requestedPath = $markupContextPath->toWikiPath(); 2134 } catch (\Exception $e) { 2135 // should not 2136 } 2137 } 2138 return FetcherMarkup::confRoot() 2139 ->setRequestedMimeToXhtml() 2140 ->setRequestedContextPath($requestedPath) 2141 ->setRequestedExecutingPath($executingPath) 2142 ->build(); 2143 } 2144 2145 public 2146 function isRootItemPage(): bool 2147 { 2148 try { 2149 if ($this->isIndexPage()) { 2150 return false; 2151 } 2152 $parent = $this->getParent(); 2153 if ($parent->isRootHomePage()) { 2154 return true; 2155 } 2156 return false; 2157 } catch (ExceptionNotFound $e) { 2158 return false; 2159 } 2160 } 2161 2162 private 2163 function getPrimaryFooterPage(): ?MarkupPath 2164 { 2165 $nearest = page_findnearest(SlotSystem::getMainFooterSlotName()); 2166 if ($nearest === false) { 2167 return null; 2168 } 2169 return MarkupPath::createMarkupFromId($nearest); 2170 } 2171 2172 /** 2173 * Set the page path to an index page for a directory path 2174 * @return void 2175 */ 2176 private 2177 function setCorrectPathForDirectoryToIndexPage(): void 2178 { 2179 2180 2181 if (!($this->path instanceof WikiPath)) { 2182 return; 2183 } 2184 /** 2185 * @var $path WikiPath 2186 */ 2187 $path = $this->path; 2188 2189 /** 2190 * We correct the path 2191 * We don't return a page because it does not work in a constructor 2192 */ 2193 $startPageName = Site::getIndexPageName(); 2194 $indexPath = $path->resolveId($startPageName); 2195 if (FileSystems::exists($indexPath)) { 2196 // start page inside namespace 2197 $this->path = $indexPath; 2198 return; 2199 } 2200 2201 // page named like the NS inside the NS 2202 try { 2203 $parentName = $this->getLastNameWithoutExtension(); 2204 $nsInsideNsIndex = $this->path->resolveId($parentName); 2205 if (FileSystems::exists($nsInsideNsIndex)) { 2206 $this->path = $nsInsideNsIndex; 2207 return; 2208 } 2209 } catch (ExceptionNotFound $e) { 2210 // no last name 2211 } 2212 2213 // We don't support the child page 2214 // Does not exist but can be used by hierarchical function 2215 $this->path = $indexPath; 2216 } 2217 2218 2219 public 2220 function getUidObject(): Metadata 2221 { 2222 if ($this->uidObject === null) { 2223 try { 2224 $this->uidObject = Meta\Api\MetadataSystem::toMetadataObject($this->getUid()) 2225 ->setResource($this); 2226 } catch (ExceptionBadArgument $e) { 2227 throw new ExceptionRuntimeInternal("Uid object is a metadata object. It should not happen.", self::CANONICAL_PAGE, 1, $e); 2228 } 2229 } 2230 2231 return $this->uidObject; 2232 } 2233 2234 function getExtension(): string 2235 { 2236 return $this->path->getExtension(); 2237 } 2238 2239 function getLastNameWithoutExtension(): string 2240 { 2241 return $this->path->getLastNameWithoutExtension(); 2242 } 2243 2244 function getScheme(): string 2245 { 2246 return MarkupFileSystem::SCHEME; 2247 } 2248 2249 function getLastName(): string 2250 { 2251 return $this->path->getLastName(); 2252 } 2253 2254 function getNames() 2255 { 2256 return $this->path->getNames(); 2257 } 2258 2259 function toAbsoluteId(): string 2260 { 2261 return $this->path->toAbsoluteId(); 2262 } 2263 2264 function toUriString(): string 2265 { 2266 return $this->path->toUriString(); 2267 } 2268 2269 function toAbsolutePath(): Path 2270 { 2271 return $this->path->toAbsolutePath(); 2272 } 2273 2274 2275 function resolve(string $name): Path 2276 { 2277 return $this->path->resolve($name); 2278 } 2279 2280 2281 function getUrl(): Url 2282 { 2283 return FetcherPage::createPageFetcherFromMarkupPath($this) 2284 ->getFetchUrl(); 2285 } 2286 2287 function getHost(): string 2288 { 2289 return $this->path->getHost(); 2290 } 2291 2292 public 2293 function __toString(): string 2294 { 2295 return $this->path->__toString(); 2296 } 2297 2298 /** 2299 * @throws ExceptionBadSyntax 2300 * @throws ExceptionBadArgument 2301 */ 2302 public 2303 static function createFromUri(string $uri): MarkupPath 2304 { 2305 $path = FileSystems::createPathFromUri($uri); 2306 return new MarkupPath($path); 2307 } 2308 2309 2310} 2311