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(); 913 } 914 915 try { 916 return UrlEndpoint::createDokuUrl() 917 ->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->getWikiId()); 918 } catch (ExceptionBadArgument $e) { 919 LogUtility::error("This markup path ($this) can not be accessed externaly"); 920 return UrlEndpoint::createBaseUrl(); 921 } 922 923 924 } 925 926 927 928 /** 929 * 930 * @return string|null - the locale facebook way 931 * @throws ExceptionNotFound 932 * @deprecated for {@link Locale} 933 */ 934 public 935 function getLocale($default = null): ?string 936 { 937 $value = $this->locale->getValueFromStore(); 938 if ($value === null) { 939 return $default; 940 } 941 return $value; 942 } 943 944 945 /** 946 * 947 * @deprecated use a {@link FetcherMarkup::getFetchString()} instead 948 */ 949 public function toXhtml(): string 950 { 951 952 $fetcherMarkup = $this->createHtmlFetcherWithItselfAsContextPath(); 953 return $fetcherMarkup->getFetchString(); 954 955 956 } 957 958 959 public 960 function getHtmlAnchorLink($logicalTag = null): string 961 { 962 $id = $this->getPathObject()->getWikiId(); 963 try { 964 return LinkMarkup::createFromPageIdOrPath($id) 965 ->toAttributes($logicalTag) 966 ->toHtmlEnterTag("a") 967 . $this->getNameOrDefault() 968 . "</a>"; 969 } catch (ExceptionCompile $e) { 970 LogUtility::msg("The markup ref returns an error for the creation of the page anchor html link ($this). Error: {$e->getMessage()}"); 971 return "<a href=\"{$this->getCanonicalUrl()}\" data-wiki-id=\"$id\">{$this->getNameOrDefault()}</a>"; 972 } 973 } 974 975 976 /** 977 * Without the `:` at the end 978 * @return string 979 * @throws ExceptionNotFound 980 * @deprecated / shortcut for {@link WikiPath::getParent()} 981 * Because a page has always a parent, the string is never null. 982 */ 983 public function getNamespacePath(): string 984 { 985 986 return $this->getParent()->toAbsoluteId(); 987 988 } 989 990 991 /** 992 * @return $this 993 * @deprecated use {@link MetadataDokuWikiStore::deleteAndFlush()} 994 */ 995 public 996 function deleteMetadatasAndFlush(): MarkupPath 997 { 998 MetadataDokuWikiStore::getOrCreateFromResource($this) 999 ->deleteAndFlush(); 1000 return $this; 1001 } 1002 1003 /** 1004 * @throws ExceptionNotFound 1005 */ 1006 public 1007 function getName(): string 1008 { 1009 1010 return $this->pageName->getValue(); 1011 1012 } 1013 1014 public 1015 function getNameOrDefault(): string 1016 { 1017 1018 return ResourceName::createForResource($this)->getValueOrDefault(); 1019 1020 1021 } 1022 1023 /** 1024 * @param $property 1025 */ 1026 public 1027 function unsetMetadata($property) 1028 { 1029 $meta = p_read_metadata($this->getPathObject()->getWikiId()); 1030 if (isset($meta['persistent'][$property])) { 1031 unset($meta['persistent'][$property]); 1032 } 1033 p_save_metadata($this->getPathObject()->getWikiId(), $meta); 1034 1035 } 1036 1037 /** 1038 * @return array - return the standard / generated metadata 1039 * used to create a variable environment (context) in rendering 1040 */ 1041 public 1042 function getMetadataForRendering(): array 1043 { 1044 1045 $metadataNames = [ 1046 PageH1::PROPERTY_NAME, 1047 PageTitle::PROPERTY_NAME, 1048 Lead::PROPERTY_NAME, 1049 Canonical::PROPERTY_NAME, 1050 PagePath::PROPERTY_NAME, 1051 Label::PROPERTY_NAME, 1052 PageDescription::PROPERTY_NAME, 1053 ResourceName::PROPERTY_NAME, 1054 PageType::PROPERTY_NAME, 1055 Slug::PROPERTY_NAME, 1056 PageTemplateName::PROPERTY_NAME, 1057 DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, // Dokuwiki id is deprecated for path 1058 PageLevel::PROPERTY_NAME, 1059 PageKeywords::PROPERTY_NAME 1060 ]; 1061 1062 /** 1063 * The metadata that works only 1064 * if the file exists 1065 */ 1066 if (FileSystems::exists($this)) { 1067 $metadataThatNeedsExistingFile = [ 1068 PageId::PROPERTY_NAME, 1069 CreationDate::PROPERTY_NAME, 1070 ModificationDate::PROPERTY_NAME, 1071 PagePublicationDate::PROPERTY_NAME, 1072 StartDate::PROPERTY_NAME, 1073 EndDate::PROPERTY_NAME, 1074 ]; 1075 $metadataNames = array_merge($metadataNames, $metadataThatNeedsExistingFile); 1076 } 1077 1078 1079 foreach ($metadataNames as $metadataName) { 1080 try { 1081 $metadata = Meta\Api\MetadataSystem::getForName($metadataName); 1082 } catch (ExceptionNotFound $e) { 1083 LogUtility::msg("The metadata ($metadataName) should be defined"); 1084 continue; 1085 } 1086 /** 1087 * The Value or Default is returned 1088 * 1089 * Because the title/h1 should never be null 1090 * otherwise a template link such as [[$path|$title]] will return a link without an description 1091 * and therefore will be not visible 1092 * 1093 * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility} 1094 * If we want the native value, we need to change the pipeline 1095 */ 1096 $value = $metadata 1097 ->setResource($this) 1098 ->setReadStore(MetadataDokuWikiStore::class) 1099 ->setWriteStore(TemplateStore::class) 1100 ->buildFromReadStore() 1101 ->toStoreValueOrDefault(); 1102 $array[$metadataName] = $value; 1103 } 1104 1105 $array["url"] = $this->getCanonicalUrl()->toAbsoluteUrl()->toString(); 1106 $array["now"] = Iso8601Date::createFromNow()->toString(); 1107 return $array; 1108 1109 } 1110 1111 1112 public 1113 function getPublishedTimeAsString(): ?string 1114 { 1115 return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null; 1116 } 1117 1118 1119 /** 1120 * @throws ExceptionNotFound 1121 */ 1122 public 1123 function getEndDate(): DateTime 1124 { 1125 return $this->endDate->getValue(); 1126 } 1127 1128 1129 1130 /** 1131 * @throws ExceptionNotFound 1132 */ 1133 public 1134 function getStartDate(): DateTime 1135 { 1136 return $this->startDate->getValue(); 1137 } 1138 1139 /** 1140 * A page id 1141 * @return string 1142 * @throws ExceptionNotFound - when the page does not exist 1143 */ 1144 public 1145 function getPageId(): string 1146 { 1147 return PageId::createForPage($this)->getValue(); 1148 } 1149 1150 1151 /** 1152 * @throws ExceptionNotExists 1153 */ 1154 public 1155 function fetchAnalyticsDocument(): FetcherMarkup 1156 { 1157 return renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this); 1158 } 1159 1160 /** 1161 * @throws ExceptionCompile 1162 * @throws ExceptionNotExists 1163 */ 1164 public 1165 function fetchAnalyticsPath(): Path 1166 { 1167 $fetcher = renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this); 1168 return $fetcher->processIfNeededAndGetFetchPath(); 1169 1170 } 1171 1172 /** 1173 */ 1174 public 1175 function getDatabasePage(): DatabasePageRow 1176 { 1177 1178 return DatabasePageRow::getFromPageObject($this); 1179 1180 } 1181 1182 /** 1183 * @throws ExceptionSqliteNotAvailable 1184 */ 1185 public 1186 function getOrCreateDatabasePage(): DatabasePageRow 1187 { 1188 1189 return DatabasePageRow::getOrCreateFromPageObject($this); 1190 1191 } 1192 1193 public 1194 function canBeUpdatedByCurrentUser(): bool 1195 { 1196 return Identity::isWriter($this->getWikiId()); 1197 } 1198 1199 1200 public 1201 function isRootHomePage(): bool 1202 { 1203 global $conf; 1204 $startPageName = $conf['start']; 1205 return $this->getPathObject()->toAbsoluteId() === ":$startPageName"; 1206 1207 } 1208 1209 1210 /** 1211 * @throws ExceptionNotFound 1212 */ 1213 public 1214 function getPageType(): string 1215 { 1216 return $this->type->getValueFromStore(); 1217 } 1218 1219 /** 1220 * @throws ExceptionNotFound 1221 * @deprecated 1222 */ 1223 public 1224 function getCanonical(): WikiPath 1225 { 1226 return $this->canonical->getValue(); 1227 } 1228 1229 /** 1230 * Create a canonical from the last page path part. 1231 * 1232 * @return string|null 1233 * @throws ExceptionNotFound 1234 */ 1235 public 1236 function getDefaultCanonical(): ?string 1237 { 1238 return $this->canonical->getDefaultValue(); 1239 } 1240 1241 /** 1242 * @throws ExceptionNotFound 1243 */ 1244 public 1245 function getLayout() 1246 { 1247 return $this->layout->getValueFromStore(); 1248 } 1249 1250 /** 1251 * @throws ExceptionNotFound 1252 */ 1253 public 1254 function getDefaultPageName(): string 1255 { 1256 return $this->pageName->getDefaultValue(); 1257 } 1258 1259 public 1260 function getDefaultTitle(): string 1261 { 1262 return $this->title->getDefaultValue(); 1263 } 1264 1265 /** 1266 * @throws ExceptionNotFound 1267 */ 1268 public 1269 function getDefaultH1() 1270 { 1271 return $this->h1->getValueOrDefault(); 1272 } 1273 1274 public 1275 function getDefaultType(): string 1276 { 1277 return $this->type->getDefaultValue(); 1278 } 1279 1280 public 1281 function getDefaultLayout(): string 1282 { 1283 return $this->layout->getDefaultValue(); 1284 } 1285 1286 1287 /** 1288 * 1289 * @throws ExceptionCompile 1290 */ 1291 public 1292 function setLowQualityIndicatorCalculation($bool): MarkupPath 1293 { 1294 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool); 1295 } 1296 1297 1298 /** 1299 * Change the quality indicator 1300 * and if the quality level has become low 1301 * and that the protection is on, delete the cache 1302 * @param MetadataBoolean $lowQualityAttributeName 1303 * @param bool $value 1304 * @return MarkupPath 1305 * @throws ExceptionBadArgument - if the value cannot be persisted 1306 */ 1307 private 1308 function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): MarkupPath 1309 { 1310 try { 1311 $actualValue = $lowQualityAttributeName->getValue(); 1312 } catch (ExceptionNotFound $e) { 1313 $actualValue = null; 1314 } 1315 if ($value !== $actualValue) { 1316 $lowQualityAttributeName 1317 ->setValue($value) 1318 ->persist(); 1319 } 1320 return $this; 1321 } 1322 1323 1324 /** 1325 * @throws ExceptionNotFound 1326 */ 1327 public 1328 function getLowQualityIndicatorCalculated() 1329 { 1330 1331 return $this->lowQualityIndicatorCalculated->getValueOrDefault(); 1332 1333 } 1334 1335 /** 1336 * @return PageImage[] 1337 * @deprecated 1338 */ 1339 public 1340 function getPageMetadataImages(): array 1341 { 1342 return $this->pageImages->getValueAsPageImages(); 1343 } 1344 1345 1346 /** 1347 * @param array|string $jsonLd 1348 * @return $this 1349 * @throws ExceptionCompile 1350 * @deprecated for {@link LdJson} 1351 */ 1352 public 1353 function setJsonLd($jsonLd): MarkupPath 1354 { 1355 $this->ldJson 1356 ->setValue($jsonLd) 1357 ->sendToWriteStore(); 1358 return $this; 1359 } 1360 1361 /** 1362 * @throws ExceptionCompile 1363 */ 1364 public 1365 function setPageType(string $value): MarkupPath 1366 { 1367 $this->type 1368 ->setValue($value) 1369 ->sendToWriteStore(); 1370 return $this; 1371 } 1372 1373 1374 /** 1375 * @param $aliasPath 1376 * @param string $aliasType 1377 * @return Alias 1378 * @deprecated for {@link Aliases} 1379 */ 1380 public 1381 function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias 1382 { 1383 1384 return $this->aliases->addAndGetAlias($aliasPath, $aliasType); 1385 1386 } 1387 1388 1389 /** 1390 * @return Alias[] 1391 * @throws ExceptionNotFound 1392 */ 1393 public 1394 function getAliases(): array 1395 { 1396 return $this->aliases->getValueAsAlias(); 1397 } 1398 1399 /** 1400 * @return string 1401 */ 1402 public 1403 function getSlugOrDefault(): string 1404 { 1405 try { 1406 return $this->getSlug(); 1407 } catch (ExceptionNotFound $e) { 1408 return $this->getDefaultSlug(); 1409 } 1410 1411 } 1412 1413 /** 1414 * 1415 * @return string 1416 * 1417 */ 1418 public 1419 function getDefaultSlug(): string 1420 { 1421 return $this->slug->getDefaultValue(); 1422 } 1423 1424 /** 1425 * The parent page is the parent in the page tree directory 1426 * 1427 * If the page is at the root, the parent page is the root home 1428 * Only the root home does not have any parent page and return null. 1429 * 1430 * @return MarkupPath 1431 * @throws ExceptionNotFound 1432 */ 1433 public function getParent(): MarkupPath 1434 { 1435 1436 $names = $this->getNames(); 1437 if (sizeof($names) == 0) { 1438 throw new ExceptionNotFound("No parent page"); 1439 } 1440 $slice = 1; 1441 if ($this->isIndexPage()) { 1442 /** 1443 * The parent of a home page 1444 * is in the parent directory 1445 */ 1446 $slice = 2; 1447 } 1448 /** 1449 * Delete the last or the two last parts 1450 */ 1451 if (sizeof($names) < $slice) { 1452 throw new ExceptionNotFound("No parent page"); 1453 } 1454 /** 1455 * Get the actual directory for a page 1456 * or the parent directory for a home page 1457 */ 1458 $parentNames = array_slice($names, 0, sizeof($names) - $slice); 1459 /** 1460 * Create the parent namespace id 1461 */ 1462 $parentNamespaceId = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $parentNames) . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT; 1463 try { 1464 return self::getIndexPageFromNamespace($parentNamespaceId); 1465 } catch (ExceptionBadSyntax $e) { 1466 $message = "Error on getParentPage, null returned - Error: {$e->getMessage()}"; 1467 LogUtility::internalError($message); 1468 throw new ExceptionNotFound($message); 1469 } 1470 1471 } 1472 1473 /** 1474 * @throws ExceptionCompile 1475 */ 1476 public 1477 function setDescription($description): MarkupPath 1478 { 1479 1480 $this->description 1481 ->setValue($description) 1482 ->sendToWriteStore(); 1483 return $this; 1484 } 1485 1486 /** 1487 * @throws ExceptionCompile 1488 * @deprecated uses {@link EndDate} instead 1489 */ 1490 public 1491 function setEndDate($value): MarkupPath 1492 { 1493 $this->endDate 1494 ->setFromStoreValue($value) 1495 ->sendToWriteStore(); 1496 return $this; 1497 } 1498 1499 /** 1500 * @throws ExceptionCompile 1501 * @deprecated uses {@link StartDate} instead 1502 */ 1503 public 1504 function setStartDate($value): MarkupPath 1505 { 1506 $this->startDate 1507 ->setFromStoreValue($value) 1508 ->sendToWriteStore(); 1509 return $this; 1510 } 1511 1512 /** 1513 * @throws ExceptionCompile 1514 */ 1515 public 1516 function setPublishedDate($value): MarkupPath 1517 { 1518 $this->publishedDate 1519 ->setFromStoreValue($value) 1520 ->sendToWriteStore(); 1521 return $this; 1522 } 1523 1524 /** 1525 * Utility to {@link ResourceName::setValue()} 1526 * Used mostly to create page in test 1527 * @throws ExceptionCompile 1528 * @deprecated use not persist 1529 */ 1530 public 1531 function setPageName($value): MarkupPath 1532 { 1533 $this->pageName 1534 ->setValue($value) 1535 ->sendToWriteStore(); 1536 return $this; 1537 } 1538 1539 1540 /** 1541 * @throws ExceptionCompile 1542 */ 1543 public 1544 function setTitle($value): MarkupPath 1545 { 1546 $this->title 1547 ->setValue($value) 1548 ->sendToWriteStore(); 1549 return $this; 1550 } 1551 1552 /** 1553 * @throws ExceptionCompile 1554 */ 1555 public 1556 function setH1($value): MarkupPath 1557 { 1558 $this->h1 1559 ->setValue($value) 1560 ->sendToWriteStore(); 1561 return $this; 1562 } 1563 1564 /** 1565 * @throws Exception 1566 */ 1567 public 1568 function setRegion($value): MarkupPath 1569 { 1570 $this->region 1571 ->setFromStoreValue($value) 1572 ->sendToWriteStore(); 1573 return $this; 1574 } 1575 1576 /** 1577 * @throws ExceptionCompile 1578 */ 1579 public 1580 function setLang($value): MarkupPath 1581 { 1582 1583 $this->lang 1584 ->setFromStoreValue($value) 1585 ->sendToWriteStore(); 1586 return $this; 1587 } 1588 1589 /** 1590 * @throws ExceptionCompile 1591 */ 1592 public 1593 function setLayout($value): MarkupPath 1594 { 1595 $this->layout 1596 ->setValue($value) 1597 ->sendToWriteStore(); 1598 return $this; 1599 } 1600 1601 1602 /** 1603 * 1604 * We manage the properties by setter and getter 1605 * 1606 * Why ? 1607 * * Because we can capture the updates 1608 * * Because setter are the entry point to good quality data 1609 * * Because dokuwiki may cache the metadata (see below) 1610 * 1611 * Note all properties have been migrated 1612 * but they should be initialized below 1613 * 1614 * Dokuwiki cache: the data may be cached without our consent 1615 * The method {@link p_get_metadata()} does it with this logic 1616 * ``` 1617 * $cache = ($ID == $id); 1618 * $meta = p_read_metadata($id, $cache); 1619 * ``` 1620 */ 1621 private 1622 function buildPropertiesFromFileSystem() 1623 { 1624 1625 /** 1626 * New meta system 1627 * Even if it does not exist, the metadata object should be instantiated 1628 * otherwise, there is a null exception 1629 */ 1630 $this->cacheExpirationDate = CacheExpirationDate::createForPage($this); 1631 $this->aliases = Aliases::createForPage($this); 1632 $this->pageImages = PageImages::createForPage($this); 1633 $this->pageName = ResourceName::createForResource($this); 1634 $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this); 1635 $this->ldJson = LdJson::createForPage($this); 1636 $this->canonical = Canonical::createForPage($this); 1637 $this->description = PageDescription::createForPage($this); 1638 $this->h1 = PageH1::createForPage($this); 1639 $this->type = PageType::createForPage($this); 1640 $this->creationTime = CreationDate::createForPage($this); 1641 $this->title = PageTitle::createForMarkup($this); 1642 $this->keywords = PageKeywords::createForPage($this); 1643 $this->publishedDate = PagePublicationDate::createFromPage($this); 1644 $this->startDate = StartDate::createFromPage($this); 1645 $this->endDate = EndDate::createFromPage($this); 1646 $this->locale = Locale::createForPage($this); 1647 $this->lang = Lang::createForMarkup($this); 1648 $this->region = Region::createForPage($this); 1649 $this->slug = \ComboStrap\Slug::createForPage($this); 1650 $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this); 1651 $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this); 1652 $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this); 1653 $this->modifiedTime = ModificationDate::createForPage($this); 1654 $this->pageUrlPath = PageUrlPath::createForPage($this); 1655 $this->layout = PageTemplateName::createFromPage($this); 1656 1657 } 1658 1659 1660 function getPageIdAbbr() 1661 { 1662 1663 if ($this->getPageId() === null) return null; 1664 return PageId::getAbbreviated($this->getPageId()); 1665 1666 } 1667 1668 public 1669 function setDatabasePage(DatabasePageRow $databasePage): MarkupPath 1670 { 1671 $this->databasePage = $databasePage; 1672 return $this; 1673 } 1674 1675 1676 /** 1677 * @return string|null 1678 * 1679 * @throws ExceptionNotFound 1680 */ 1681 public 1682 function getSlug(): string 1683 { 1684 return $this->slug->getValue(); 1685 } 1686 1687 1688 /** 1689 * @throws ExceptionCompile 1690 */ 1691 public 1692 function setSlug($slug): MarkupPath 1693 { 1694 $this->slug 1695 ->setFromStoreValue($slug) 1696 ->sendToWriteStore(); 1697 return $this; 1698 } 1699 1700 1701 /** 1702 * @return string - the id in the Url 1703 */ 1704 public function getUrlId(): string 1705 { 1706 return $this->pageUrlPath->getValueOrDefaultAsWikiId(); 1707 } 1708 1709 1710 /** 1711 * @throws ExceptionCompile 1712 */ 1713 public 1714 function setQualityMonitoringIndicator($boolean): MarkupPath 1715 { 1716 $this->qualityMonitoringIndicator 1717 ->setFromStoreValue($boolean) 1718 ->sendToWriteStore(); 1719 return $this; 1720 } 1721 1722 /** 1723 * 1724 * @param $aliasPath - third information - the alias used to build this page 1725 */ 1726 public 1727 function setBuildAliasPath($aliasPath) 1728 { 1729 $this->buildAliasPath = $aliasPath; 1730 } 1731 1732 public 1733 function getBuildAlias(): ?Alias 1734 { 1735 if ($this->buildAliasPath === null) return null; 1736 try { 1737 $aliases = $this->getAliases(); 1738 } catch (ExceptionNotFound $e) { 1739 // should not 1740 return null; 1741 } 1742 foreach ($aliases as $alias) { 1743 if ($alias->getPath() === $this->buildAliasPath) { 1744 return $alias; 1745 } 1746 } 1747 return null; 1748 } 1749 1750 public 1751 function isDynamicQualityMonitored(): bool 1752 { 1753 if ($this->getQualityMonitoringIndicator() !== null) { 1754 return $this->getQualityMonitoringIndicator(); 1755 } 1756 return $this->getDefaultQualityMonitoring(); 1757 } 1758 1759 public 1760 function getDefaultQualityMonitoring(): bool 1761 { 1762 if (SiteConfig::getConfValue(QualityMessageHandler::CONF_DISABLE_QUALITY_MONITORING) === 1) { 1763 return false; 1764 } else { 1765 return true; 1766 } 1767 } 1768 1769 /** 1770 * @param MetadataStore|string $store 1771 * @return $this 1772 */ 1773 public 1774 function setReadStore($store): MarkupPath 1775 { 1776 $this->readStore = $store; 1777 return $this; 1778 } 1779 1780 1781 /** 1782 * @param array $usages 1783 * @return IFetcherLocalImage[] 1784 */ 1785 public 1786 function getImagesForTheFollowingUsages(array $usages): array 1787 { 1788 $usages = array_merge($usages, [PageImageUsage::ALL]); 1789 $images = []; 1790 foreach ($this->getPageMetadataImages() as $pageImage) { 1791 foreach ($usages as $usage) { 1792 if (in_array($usage, $pageImage->getUsages())) { 1793 $path = $pageImage->getImagePath(); 1794 try { 1795 $images[] = IFetcherLocalImage::createImageFetchFromPath($path); 1796 } catch (ExceptionBadArgument $e) { 1797 LogUtility::error(`The page image $path of the page $this is not an image`); 1798 } catch (ExceptionBadSyntax $e) { 1799 LogUtility::error(`The page image $path has a bad syntax`); 1800 } catch (ExceptionNotExists $e) { 1801 LogUtility::error(`The page image $path does not exists`); 1802 } 1803 continue 2; 1804 } 1805 } 1806 } 1807 return $images; 1808 1809 } 1810 1811 1812 /** 1813 * @throws ExceptionNotFound 1814 */ 1815 public 1816 function getKeywords(): array 1817 { 1818 return $this->keywords->getValue(); 1819 } 1820 1821 /** 1822 * @throws ExceptionNotFound 1823 */ 1824 public function getKeywordsOrDefault(): array 1825 { 1826 return $this->keywords->getValueOrDefaults(); 1827 } 1828 1829 1830 /** 1831 * @throws ExceptionCompile 1832 */ 1833 public 1834 function setKeywords($value): MarkupPath 1835 { 1836 $this->keywords 1837 ->setFromStoreValue($value) 1838 ->sendToWriteStore(); 1839 return $this; 1840 } 1841 1842 /** 1843 * @return DateTime|null 1844 * @throws ExceptionNotFound 1845 * @deprecated for {@link CacheExpirationDate} 1846 */ 1847 public 1848 function getCacheExpirationDate(): ?DateTime 1849 { 1850 return $this->cacheExpirationDate->getValue(); 1851 } 1852 1853 /** 1854 * @return DateTime|null 1855 * @throws ExceptionNotFound 1856 * @deprecated for {@link CacheExpirationDate} 1857 */ 1858 public 1859 function getDefaultCacheExpirationDate(): ?DateTime 1860 { 1861 return $this->cacheExpirationDate->getDefaultValue(); 1862 } 1863 1864 /** 1865 * @return string|null 1866 * @throws ExceptionNotFound 1867 * @deprecated for {@link CacheExpirationFrequency} 1868 */ 1869 public 1870 function getCacheExpirationFrequency(): string 1871 { 1872 return $this->cacheExpirationFrequency->getValue(); 1873 } 1874 1875 1876 /** 1877 * @param DateTime $cacheExpirationDate 1878 * @return $this 1879 * @deprecated for {@link CacheExpirationDate} 1880 */ 1881 public 1882 function setCacheExpirationDate(DateTime $cacheExpirationDate): MarkupPath 1883 { 1884 $this->cacheExpirationDate->setValue($cacheExpirationDate); 1885 return $this; 1886 } 1887 1888 1889 /** 1890 * Utility class 1891 * Get the instructions document as if it was the main page. 1892 * Ie the context path is: 1893 * * the markup path itself) 1894 * * or the default context path if the path cannot be transformed as wiki path. 1895 */ 1896 public function getInstructionsDocument(): FetcherMarkup 1897 { 1898 1899 $path = $this->getPathObject(); 1900 try { 1901 $contextPath = $path->toWikiPath(); 1902 } catch (ExceptionCast $e) { 1903 $contextPath = ExecutionContext::getActualOrCreateFromEnv() 1904 ->getDefaultContextPath(); 1905 } 1906 return FetcherMarkup::confRoot() 1907 ->setRequestedExecutingPath($path) 1908 ->setRequestedContextPath($contextPath) 1909 ->setRequestedMimeToInstructions() 1910 ->build(); 1911 1912 } 1913 1914 public 1915 function delete() 1916 { 1917 1918 Index::getOrCreate()->deletePage($this); 1919 saveWikiText($this->getWikiId(), "", "Delete"); 1920 1921 } 1922 1923 /** 1924 * @return Url -the absolute canonical url 1925 */ 1926 public 1927 function getAbsoluteCanonicalUrl(): Url 1928 { 1929 return $this->getCanonicalUrl()->toAbsoluteUrl(); 1930 } 1931 1932 1933 public 1934 function getReadStoreOrDefault(): MetadataStore 1935 { 1936 if ($this->readStore === null) { 1937 /** 1938 * No cache please if not set 1939 * Cache should be in the MetadataDokuWikiStore 1940 * that is page requested scoped and not by slot 1941 */ 1942 return MetadataDokuWikiStore::getOrCreateFromResource($this); 1943 } 1944 if (!($this->readStore instanceof MetadataStore)) { 1945 $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this); 1946 } 1947 return $this->readStore; 1948 } 1949 1950 /** 1951 * @return Path 1952 * A markup path wraps a path 1953 */ 1954 public function getPathObject(): Path 1955 { 1956 return $this->path; 1957 } 1958 1959 1960 /** 1961 * A shortcut for {@link MarkupPath::getPathObject()::getDokuwikiId()} 1962 * 1963 * @throws ExceptionBadArgument - if the markup path is not a {@link WikiPath} 1964 */ 1965 public 1966 function getWikiId(): string 1967 { 1968 $path = $this->getPathObject(); 1969 return WikiPath::createFromPathObject($path)->getWikiId(); 1970 } 1971 1972 public 1973 function getUid(): Metadata 1974 { 1975 return PageId::createForPage($this); 1976 } 1977 1978 1979 public 1980 function getAbsolutePath(): string 1981 { 1982 return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId(); 1983 } 1984 1985 /** 1986 * Todo, it should be a property of the markup not every markup file are main page markup. 1987 * @return string 1988 */ 1989 function getType(): string 1990 { 1991 return self::TYPE; 1992 } 1993 1994 /** 1995 * @return PageUrlPath 1996 * @deprecated use {@link PageUrlPath} instead 1997 */ 1998 public 1999 function getUrlPathObject(): PageUrlPath 2000 { 2001 return $this->pageUrlPath; 2002 } 2003 2004 2005 public function getSideSlot(): ?MarkupPath 2006 { 2007 2008 /** 2009 * Only primary slot have a side slot 2010 * Root Home page does not have one either 2011 */ 2012 if ($this->isSlot()) { 2013 return null; 2014 } 2015 2016 $nearestMainFooter = $this->findNearest(SlotSystem::getSidebarName()); 2017 if ($nearestMainFooter === false) { 2018 return null; 2019 } 2020 return MarkupPath::createMarkupFromId($nearestMainFooter); 2021 2022 2023 } 2024 2025 /** 2026 * @param $pageName 2027 * @return false|string 2028 */ 2029 private function findNearest($pageName) 2030 { 2031 global $ID; 2032 $keep = $ID; 2033 try { 2034 $ID = $this->getWikiId(); 2035 return page_findnearest($pageName); 2036 } finally { 2037 $ID = $keep; 2038 } 2039 2040 } 2041 2042 /** 2043 * The slots that are independent from the primary slot 2044 * 2045 * @return MarkupPath[] 2046 * @deprecated should be {@link TemplateForWebPage} based 2047 */ 2048 public function getPrimaryIndependentSlots(): array 2049 { 2050 $secondarySlots = []; 2051 $sideSlot = $this->getSideSlot(); 2052 if ($sideSlot !== null) { 2053 $secondarySlots[] = $sideSlot; 2054 } 2055 return $secondarySlots; 2056 } 2057 2058 2059 public function isHidden(): bool 2060 { 2061 return isHiddenPage($this->getWikiId()); 2062 } 2063 2064 2065 public function getPrimaryHeaderPage(): ?MarkupPath 2066 { 2067 $nearest = page_findnearest(SlotSystem::getMainHeaderSlotName()); 2068 if ($nearest === false) { 2069 return null; 2070 } 2071 return MarkupPath::createMarkupFromId($nearest); 2072 } 2073 2074 public function createPageFetcherHtml(): FetcherPage 2075 { 2076 return FetcherPage::createPageFetcherFromMarkupPath($this); 2077 } 2078 2079 public function getHttpResponse(): HttpResponse 2080 { 2081 return HttpRequest::fetchXhtmlPageResponse($this->getWikiId()); 2082 } 2083 2084 /** 2085 * @return Outline 2086 * @deprecated uses {@link FetcherMarkup::getOutline()} instead 2087 */ 2088 public function getOutline(): Outline 2089 { 2090 2091 return $this->getInstructionsDocument()->getOutline(); 2092 2093 } 2094 2095 2096 public function persistToDefaultMetaStore(): MarkupPath 2097 { 2098 $this->getReadStoreOrDefault()->persist(); 2099 return $this; 2100 } 2101 2102 public function getInstructionsPath(): LocalPath 2103 { 2104 2105 $instructionsDocument = $this->getInstructionsDocument(); 2106 return $instructionsDocument->getInstructionsPath(); 2107 2108 } 2109 2110 public function setContent(string $textContent): MarkupPath 2111 { 2112 FileSystems::setContent($this, $textContent); 2113 return $this; 2114 } 2115 2116 /** 2117 * @throws ExceptionNotExists - if the path does not exist 2118 */ 2119 public function createHtmlFetcherWithRequestedPathAsContextPath(): FetcherMarkup 2120 { 2121 $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 2122 $executingPath = $this->getPathObject(); 2123 $requestedPath = $executionContext->getRequestedPath(); 2124 $requestedMarkupPath = MarkupPath::createPageFromPathObject($requestedPath); 2125 2126 if ($requestedMarkupPath->isSlot()) { 2127 try { 2128 $markupContextPath = SlotSystem::getContextPath(); 2129 SlotSystem::sendContextPathMessage($markupContextPath); 2130 $requestedPath = $markupContextPath->toWikiPath(); 2131 } catch (\Exception $e) { 2132 // should not 2133 } 2134 } 2135 return FetcherMarkup::confRoot() 2136 ->setRequestedMimeToXhtml() 2137 ->setRequestedContextPath($requestedPath) 2138 ->setRequestedExecutingPath($executingPath) 2139 ->build(); 2140 } 2141 2142 public 2143 function isRootItemPage(): bool 2144 { 2145 try { 2146 if ($this->isIndexPage()) { 2147 return false; 2148 } 2149 $parent = $this->getParent(); 2150 if ($parent->isRootHomePage()) { 2151 return true; 2152 } 2153 return false; 2154 } catch (ExceptionNotFound $e) { 2155 return false; 2156 } 2157 } 2158 2159 private 2160 function getPrimaryFooterPage(): ?MarkupPath 2161 { 2162 $nearest = page_findnearest(SlotSystem::getMainFooterSlotName()); 2163 if ($nearest === false) { 2164 return null; 2165 } 2166 return MarkupPath::createMarkupFromId($nearest); 2167 } 2168 2169 /** 2170 * Set the page path to an index page for a directory path 2171 * @return void 2172 */ 2173 private 2174 function setCorrectPathForDirectoryToIndexPage(): void 2175 { 2176 2177 2178 if (!($this->path instanceof WikiPath)) { 2179 return; 2180 } 2181 /** 2182 * @var $path WikiPath 2183 */ 2184 $path = $this->path; 2185 2186 /** 2187 * We correct the path 2188 * We don't return a page because it does not work in a constructor 2189 */ 2190 $startPageName = Site::getIndexPageName(); 2191 $indexPath = $path->resolveId($startPageName); 2192 if (FileSystems::exists($indexPath)) { 2193 // start page inside namespace 2194 $this->path = $indexPath; 2195 return; 2196 } 2197 2198 // page named like the NS inside the NS 2199 try { 2200 $parentName = $this->getLastNameWithoutExtension(); 2201 $nsInsideNsIndex = $this->path->resolveId($parentName); 2202 if (FileSystems::exists($nsInsideNsIndex)) { 2203 $this->path = $nsInsideNsIndex; 2204 return; 2205 } 2206 } catch (ExceptionNotFound $e) { 2207 // no last name 2208 } 2209 2210 // We don't support the child page 2211 // Does not exist but can be used by hierarchical function 2212 $this->path = $indexPath; 2213 } 2214 2215 2216 public 2217 function getUidObject(): Metadata 2218 { 2219 if ($this->uidObject === null) { 2220 try { 2221 $this->uidObject = Meta\Api\MetadataSystem::toMetadataObject($this->getUid()) 2222 ->setResource($this); 2223 } catch (ExceptionBadArgument $e) { 2224 throw new ExceptionRuntimeInternal("Uid object is a metadata object. It should not happen.", self::CANONICAL_PAGE, 1, $e); 2225 } 2226 } 2227 2228 return $this->uidObject; 2229 } 2230 2231 function getExtension(): string 2232 { 2233 return $this->path->getExtension(); 2234 } 2235 2236 function getLastNameWithoutExtension(): string 2237 { 2238 return $this->path->getLastNameWithoutExtension(); 2239 } 2240 2241 function getScheme(): string 2242 { 2243 return MarkupFileSystem::SCHEME; 2244 } 2245 2246 function getLastName(): string 2247 { 2248 return $this->path->getLastName(); 2249 } 2250 2251 function getNames() 2252 { 2253 return $this->path->getNames(); 2254 } 2255 2256 function toAbsoluteId(): string 2257 { 2258 return $this->path->toAbsoluteId(); 2259 } 2260 2261 function toUriString(): string 2262 { 2263 return $this->path->toUriString(); 2264 } 2265 2266 function toAbsolutePath(): Path 2267 { 2268 return $this->path->toAbsolutePath(); 2269 } 2270 2271 2272 function resolve(string $name): Path 2273 { 2274 return $this->path->resolve($name); 2275 } 2276 2277 2278 function getUrl(): Url 2279 { 2280 return FetcherPage::createPageFetcherFromMarkupPath($this) 2281 ->getFetchUrl(); 2282 } 2283 2284 function getHost(): string 2285 { 2286 return $this->path->getHost(); 2287 } 2288 2289 public 2290 function __toString(): string 2291 { 2292 return $this->path->__toString(); 2293 } 2294 2295 /** 2296 * @throws ExceptionBadSyntax 2297 * @throws ExceptionBadArgument 2298 */ 2299 public 2300 static function createFromUri(string $uri): MarkupPath 2301 { 2302 $path = FileSystems::createPathFromUri($uri); 2303 return new MarkupPath($path); 2304 } 2305 2306 2307} 2308