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 function getPageIdAbbr() 1662 { 1663 1664 if ($this->getPageId() === null) return null; 1665 return PageId::getAbbreviated($this->getPageId()); 1666 1667 } 1668 1669 public 1670 function setDatabasePage(DatabasePageRow $databasePage): MarkupPath 1671 { 1672 $this->databasePage = $databasePage; 1673 return $this; 1674 } 1675 1676 1677 /** 1678 * @return string|null 1679 * 1680 * @throws ExceptionNotFound 1681 */ 1682 public 1683 function getSlug(): string 1684 { 1685 return $this->slug->getValue(); 1686 } 1687 1688 1689 /** 1690 * @throws ExceptionCompile 1691 */ 1692 public 1693 function setSlug($slug): MarkupPath 1694 { 1695 $this->slug 1696 ->setFromStoreValue($slug) 1697 ->sendToWriteStore(); 1698 return $this; 1699 } 1700 1701 1702 /** 1703 * @return string - the id in the Url 1704 */ 1705 public function getUrlId(): string 1706 { 1707 return $this->pageUrlPath->getValueOrDefaultAsWikiId(); 1708 } 1709 1710 1711 /** 1712 * @throws ExceptionCompile 1713 */ 1714 public 1715 function setQualityMonitoringIndicator($boolean): MarkupPath 1716 { 1717 $this->qualityMonitoringIndicator 1718 ->setFromStoreValue($boolean) 1719 ->sendToWriteStore(); 1720 return $this; 1721 } 1722 1723 /** 1724 * 1725 * @param $aliasPath - third information - the alias used to build this page 1726 */ 1727 public 1728 function setBuildAliasPath($aliasPath) 1729 { 1730 $this->buildAliasPath = $aliasPath; 1731 } 1732 1733 public 1734 function getBuildAlias(): ?Alias 1735 { 1736 if ($this->buildAliasPath === null) return null; 1737 try { 1738 $aliases = $this->getAliases(); 1739 } catch (ExceptionNotFound $e) { 1740 // should not 1741 return null; 1742 } 1743 foreach ($aliases as $alias) { 1744 if ($alias->getPath() === $this->buildAliasPath) { 1745 return $alias; 1746 } 1747 } 1748 return null; 1749 } 1750 1751 public 1752 function isDynamicQualityMonitored(): bool 1753 { 1754 if ($this->getQualityMonitoringIndicator() !== null) { 1755 return $this->getQualityMonitoringIndicator(); 1756 } 1757 return $this->getDefaultQualityMonitoring(); 1758 } 1759 1760 public 1761 function getDefaultQualityMonitoring(): bool 1762 { 1763 if (SiteConfig::getConfValue(QualityMessageHandler::CONF_DISABLE_QUALITY_MONITORING) === 1) { 1764 return false; 1765 } else { 1766 return true; 1767 } 1768 } 1769 1770 /** 1771 * @param MetadataStore|string $store 1772 * @return $this 1773 */ 1774 public 1775 function setReadStore($store): MarkupPath 1776 { 1777 $this->readStore = $store; 1778 return $this; 1779 } 1780 1781 1782 /** 1783 * @param array $usages 1784 * @return IFetcherLocalImage[] 1785 */ 1786 public 1787 function getImagesForTheFollowingUsages(array $usages): array 1788 { 1789 $usages = array_merge($usages, [PageImageUsage::ALL]); 1790 $images = []; 1791 foreach ($this->getPageMetadataImages() as $pageImage) { 1792 foreach ($usages as $usage) { 1793 if (in_array($usage, $pageImage->getUsages())) { 1794 $path = $pageImage->getImagePath(); 1795 try { 1796 $images[] = IFetcherLocalImage::createImageFetchFromPath($path); 1797 } catch (ExceptionBadArgument $e) { 1798 LogUtility::error(`The page image $path of the page $this is not an image`); 1799 } catch (ExceptionBadSyntax $e) { 1800 LogUtility::error(`The page image $path has a bad syntax`); 1801 } catch (ExceptionNotExists $e) { 1802 LogUtility::error(`The page image $path does not exists`); 1803 } 1804 continue 2; 1805 } 1806 } 1807 } 1808 return $images; 1809 1810 } 1811 1812 1813 /** 1814 * @throws ExceptionNotFound 1815 */ 1816 public 1817 function getKeywords(): array 1818 { 1819 return $this->keywords->getValue(); 1820 } 1821 1822 /** 1823 * @throws ExceptionNotFound 1824 */ 1825 public function getKeywordsOrDefault(): array 1826 { 1827 return $this->keywords->getValueOrDefaults(); 1828 } 1829 1830 1831 /** 1832 * @throws ExceptionCompile 1833 */ 1834 public 1835 function setKeywords($value): MarkupPath 1836 { 1837 $this->keywords 1838 ->setFromStoreValue($value) 1839 ->sendToWriteStore(); 1840 return $this; 1841 } 1842 1843 /** 1844 * @return DateTime|null 1845 * @throws ExceptionNotFound 1846 * @deprecated for {@link CacheExpirationDate} 1847 */ 1848 public 1849 function getCacheExpirationDate(): ?DateTime 1850 { 1851 return $this->cacheExpirationDate->getValue(); 1852 } 1853 1854 /** 1855 * @return DateTime|null 1856 * @throws ExceptionNotFound 1857 * @deprecated for {@link CacheExpirationDate} 1858 */ 1859 public 1860 function getDefaultCacheExpirationDate(): ?DateTime 1861 { 1862 return $this->cacheExpirationDate->getDefaultValue(); 1863 } 1864 1865 /** 1866 * @return string|null 1867 * @throws ExceptionNotFound 1868 * @deprecated for {@link CacheExpirationFrequency} 1869 */ 1870 public 1871 function getCacheExpirationFrequency(): string 1872 { 1873 return $this->cacheExpirationFrequency->getValue(); 1874 } 1875 1876 1877 /** 1878 * @param DateTime $cacheExpirationDate 1879 * @return $this 1880 * @deprecated for {@link CacheExpirationDate} 1881 */ 1882 public 1883 function setCacheExpirationDate(DateTime $cacheExpirationDate): MarkupPath 1884 { 1885 $this->cacheExpirationDate->setValue($cacheExpirationDate); 1886 return $this; 1887 } 1888 1889 1890 /** 1891 * Utility class 1892 * Get the instructions document as if it was the main page. 1893 * Ie the context path is: 1894 * * the markup path itself) 1895 * * or the default context path if the path cannot be transformed as wiki path. 1896 */ 1897 public function getInstructionsDocument(): FetcherMarkup 1898 { 1899 1900 $path = $this->getPathObject(); 1901 try { 1902 $contextPath = $path->toWikiPath(); 1903 } catch (ExceptionCast $e) { 1904 $contextPath = ExecutionContext::getActualOrCreateFromEnv() 1905 ->getDefaultContextPath(); 1906 } 1907 return FetcherMarkup::confRoot() 1908 ->setRequestedExecutingPath($path) 1909 ->setRequestedContextPath($contextPath) 1910 ->setRequestedMimeToInstructions() 1911 ->build(); 1912 1913 } 1914 1915 public 1916 function delete() 1917 { 1918 1919 Index::getOrCreate()->deletePage($this); 1920 saveWikiText($this->getWikiId(), "", "Delete"); 1921 1922 } 1923 1924 /** 1925 * @return Url -the absolute canonical url 1926 */ 1927 public 1928 function getAbsoluteCanonicalUrl(): Url 1929 { 1930 return $this->getCanonicalUrl()->toAbsoluteUrl(); 1931 } 1932 1933 1934 public 1935 function getReadStoreOrDefault(): MetadataStore 1936 { 1937 if ($this->readStore === null) { 1938 /** 1939 * No cache please if not set 1940 * Cache should be in the MetadataDokuWikiStore 1941 * that is page requested scoped and not by slot 1942 */ 1943 return MetadataDokuWikiStore::getOrCreateFromResource($this); 1944 } 1945 if (!($this->readStore instanceof MetadataStore)) { 1946 $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this); 1947 } 1948 return $this->readStore; 1949 } 1950 1951 /** 1952 * @return Path 1953 * A markup path wraps a path 1954 */ 1955 public function getPathObject(): Path 1956 { 1957 return $this->path; 1958 } 1959 1960 1961 /** 1962 * A shortcut for {@link MarkupPath::getPathObject()::getDokuwikiId()} 1963 * 1964 * @throws ExceptionBadArgument - if the markup path is not a {@link WikiPath} 1965 */ 1966 public 1967 function getWikiId(): string 1968 { 1969 $path = $this->getPathObject(); 1970 return WikiPath::createFromPathObject($path)->getWikiId(); 1971 } 1972 1973 public 1974 function getUid(): Metadata 1975 { 1976 return PageId::createForPage($this); 1977 } 1978 1979 1980 public 1981 function getAbsolutePath(): string 1982 { 1983 return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId(); 1984 } 1985 1986 /** 1987 * Todo, it should be a property of the markup not every markup file are main page markup. 1988 * @return string 1989 */ 1990 function getType(): string 1991 { 1992 return self::TYPE; 1993 } 1994 1995 /** 1996 * @return PageUrlPath 1997 * @deprecated use {@link PageUrlPath} instead 1998 */ 1999 public 2000 function getUrlPathObject(): PageUrlPath 2001 { 2002 return $this->pageUrlPath; 2003 } 2004 2005 2006 public function getSideSlot(): ?MarkupPath 2007 { 2008 2009 /** 2010 * Only primary slot have a side slot 2011 * Root Home page does not have one either 2012 */ 2013 if ($this->isSlot()) { 2014 return null; 2015 } 2016 2017 $nearestMainFooter = $this->findNearest(SlotSystem::getSidebarName()); 2018 if ($nearestMainFooter === false) { 2019 return null; 2020 } 2021 return MarkupPath::createMarkupFromId($nearestMainFooter); 2022 2023 2024 } 2025 2026 /** 2027 * @param $pageName 2028 * @return false|string 2029 */ 2030 private function findNearest($pageName) 2031 { 2032 global $ID; 2033 $keep = $ID; 2034 try { 2035 $ID = $this->getWikiId(); 2036 return page_findnearest($pageName); 2037 } finally { 2038 $ID = $keep; 2039 } 2040 2041 } 2042 2043 /** 2044 * The slots that are independent from the primary slot 2045 * 2046 * @return MarkupPath[] 2047 * @deprecated should be {@link TemplateForWebPage} based 2048 */ 2049 public function getPrimaryIndependentSlots(): array 2050 { 2051 $secondarySlots = []; 2052 $sideSlot = $this->getSideSlot(); 2053 if ($sideSlot !== null) { 2054 $secondarySlots[] = $sideSlot; 2055 } 2056 return $secondarySlots; 2057 } 2058 2059 2060 public function isHidden(): bool 2061 { 2062 return isHiddenPage($this->getWikiId()); 2063 } 2064 2065 2066 public function getPrimaryHeaderPage(): ?MarkupPath 2067 { 2068 $nearest = page_findnearest(SlotSystem::getMainHeaderSlotName()); 2069 if ($nearest === false) { 2070 return null; 2071 } 2072 return MarkupPath::createMarkupFromId($nearest); 2073 } 2074 2075 public function createPageFetcherHtml(): FetcherPage 2076 { 2077 return FetcherPage::createPageFetcherFromMarkupPath($this); 2078 } 2079 2080 public function getHttpResponse(): HttpResponse 2081 { 2082 return HttpRequest::fetchXhtmlPageResponse($this->getWikiId()); 2083 } 2084 2085 /** 2086 * @return Outline 2087 * @deprecated uses {@link FetcherMarkup::getOutline()} instead 2088 */ 2089 public function getOutline(): Outline 2090 { 2091 2092 return $this->getInstructionsDocument()->getOutline(); 2093 2094 } 2095 2096 2097 public function persistToDefaultMetaStore(): MarkupPath 2098 { 2099 $this->getReadStoreOrDefault()->persist(); 2100 return $this; 2101 } 2102 2103 public function getInstructionsPath(): LocalPath 2104 { 2105 2106 $instructionsDocument = $this->getInstructionsDocument(); 2107 return $instructionsDocument->getInstructionsPath(); 2108 2109 } 2110 2111 public function setContent(string $textContent): MarkupPath 2112 { 2113 FileSystems::setContent($this, $textContent); 2114 return $this; 2115 } 2116 2117 /** 2118 * @throws ExceptionNotExists - if the path does not exist 2119 */ 2120 public function createHtmlFetcherWithRequestedPathAsContextPath(): FetcherMarkup 2121 { 2122 $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 2123 $executingPath = $this->getPathObject(); 2124 $requestedPath = $executionContext->getRequestedPath(); 2125 $requestedMarkupPath = MarkupPath::createPageFromPathObject($requestedPath); 2126 2127 if ($requestedMarkupPath->isSlot()) { 2128 try { 2129 $markupContextPath = SlotSystem::getContextPath(); 2130 SlotSystem::sendContextPathMessage($markupContextPath); 2131 $requestedPath = $markupContextPath->toWikiPath(); 2132 } catch (\Exception $e) { 2133 // should not 2134 } 2135 } 2136 return FetcherMarkup::confRoot() 2137 ->setRequestedMimeToXhtml() 2138 ->setRequestedContextPath($requestedPath) 2139 ->setRequestedExecutingPath($executingPath) 2140 ->build(); 2141 } 2142 2143 public 2144 function isRootItemPage(): bool 2145 { 2146 try { 2147 if ($this->isIndexPage()) { 2148 return false; 2149 } 2150 $parent = $this->getParent(); 2151 if ($parent->isRootHomePage()) { 2152 return true; 2153 } 2154 return false; 2155 } catch (ExceptionNotFound $e) { 2156 return false; 2157 } 2158 } 2159 2160 private 2161 function getPrimaryFooterPage(): ?MarkupPath 2162 { 2163 $nearest = page_findnearest(SlotSystem::getMainFooterSlotName()); 2164 if ($nearest === false) { 2165 return null; 2166 } 2167 return MarkupPath::createMarkupFromId($nearest); 2168 } 2169 2170 /** 2171 * Set the page path to an index page for a directory path 2172 * @return void 2173 */ 2174 private 2175 function setCorrectPathForDirectoryToIndexPage(): void 2176 { 2177 2178 2179 if (!($this->path instanceof WikiPath)) { 2180 return; 2181 } 2182 /** 2183 * @var $path WikiPath 2184 */ 2185 $path = $this->path; 2186 2187 /** 2188 * We correct the path 2189 * We don't return a page because it does not work in a constructor 2190 */ 2191 $startPageName = Site::getIndexPageName(); 2192 $indexPath = $path->resolveId($startPageName); 2193 if (FileSystems::exists($indexPath)) { 2194 // start page inside namespace 2195 $this->path = $indexPath; 2196 return; 2197 } 2198 2199 // page named like the NS inside the NS 2200 try { 2201 $parentName = $this->getLastNameWithoutExtension(); 2202 $nsInsideNsIndex = $this->path->resolveId($parentName); 2203 if (FileSystems::exists($nsInsideNsIndex)) { 2204 $this->path = $nsInsideNsIndex; 2205 return; 2206 } 2207 } catch (ExceptionNotFound $e) { 2208 // no last name 2209 } 2210 2211 // We don't support the child page 2212 // Does not exist but can be used by hierarchical function 2213 $this->path = $indexPath; 2214 } 2215 2216 2217 public 2218 function getUidObject(): Metadata 2219 { 2220 if ($this->uidObject === null) { 2221 try { 2222 $this->uidObject = Meta\Api\MetadataSystem::toMetadataObject($this->getUid()) 2223 ->setResource($this); 2224 } catch (ExceptionBadArgument $e) { 2225 throw new ExceptionRuntimeInternal("Uid object is a metadata object. It should not happen.", self::CANONICAL_PAGE, 1, $e); 2226 } 2227 } 2228 2229 return $this->uidObject; 2230 } 2231 2232 function getExtension(): string 2233 { 2234 return $this->path->getExtension(); 2235 } 2236 2237 function getLastNameWithoutExtension(): string 2238 { 2239 return $this->path->getLastNameWithoutExtension(); 2240 } 2241 2242 function getScheme(): string 2243 { 2244 return MarkupFileSystem::SCHEME; 2245 } 2246 2247 function getLastName(): string 2248 { 2249 return $this->path->getLastName(); 2250 } 2251 2252 function getNames() 2253 { 2254 return $this->path->getNames(); 2255 } 2256 2257 function toAbsoluteId(): string 2258 { 2259 return $this->path->toAbsoluteId(); 2260 } 2261 2262 function toUriString(): string 2263 { 2264 return $this->path->toUriString(); 2265 } 2266 2267 function toAbsolutePath(): Path 2268 { 2269 return $this->path->toAbsolutePath(); 2270 } 2271 2272 2273 function resolve(string $name): Path 2274 { 2275 return $this->path->resolve($name); 2276 } 2277 2278 2279 function getUrl(): Url 2280 { 2281 return FetcherPage::createPageFetcherFromMarkupPath($this) 2282 ->getFetchUrl(); 2283 } 2284 2285 function getHost(): string 2286 { 2287 return $this->path->getHost(); 2288 } 2289 2290 public 2291 function __toString(): string 2292 { 2293 return $this->path->__toString(); 2294 } 2295 2296 /** 2297 * @throws ExceptionBadSyntax 2298 * @throws ExceptionBadArgument 2299 */ 2300 public 2301 static function createFromUri(string $uri): MarkupPath 2302 { 2303 $path = FileSystems::createPathFromUri($uri); 2304 return new MarkupPath($path); 2305 } 2306 2307 2308} 2309