1<?php 2 3namespace ComboStrap; 4 5 6use action_plugin_combo_qualitymessage; 7use DateTime; 8use Exception; 9use ModificationDate; 10use Slug; 11 12 13/** 14 * Page 15 */ 16require_once(__DIR__ . '/PluginUtility.php'); 17 18/** 19 * 20 * Class Page 21 * @package ComboStrap 22 * 23 * This is just a wrapper around a file with the mime Dokuwiki 24 * that has a doku path (ie with the `:` separator) 25 */ 26class Page extends ResourceComboAbs 27{ 28 29 30 // The page id abbreviation is used in the url 31 // to make them unique. 32 // 33 // A website is not git but an abbreviation of 7 34 // is enough for a website. 35 // 36 // 7 is also the initial length of the git has abbreviation 37 // 38 // It gives a probability of collision of 1 percent 39 // for 24 pages creation by day over a period of 100 year 40 // (You need to create 876k pages). 41 // with the 36 alphabet 42 // https://datacadamia.com/crypto/hash/collision 43 44 45 const TYPE = "page"; 46 47 48 /** 49 * The id requested (ie the main page) 50 * The page may be a slot 51 * @var string 52 */ 53 private $requestedId; 54 /** 55 * @var DatabasePageRow 56 */ 57 private $databasePage; 58 /** 59 * @var Canonical 60 */ 61 private $canonical; 62 /** 63 * @var PageH1 64 */ 65 private $h1; 66 /** 67 * @var ResourceName 68 */ 69 private $pageName; 70 /** 71 * @var PageType 72 */ 73 private $type; 74 /** 75 * @var PageTitle $title 76 */ 77 private $title; 78 79 /** 80 * @var LowQualityPageOverwrite 81 */ 82 private $canBeOfLowQuality; 83 /** 84 * @var Region 85 */ 86 private $region; 87 /** 88 * @var Lang 89 */ 90 private $lang; 91 /** 92 * @var PageId 93 */ 94 private $pageId; 95 96 /** 97 * @var LowQualityCalculatedIndicator 98 */ 99 private $lowQualityIndicatorCalculated; 100 101 /** 102 * @var PageLayout 103 */ 104 private $layout; 105 /** 106 * @var Aliases 107 */ 108 private $aliases; 109 /** 110 * @var Slug a slug path 111 */ 112 private $slug; 113 114 115 /** 116 * @var QualityDynamicMonitoringOverwrite 117 */ 118 private $qualityMonitoringIndicator; 119 120 /** 121 * @var string the alias used to build this page 122 */ 123 private $buildAliasPath; 124 /** 125 * @var PagePublicationDate 126 */ 127 private $publishedDate; 128 /** 129 * @var StartDate 130 */ 131 private $startDate; 132 /** 133 * @var EndDate 134 */ 135 private $endDate; 136 /** 137 * @var PageImages 138 */ 139 private $pageImages; 140 /** 141 * @var PageKeywords 142 */ 143 private $keywords; 144 /** 145 * @var CacheExpirationFrequency 146 */ 147 private $cacheExpirationFrequency; 148 /** 149 * @var CacheExpirationDate 150 */ 151 private $cacheExpirationDate; 152 /** 153 * 154 * @var LdJson 155 */ 156 private $ldJson; 157 /** 158 * @var HtmlDocument 159 */ 160 private $htmlDocument; 161 /** 162 * @var InstructionsDocument 163 */ 164 private $instructionsDocument; 165 166 private $dokuPath; 167 /** 168 * @var PageDescription $description 169 */ 170 private $description; 171 /** 172 * @var PageCreationDate 173 */ 174 private $creationTime; 175 /** 176 * @var Locale 177 */ 178 private $locale; 179 /** 180 * @var ModificationDate 181 */ 182 private $modifiedTime; 183 /** 184 * @var PageUrlPath 185 */ 186 private $pageUrlPath; 187 /** 188 * @var MetadataStore|string 189 */ 190 private $readStore; 191 192 /** 193 * Page constructor. 194 * @param $absolutePath - the qualified path (may be not relative) 195 * 196 */ 197 public function __construct($absolutePath) 198 { 199 200 $this->dokuPath = DokuPath::createPagePathFromPath($absolutePath); 201 202 if ($this->isSecondarySlot()) { 203 204 /** 205 * Used when we want to remove the cache of slots for a requested page 206 * (ie {@link Cache::removeSideSlotCache()}) 207 * 208 * The $absolutePath is the logical path and may not exists 209 * 210 * Find the first physical file 211 * Don't use ACL otherwise the ACL protection event 'AUTH_ACL_CHECK' will kick in 212 * and we got then a recursive problem 213 * with the {@link \action_plugin_combo_pageprotection} 214 */ 215 $useAcl = false; 216 $id = page_findnearest($this->dokuPath->getLastNameWithoutExtension(), $useAcl); 217 if ($id !== false && $id !== $this->dokuPath->getDokuwikiId()) { 218 $absolutePath = DokuPath::PATH_SEPARATOR . $id; 219 $this->dokuPath = DokuPath::createPagePathFromPath($absolutePath); 220 } 221 222 } 223 224 global $ID; 225 $this->requestedId = $ID; 226 227 $this->buildPropertiesFromFileSystem(); 228 229 } 230 231 public static function createPageFromGlobalDokuwikiId(): Page 232 { 233 global $ID; 234 if ($ID === null) { 235 LogUtility::msg("The global wiki ID is null, unable to instantiate a page"); 236 } 237 return self::createPageFromId($ID); 238 } 239 240 public static function createPageFromId($id): Page 241 { 242 DokuPath::addRootSeparatorIfNotPresent($id); 243 return new Page($id); 244 } 245 246 public static function createPageFromNonQualifiedPath($pathOrId): Page 247 { 248 global $ID; 249 $qualifiedId = $pathOrId; 250 resolve_pageid(getNS($ID), $qualifiedId, $exists); 251 /** 252 * Root correction 253 * yeah no root functionality in the {@link resolve_pageid resolution} 254 * meaning that we get an empty string 255 * they correct it in the link creation {@link wl()} 256 */ 257 if ($qualifiedId === '') { 258 global $conf; 259 $qualifiedId = $conf['start']; 260 } 261 return Page::createPageFromId($qualifiedId); 262 263 } 264 265 /** 266 * @return Page - the requested page 267 */ 268 public static function createPageFromRequestedPage(): Page 269 { 270 $pageId = PluginUtility::getRequestedWikiId(); 271 if ($pageId === null) { 272 $pageId = RenderUtility::DEFAULT_SLOT_ID_FOR_TEST; 273 if(!PluginUtility::isTest()) { 274 // should never happen, we don't throw an exception 275 LogUtility::msg("We were unable to determine the requested page from the variables environment, default non-existing page id used"); 276 } 277 } 278 return Page::createPageFromId($pageId); 279 } 280 281 282 public static function getHomePageFromNamespace(string $namespacePath): Page 283 { 284 global $conf; 285 286 if ($namespacePath != ":") { 287 $namespacePath = $namespacePath . ":"; 288 } 289 290 $startPageName = $conf['start']; 291 if (page_exists($namespacePath . $startPageName)) { 292 // start page inside namespace 293 return self::createPageFromId($namespacePath . $startPageName); 294 } elseif (page_exists($namespacePath . noNS(cleanID($namespacePath)))) { 295 // page named like the NS inside the NS 296 return self::createPageFromId($namespacePath . noNS(cleanID($namespacePath))); 297 } elseif (page_exists($namespacePath)) { 298 // page like namespace exists 299 return self::createPageFromId(substr($namespacePath, 0, -1)); 300 } else { 301 // Does not exist but can be used by hierarchical function 302 return self::createPageFromId($namespacePath . $startPageName); 303 } 304 } 305 306 307 static function createPageFromQualifiedPath($qualifiedPath): Page 308 { 309 return new Page($qualifiedPath); 310 } 311 312 313 /** 314 * 315 * @throws ExceptionCombo 316 */ 317 public 318 function setCanonical($canonical): Page 319 { 320 $this->canonical 321 ->setValue($canonical) 322 ->sendToWriteStore(); 323 return $this; 324 } 325 326 327 /** 328 * @return bool true if this is not the main slot. 329 */ 330 public function isSecondarySlot(): bool 331 { 332 $slotNames = Site::getSecondarySlotNames(); 333 $name = $this->getPath()->getLastNameWithoutExtension(); 334 if ($name === null) { 335 // root case 336 return false; 337 } 338 return in_array($name, $slotNames, true); 339 } 340 341 /** 342 * @return bool true if this is the main 343 */ 344 public function isMainHeaderFooterSlot(): bool 345 { 346 347 try { 348 $slotNames = [Site::getMainHeaderSlotName(), Site::getMainFooterSlotName()]; 349 } catch (ExceptionCombo $e) { 350 return false; 351 } 352 $name = $this->getPath()->getLastNameWithoutExtension(); 353 if ($name === null) { 354 // root case 355 return false; 356 } 357 return in_array($name, $slotNames, true); 358 } 359 360 361 /** 362 * 363 * @return bool 364 * Used to delete the part path of a page for default name or canonical value 365 */ 366 public 367 function isStartPage(): bool 368 { 369 $startPageName = Site::getHomePageName(); 370 return $this->getPath()->getLastName() === $startPageName; 371 } 372 373 /** 374 * Return a canonical if set 375 * otherwise derive it from the id 376 * by taking the last two parts 377 * 378 * @return string 379 * @deprecated for {@link Canonical::getValueOrDefault()} 380 */ 381 public 382 function getCanonicalOrDefault(): ?string 383 { 384 return $this->canonical->getValueFromStoreOrDefault(); 385 386 } 387 388 389 /** 390 * Rebuild the page 391 * (refresh from disk, reset object to null) 392 * @return $this 393 */ 394 public 395 function rebuild(): Page 396 { 397 $this->readStore = null; 398 $this->buildPropertiesFromFileSystem(); 399 $this->databasePage = null; 400 $this->htmlDocument = null; 401 $this->instructionsDocument = null; 402 return $this; 403 } 404 405 /** 406 * 407 * @return Page[]|null the internal links or null 408 */ 409 public 410 function getLinkReferences(): ?array 411 { 412 $store = $this->getReadStoreOrDefault(); 413 if (!($store instanceof MetadataDokuWikiStore)) { 414 return null; 415 } 416 $metadata = $store->getCurrentFromName('relation'); 417 if ($metadata === null) { 418 /** 419 * Happens when no rendering has been made 420 */ 421 return null; 422 } 423 if (!key_exists('references', $metadata)) { 424 return null; 425 } 426 427 $pages = []; 428 foreach (array_keys($metadata['references']) as $referencePageId) { 429 $pages[$referencePageId] = Page::createPageFromId($referencePageId); 430 } 431 return $pages; 432 433 } 434 435 436 public 437 function getHtmlDocument(): HtmlDocument 438 { 439 if ($this->htmlDocument === null) { 440 $this->htmlDocument = new HtmlDocument($this); 441 } 442 return $this->htmlDocument; 443 444 } 445 446 /** 447 * Set the page quality 448 * @param boolean $value true if this is a low quality page rank false otherwise 449 * @throws ExceptionCombo 450 */ 451 public 452 function setCanBeOfLowQuality(bool $value): Page 453 { 454 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->canBeOfLowQuality, $value); 455 } 456 457 /** 458 * @return Page[] the backlinks 459 * Duplicate of related 460 * 461 * Same as {@link DokuPath::getReferencedBy()} ? 462 */ 463 public 464 function getBacklinks(): array 465 { 466 $backlinks = array(); 467 /** 468 * Same as 469 * idx_get_indexer()->lookupKey('relation_references', $ID); 470 */ 471 $ft_backlinks = ft_backlinks($this->getDokuwikiId()); 472 foreach ($ft_backlinks as $backlinkId) { 473 $backlinks[$backlinkId] = Page::createPageFromId($backlinkId); 474 } 475 return $backlinks; 476 } 477 478 479 /** 480 * Low page quality 481 * @return bool true if this is a low quality page 482 */ 483 function isLowQualityPage(): bool 484 { 485 486 $canBeOfLowQuality = $this->getCanBeOfLowQuality(); 487 if ($canBeOfLowQuality === false) { 488 return false; 489 } 490 if (!Site::isLowQualityProtectionEnable()) { 491 return false; 492 } 493 return $this->getLowQualityIndicatorCalculated(); 494 495 496 } 497 498 499 public 500 function getCanBeOfLowQuality(): ?bool 501 { 502 503 return $this->canBeOfLowQuality->getValue(); 504 505 } 506 507 508 public 509 function getH1(): ?string 510 { 511 512 return $this->h1->getValueFromStore(); 513 514 } 515 516 /** 517 * Return the Title 518 * @deprecated for {@link PageTitle::getValue()} 519 */ 520 public 521 function getTitle(): ?string 522 { 523 return $this->title->getValueFromStore(); 524 } 525 526 /** 527 * If true, the page is quality monitored (a note is shown to the writer) 528 * @return null|bool 529 */ 530 public 531 function getQualityMonitoringIndicator(): ?bool 532 { 533 return $this->qualityMonitoringIndicator->getValueFromStore(); 534 } 535 536 /** 537 * @return string the title, or h1 if empty or the id if empty 538 * Shortcut to {@link PageTitle::getValueOrDefault()} 539 */ 540 public 541 function getTitleOrDefault(): ?string 542 { 543 return $this->title->getValueFromStoreOrDefault(); 544 } 545 546 /** 547 * @return mixed 548 * @deprecated for {@link PageH1::getValueOrDefault()} 549 */ 550 public 551 function getH1OrDefault() 552 { 553 554 return $this->h1->getValueFromStoreOrDefault(); 555 556 557 } 558 559 /** 560 * @return mixed 561 */ 562 public 563 function getDescription(): ?string 564 { 565 return $this->description->getValueFromStore(); 566 } 567 568 569 /** 570 * @return string - the description or the dokuwiki generated description 571 */ 572 public 573 function getDescriptionOrElseDokuWiki(): ?string 574 { 575 return $this->description->getValueFromStoreOrDefault(); 576 } 577 578 579 /** 580 * @return string 581 * A wrapper around {@link FileSystems::getContent()} with {@link DokuPath} 582 */ 583 public 584 function getTextContent(): string 585 { 586 /** 587 * 588 * use {@link io_readWikiPage(wikiFN($id, $rev), $id, $rev)}; 589 */ 590 return rawWiki($this->getPath()->getDokuwikiId()); 591 } 592 593 594 public 595 function isInIndex() 596 { 597 $Indexer = idx_get_indexer(); 598 $pages = $Indexer->getPages(); 599 $return = array_search($this->getPath()->getDokuwikiId(), $pages, true); 600 return $return !== false; 601 } 602 603 604 public 605 function upsertContent($content, $summary = "Default"): Page 606 { 607 saveWikiText($this->getPath()->getDokuwikiId(), $content, $summary); 608 return $this; 609 } 610 611 public 612 function addToIndex() 613 { 614 /** 615 * Add to index check the metadata cache 616 * Because we log the cache at the requested page level, we need to 617 * set the global ID 618 */ 619 global $ID; 620 $keep = $ID; 621 try { 622 $ID = $this->getPath()->getDokuwikiId(); 623 idx_addPage($ID); 624 } finally { 625 $ID = $keep; 626 } 627 return $this; 628 629 } 630 631 /** 632 * @return mixed 633 */ 634 public 635 function getTypeOrDefault() 636 { 637 return $this->type->getValueFromStoreOrDefault(); 638 } 639 640 641 public 642 function getFirstImage() 643 { 644 return $this->pageImages->getFirstImage(); 645 } 646 647 /** 648 * Return the media stored during parsing 649 * 650 * They are saved via the function {@link \Doku_Renderer_metadata::_recordMediaUsage()} 651 * called by the {@link \Doku_Renderer_metadata::internalmedia()} 652 * 653 * 654 * {@link \Doku_Renderer_metadata::externalmedia()} does not save them 655 */ 656 public 657 function getMediasMetadata(): ?array 658 { 659 660 $store = $this->getReadStoreOrDefault(); 661 if (!($store instanceof MetadataDokuWikiStore)) { 662 return null; 663 } 664 $medias = []; 665 666 $relation = $store->getCurrentFromName('relation'); 667 if (isset($relation['media'])) { 668 /** 669 * The relation is 670 * $this->meta['relation']['media'][$src] = $exists; 671 * 672 */ 673 foreach ($relation['media'] as $src => $exists) { 674 if ($exists) { 675 $medias[] = $src; 676 } 677 } 678 } 679 return $medias; 680 } 681 682 /** 683 * An array of local/internal images that represents the same image 684 * but in different dimension and ratio 685 * (may be empty) 686 * @return PageImage[] 687 */ 688 public 689 function getPageImagesOrDefault(): array 690 { 691 692 return $this->pageImages->getValueAsPageImagesOrDefault(); 693 694 } 695 696 697 /** 698 * @return Image 699 */ 700 public 701 function getImage(): ?Image 702 { 703 704 $images = $this->getPageImagesOrDefault(); 705 if (sizeof($images) >= 1) { 706 return $images[0]->getImage(); 707 } else { 708 return null; 709 } 710 711 } 712 713 /** 714 * Get author name 715 * 716 * @return string 717 */ 718 public 719 function getAuthor(): ?string 720 { 721 $store = $this->getReadStoreOrDefault(); 722 if (!($store instanceof MetadataDokuWikiStore)) { 723 return null; 724 } 725 726 return $store->getFromPersistentName('creator'); 727 } 728 729 /** 730 * Get author ID 731 * 732 * @return string 733 */ 734 public 735 function getAuthorID(): ?string 736 { 737 738 $store = $this->getReadStoreOrDefault(); 739 if (!($store instanceof MetadataDokuWikiStore)) { 740 return null; 741 } 742 743 return $store->getFromPersistentName('user'); 744 745 } 746 747 748 /** 749 * Get the create date of page 750 * 751 * @return DateTime 752 */ 753 public 754 function getCreatedTime(): ?DateTime 755 { 756 return $this->creationTime->getValueFromStore(); 757 } 758 759 760 /** 761 * 762 * @return null|DateTime 763 */ 764 public 765 function getModifiedTime(): ?\DateTime 766 { 767 return $this->modifiedTime->getValueFromStore(); 768 } 769 770 public 771 function getModifiedTimeOrDefault(): ?\DateTime 772 { 773 return $this->modifiedTime->getValueFromStoreOrDefault(); 774 } 775 776 777 /** 778 * Refresh the metadata (used only in test) 779 * 780 * Trigger a: 781 * a {@link p_render_metadata() metadata render} 782 * a {@link p_save_metadata() metadata save} 783 * 784 * Note that {@link p_get_metadata()} uses a strange recursion 785 * There is a metadata recursion logic to avoid rendering 786 * that is not easy to grasp 787 * and therefore you may get no metadata and no backlinks 788 */ 789 public 790 function renderMetadataAndFlush(): Page 791 { 792 793 if (!$this->exists()) { 794 if (PluginUtility::isDevOrTest()) { 795 LogUtility::msg("You can't render the metadata of a page that does not exist"); 796 } 797 return $this; 798 } 799 800 /** 801 * @var MetadataDokuWikiStore $metadataStore 802 */ 803 $metadataStore = $this->getReadStoreOrDefault(); 804 $metadataStore->renderAndPersist(); 805 806 /** 807 * Return 808 */ 809 return $this; 810 811 } 812 813 /** 814 * @return string|null 815 * @deprecated for {@link Region} 816 */ 817 public 818 function getLocaleRegion(): ?string 819 { 820 return $this->region->getValueFromStore(); 821 } 822 823 public 824 function getRegionOrDefault() 825 { 826 827 return $this->region->getValueFromStoreOrDefault(); 828 829 } 830 831 public 832 function getLang(): ?string 833 { 834 return $this->lang->getValueFromStore(); 835 } 836 837 public 838 function getLangOrDefault() 839 { 840 841 return $this->lang->getValueFromStoreOrDefault(); 842 } 843 844 /** 845 * Adapted from {@link FsWikiUtility::getHomePagePath()} 846 * @return bool 847 */ 848 public 849 function isHomePage(): bool 850 { 851 global $conf; 852 $startPageName = $conf['start']; 853 if ($this->getPath()->getLastName() == $startPageName) { 854 return true; 855 } else { 856 $namespace = $this->dokuPath->getParent(); 857 if ($namespace->getLastName() === $this->getPath()->getLastName()) { 858 /** 859 * page named like the NS inside the NS 860 * ie ns:ns 861 */ 862 $startPage = Page::createPageFromId($namespace->getDokuwikiId() . DokuPath::PATH_SEPARATOR . $startPageName); 863 if (!$startPage->exists()) { 864 return true; 865 } 866 } 867 } 868 return false; 869 } 870 871 872 public 873 function getPublishedTime(): ?DateTime 874 { 875 return $this->publishedDate->getValueFromStore(); 876 } 877 878 879 /** 880 * @return DateTime 881 */ 882 public 883 function getPublishedElseCreationTime(): ?DateTime 884 { 885 return $this->publishedDate->getValueFromStoreOrDefault(); 886 } 887 888 889 public 890 function isLatePublication(): bool 891 { 892 $dateTime = $this->getPublishedElseCreationTime(); 893 return $dateTime > new DateTime('now'); 894 } 895 896 /** 897 * The unique page Url (also known as Canonical URL) used: 898 * * in the link 899 * * in the canonical ref 900 * * in the site map 901 * @param array $urlParameters 902 * @param bool $absoluteUrlMandatory - by default, dokuwiki allows the canonical to be relative but it's mandatory to be absolute for the HTML meta 903 * @param string $separator - TODO: delete. HTML encoded or not ampersand (the default should always be good because the encoding is done just before printing (ie {@link TagAttributes::encodeToHtmlValue()}) 904 * @return string|null 905 */ 906 public 907 function getCanonicalUrl(array $urlParameters = [], bool $absoluteUrlMandatory = false, string $separator = DokuwikiUrl::AMPERSAND_CHARACTER): ?string 908 { 909 910 /** 911 * Conf 912 */ 913 $urlType = PageUrlType::getOrCreateForPage($this)->getValue(); 914 if ($urlType === PageUrlType::CONF_VALUE_PAGE_PATH && $absoluteUrlMandatory == false) { 915 $absoluteUrlMandatory = Site::shouldUrlBeAbsolute(); 916 } 917 918 /** 919 * Dokuwiki Methodology Taken from {@link tpl_metaheaders()} 920 */ 921 if ($absoluteUrlMandatory && $this->isRootHomePage()) { 922 return DOKU_URL; 923 } 924 925 return wl($this->getUrlId(), $urlParameters, $absoluteUrlMandatory, $separator); 926 927 928 } 929 930 public function getUrl($type = null): ?string 931 { 932 if ($type === null) { 933 return $this->getCanonicalUrl(); 934 } 935 $pageUrlId = DokuPath::toDokuwikiId(PageUrlPath::createForPage($this) 936 ->getUrlPathFromType($type)); 937 return wl($pageUrlId); 938 } 939 940 941 /** 942 * 943 * @return string|null - the locale facebook way 944 * @deprecated for {@link Locale} 945 */ 946 public 947 function getLocale($default = null): ?string 948 { 949 $value = $this->locale->getValueFromStore(); 950 if ($value === null) { 951 return $default; 952 } 953 return $value; 954 } 955 956 957 /** 958 * 959 */ 960 public 961 function toXhtml(): ?string 962 { 963 964 return $this->getHtmlDocument()->getOrProcessContent(); 965 966 } 967 968 969 public 970 function getHtmlAnchorLink($logicalTag = null): string 971 { 972 $id = $this->getPath()->getDokuwikiId(); 973 try { 974 return MarkupRef::createFromPageId($id) 975 ->toAttributes($logicalTag) 976 ->toHtmlEnterTag("a") 977 . $this->getNameOrDefault() 978 . "</a>"; 979 } catch (ExceptionCombo $e) { 980 LogUtility::msg("The markup ref returns an error for the creation of the page anchor html link ($this). Error: {$e->getMessage()}"); 981 return "<a href=\"{$this->getCanonicalUrl()}\" data-wiki-id=\"$id\">{$this->getNameOrDefault()}</a>"; 982 } 983 } 984 985 986 /** 987 * Without the `:` at the end 988 * @return string 989 * @deprecated / shortcut for {@link DokuPath::getParent()} 990 * Because a page has always a parent, the string is never null. 991 */ 992 public 993 function getNamespacePath(): string 994 { 995 996 return $this->dokuPath->getParent()->toString(); 997 998 } 999 1000 1001 /** 1002 * @return $this 1003 * @deprecated use {@link MetadataDokuWikiStore::deleteAndFlush()} 1004 */ 1005 public 1006 function deleteMetadatasAndFlush(): Page 1007 { 1008 MetadataDokuWikiStore::getOrCreateFromResource($this) 1009 ->deleteAndFlush(); 1010 return $this; 1011 } 1012 1013 public 1014 function getName(): ?string 1015 { 1016 1017 return $this->pageName->getValueFromStore(); 1018 1019 } 1020 1021 public 1022 function getNameOrDefault(): string 1023 { 1024 return $this->pageName->getValueFromStoreOrDefault(); 1025 } 1026 1027 /** 1028 * @param $property 1029 */ 1030 public 1031 function unsetMetadata($property) 1032 { 1033 $meta = p_read_metadata($this->getPath()->getDokuwikiId()); 1034 if (isset($meta['persistent'][$property])) { 1035 unset($meta['persistent'][$property]); 1036 } 1037 p_save_metadata($this->getPath()->getDokuwikiId(), $meta); 1038 1039 } 1040 1041 /** 1042 * @return array - return the standard / generated metadata 1043 * used in templating with the value or default 1044 * TODO: should move in the templating class 1045 */ 1046 public 1047 function getMetadataForRendering(): array 1048 { 1049 1050 $metadataNames = [ 1051 PageH1::PROPERTY_NAME, 1052 PageTitle::TITLE, 1053 PageId::PROPERTY_NAME, 1054 Canonical::PROPERTY_NAME, 1055 PagePath::PROPERTY_NAME, 1056 PageDescription::PROPERTY_NAME, 1057 ResourceName::PROPERTY_NAME, 1058 PageType::PROPERTY_NAME, 1059 Slug::PROPERTY_NAME, 1060 PageCreationDate::PROPERTY_NAME, 1061 ModificationDate::PROPERTY_NAME, 1062 PagePublicationDate::PROPERTY_NAME, 1063 StartDate::PROPERTY_NAME, 1064 EndDate::PROPERTY_NAME, 1065 PageLayout::PROPERTY_NAME, 1066 // Dokuwiki id is deprecated for path, no more advertised 1067 DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, 1068 PageKeywords::PROPERTY_NAME 1069 ]; 1070 1071 foreach ($metadataNames as $metadataName) { 1072 $metadata = Metadata::getForName($metadataName); 1073 if ($metadata === null) { 1074 LogUtility::msg("The metadata ($metadata) should be defined"); 1075 continue; 1076 } 1077 /** 1078 * The Value or Default is returned 1079 * 1080 * Because the title/h1 should never be null 1081 * otherwise a template link such as [[$path|$title]] will return a link without an description 1082 * and therefore will be not visible 1083 * 1084 * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility} 1085 * If we want the native value, we need to change the pipeline 1086 */ 1087 $value = $metadata 1088 ->setResource($this) 1089 ->setWriteStore(TemplateStore::class) 1090 ->toStoreValueOrDefault(); 1091 if ($metadata->getDataType() === DataType::TEXT_TYPE_VALUE) { 1092 1093 /** 1094 * Hack: Replace every " by a ' to be able to detect/parse the title/h1 on a pipeline 1095 * @see {@link \syntax_plugin_combo_pipeline} 1096 */ 1097 $value = str_replace('"', "'", $value); 1098 } 1099 $array[$metadataName] = $value; 1100 } 1101 $array["url"] = $this->getCanonicalUrl(); 1102 return $array; 1103 1104 } 1105 1106 public 1107 function __toString() 1108 { 1109 return $this->dokuPath->toUriString(); 1110 } 1111 1112 1113 public 1114 function getPublishedTimeAsString(): ?string 1115 { 1116 return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null; 1117 } 1118 1119 public 1120 function getEndDateAsString(): ?string 1121 { 1122 return $this->getEndDate() !== null ? $this->getEndDate()->format(Iso8601Date::getFormat()) : null; 1123 } 1124 1125 public 1126 function getEndDate(): ?DateTime 1127 { 1128 return $this->endDate->getValueFromStore(); 1129 } 1130 1131 public 1132 function getStartDateAsString(): ?string 1133 { 1134 return $this->getStartDate() !== null ? $this->getStartDate()->format(Iso8601Date::getFormat()) : null; 1135 } 1136 1137 public 1138 function getStartDate(): ?DateTime 1139 { 1140 return $this->startDate->getValueFromStore(); 1141 } 1142 1143 /** 1144 * A page id or null if the page id does not exists 1145 * @return string|null 1146 */ 1147 public 1148 function getPageId(): ?string 1149 { 1150 1151 return $this->pageId->getValue(); 1152 1153 } 1154 1155 1156 public 1157 function getAnalyticsDocument(): AnalyticsDocument 1158 { 1159 return new AnalyticsDocument($this); 1160 } 1161 1162 public 1163 function getDatabasePage(): DatabasePageRow 1164 { 1165 if ($this->databasePage == null) { 1166 $this->databasePage = DatabasePageRow::createFromPageObject($this); 1167 } 1168 return $this->databasePage; 1169 } 1170 1171 public 1172 function canBeUpdatedByCurrentUser(): bool 1173 { 1174 return Identity::isWriter($this->getDokuwikiId()); 1175 } 1176 1177 1178 public 1179 function isRootHomePage(): bool 1180 { 1181 global $conf; 1182 $startPageName = $conf['start']; 1183 return $this->getPath()->toString() === ":$startPageName"; 1184 1185 } 1186 1187 /** 1188 * Used when the page is moved to take the Page Id of the source 1189 * @param string|null $pageId 1190 * @return Page 1191 * @throws ExceptionCombo 1192 */ 1193 public 1194 function setPageId(?string $pageId): Page 1195 { 1196 1197 $this->pageId 1198 ->setValue($pageId) 1199 ->sendToWriteStore(); 1200 1201 return $this; 1202 1203 } 1204 1205 public 1206 function getPageType(): ?string 1207 { 1208 return $this->type->getValueFromStore(); 1209 } 1210 1211 public 1212 function getCanonical(): ?string 1213 { 1214 return $this->canonical->getValueFromStore(); 1215 } 1216 1217 /** 1218 * Create a canonical from the last page path part. 1219 * 1220 * @return string|null 1221 */ 1222 public 1223 function getDefaultCanonical(): ?string 1224 { 1225 return $this->canonical->getDefaultValue(); 1226 } 1227 1228 public 1229 function getLayout() 1230 { 1231 return $this->layout->getValueFromStore(); 1232 } 1233 1234 public 1235 function getDefaultPageName(): string 1236 { 1237 return $this->pageName->getDefaultValue(); 1238 } 1239 1240 public 1241 function getDefaultTitle(): ?string 1242 { 1243 return $this->title->getDefaultValue(); 1244 } 1245 1246 public 1247 function getDefaultH1() 1248 { 1249 return $this->h1->getValueOrDefault(); 1250 } 1251 1252 public 1253 function getDefaultType(): string 1254 { 1255 return $this->type->getDefaultValue(); 1256 } 1257 1258 public 1259 function getDefaultLayout(): string 1260 { 1261 return $this->layout->getDefaultValue(); 1262 } 1263 1264 1265 /** 1266 * @throws ExceptionCombo 1267 */ 1268 public 1269 function setLowQualityIndicatorCalculation($bool): Page 1270 { 1271 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool); 1272 } 1273 1274 1275 /** 1276 * Change the quality indicator 1277 * and if the quality level has become low 1278 * and that the protection is on, delete the cache 1279 * @param MetadataBoolean $lowQualityAttributeName 1280 * @param bool $value 1281 * @return Page 1282 * @throws ExceptionCombo 1283 */ 1284 private 1285 function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): Page 1286 { 1287 $actualValue = $lowQualityAttributeName->getValue(); 1288 if ($actualValue === null || $value !== $actualValue) { 1289 $lowQualityAttributeName 1290 ->setValue($value) 1291 ->persist(); 1292 } 1293 return $this; 1294 } 1295 1296 1297 public 1298 function getLowQualityIndicatorCalculated() 1299 { 1300 1301 return $this->lowQualityIndicatorCalculated->getValueOrDefault(); 1302 1303 } 1304 1305 /** 1306 * @return PageImage[] 1307 */ 1308 public 1309 function getPageImages(): ?array 1310 { 1311 return $this->pageImages->getValueAsPageImages(); 1312 } 1313 1314 1315 /** 1316 * @return array|null 1317 * @deprecated for {@link LdJson} 1318 */ 1319 public 1320 function getLdJson(): ?string 1321 { 1322 return $this->ldJson->getValue(); 1323 1324 } 1325 1326 /** 1327 * @param array|string $jsonLd 1328 * @return $this 1329 * @throws ExceptionCombo 1330 * @deprecated for {@link LdJson} 1331 */ 1332 public 1333 function setJsonLd($jsonLd): Page 1334 { 1335 $this->ldJson 1336 ->setValue($jsonLd) 1337 ->sendToWriteStore(); 1338 return $this; 1339 } 1340 1341 /** 1342 * @throws ExceptionCombo 1343 */ 1344 public 1345 function setPageType(string $value): Page 1346 { 1347 $this->type 1348 ->setValue($value) 1349 ->sendToWriteStore(); 1350 return $this; 1351 } 1352 1353 1354 /** 1355 * @param $aliasPath 1356 * @param string $aliasType 1357 * @return Alias 1358 * @deprecated for {@link Aliases} 1359 */ 1360 public 1361 function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias 1362 { 1363 1364 return $this->aliases->addAndGetAlias($aliasPath, $aliasType); 1365 1366 } 1367 1368 1369 /** 1370 * @return Alias[] 1371 */ 1372 public 1373 function getAliases(): ?array 1374 { 1375 return $this->aliases->getValueAsAlias(); 1376 } 1377 1378 /** 1379 * @return string|null 1380 * 1381 */ 1382 public 1383 function getSlugOrDefault(): ?string 1384 { 1385 1386 if ($this->getSlug() !== null) { 1387 return $this->getSlug(); 1388 } 1389 return $this->getDefaultSlug(); 1390 } 1391 1392 /** 1393 * 1394 * @return string|null 1395 * 1396 */ 1397 public 1398 function getDefaultSlug(): ?string 1399 { 1400 return $this->slug->getDefaultValue(); 1401 } 1402 1403 /** 1404 * The parent page is the parent in the page tree directory 1405 * 1406 * If the page is at the root, the parent page is the root home 1407 * Only the root home does not have any parent page and return null. 1408 * 1409 * @return Page|null 1410 */ 1411 public 1412 function getParentPage(): ?Page 1413 { 1414 1415 $names = $this->getPath()->getNames(); 1416 if (sizeof($names) == 0) { 1417 return null; 1418 } 1419 $slice = 1; 1420 if ($this->isHomePage()) { 1421 /** 1422 * The parent of a home page 1423 * is in the parent directory 1424 */ 1425 $slice = 2; 1426 } 1427 /** 1428 * Delete the last or the two last parts 1429 */ 1430 if (sizeof($names) < $slice) { 1431 return null; 1432 } 1433 /** 1434 * Get the actual directory for a page 1435 * or the parent directory for a home page 1436 */ 1437 $parentNames = array_slice($names, 0, sizeof($names) - $slice); 1438 /** 1439 * Create the parent namespace id 1440 */ 1441 $parentNamespaceId = implode(DokuPath::PATH_SEPARATOR, $parentNames); 1442 return self::getHomePageFromNamespace($parentNamespaceId); 1443 1444 } 1445 1446 /** 1447 * @throws ExceptionCombo 1448 */ 1449 public 1450 function setDescription($description): Page 1451 { 1452 1453 $this->description 1454 ->setValue($description) 1455 ->sendToWriteStore(); 1456 return $this; 1457 } 1458 1459 /** 1460 * @throws ExceptionCombo 1461 * @deprecated uses {@link EndDate} instead 1462 */ 1463 public 1464 function setEndDate($value): Page 1465 { 1466 $this->endDate 1467 ->setFromStoreValue($value) 1468 ->sendToWriteStore(); 1469 return $this; 1470 } 1471 1472 /** 1473 * @throws ExceptionCombo 1474 * @deprecated uses {@link StartDate} instead 1475 */ 1476 public 1477 function setStartDate($value): Page 1478 { 1479 $this->startDate 1480 ->setFromStoreValue($value) 1481 ->sendToWriteStore(); 1482 return $this; 1483 } 1484 1485 /** 1486 * @throws ExceptionCombo 1487 */ 1488 public 1489 function setPublishedDate($value): Page 1490 { 1491 $this->publishedDate 1492 ->setFromStoreValue($value) 1493 ->sendToWriteStore(); 1494 return $this; 1495 } 1496 1497 /** 1498 * Utility to {@link ResourceName::setValue()} 1499 * Used mostly to create page in test 1500 * @throws ExceptionCombo 1501 */ 1502 public 1503 function setPageName($value): Page 1504 { 1505 $this->pageName 1506 ->setValue($value) 1507 ->sendToWriteStore(); 1508 return $this; 1509 } 1510 1511 1512 /** 1513 * @throws ExceptionCombo 1514 */ 1515 public 1516 function setTitle($value): Page 1517 { 1518 $this->title 1519 ->setValue($value) 1520 ->sendToWriteStore(); 1521 return $this; 1522 } 1523 1524 /** 1525 * @throws ExceptionCombo 1526 */ 1527 public 1528 function setH1($value): Page 1529 { 1530 $this->h1 1531 ->setValue($value) 1532 ->sendToWriteStore(); 1533 return $this; 1534 } 1535 1536 /** 1537 * @throws Exception 1538 */ 1539 public 1540 function setRegion($value): Page 1541 { 1542 $this->region 1543 ->setFromStoreValue($value) 1544 ->sendToWriteStore(); 1545 return $this; 1546 } 1547 1548 /** 1549 * @throws ExceptionCombo 1550 */ 1551 public 1552 function setLang($value): Page 1553 { 1554 1555 $this->lang 1556 ->setFromStoreValue($value) 1557 ->sendToWriteStore(); 1558 return $this; 1559 } 1560 1561 /** 1562 * @throws ExceptionCombo 1563 */ 1564 public 1565 function setLayout($value): Page 1566 { 1567 $this->layout 1568 ->setValue($value) 1569 ->sendToWriteStore(); 1570 return $this; 1571 } 1572 1573 1574 /** 1575 * 1576 * We manage the properties by setter and getter 1577 * 1578 * Why ? 1579 * * Because we can capture the updates 1580 * * Because setter are the entry point to good quality data 1581 * * Because dokuwiki may cache the metadata (see below) 1582 * 1583 * Note all properties have been migrated 1584 * but they should be initialized below 1585 * 1586 * Dokuwiki cache: the data may be cached without our consent 1587 * The method {@link p_get_metadata()} does it with this logic 1588 * ``` 1589 * $cache = ($ID == $id); 1590 * $meta = p_read_metadata($id, $cache); 1591 * ``` 1592 */ 1593 private 1594 function buildPropertiesFromFileSystem() 1595 { 1596 1597 /** 1598 * New meta system 1599 * Even if it does not exist, the metadata object should be instantiated 1600 * otherwise, there is a null exception 1601 */ 1602 $this->cacheExpirationDate = CacheExpirationDate::createForPage($this); 1603 $this->aliases = Aliases::createForPage($this); 1604 $this->pageImages = PageImages::createForPage($this); 1605 $this->pageName = ResourceName::createForResource($this); 1606 $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this); 1607 $this->ldJson = LdJson::createForPage($this); 1608 $this->canonical = Canonical::createForPage($this); 1609 $this->pageId = PageId::createForPage($this); 1610 $this->description = PageDescription::createForPage($this); 1611 $this->h1 = PageH1::createForPage($this); 1612 $this->type = PageType::createForPage($this); 1613 $this->creationTime = PageCreationDate::createForPage($this); 1614 $this->title = PageTitle::createForPage($this); 1615 $this->keywords = PageKeywords::createForPage($this); 1616 $this->publishedDate = PagePublicationDate::createFromPage($this); 1617 $this->startDate = StartDate::createFromPage($this); 1618 $this->endDate = EndDate::createFromPage($this); 1619 $this->locale = Locale::createForPage($this); 1620 $this->lang = Lang::createForPage($this); 1621 $this->region = Region::createForPage($this); 1622 $this->slug = Slug::createForPage($this); 1623 $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this); 1624 $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this); 1625 $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this); 1626 $this->modifiedTime = ModificationDate::createForPage($this); 1627 $this->pageUrlPath = PageUrlPath::createForPage($this); 1628 $this->layout = PageLayout::createFromPage($this); 1629 1630 } 1631 1632 1633 function getPageIdAbbr() 1634 { 1635 1636 if ($this->getPageId() === null) return null; 1637 return substr($this->getPageId(), 0, PageId::PAGE_ID_ABBREV_LENGTH); 1638 1639 } 1640 1641 public 1642 function setDatabasePage(DatabasePageRow $databasePage): Page 1643 { 1644 $this->databasePage = $databasePage; 1645 return $this; 1646 } 1647 1648 /** 1649 * 1650 * TODO: Move to {@link HtmlDocument} ? 1651 */ 1652 public function getUrlPath(): string 1653 { 1654 1655 return $this->pageUrlPath->getValueOrDefault(); 1656 1657 } 1658 1659 1660 /** 1661 * @return string|null 1662 * 1663 */ 1664 public 1665 function getSlug(): ?string 1666 { 1667 return $this->slug->getValue(); 1668 } 1669 1670 1671 /** 1672 * @throws ExceptionCombo 1673 */ 1674 public 1675 function setSlug($slug): Page 1676 { 1677 $this->slug 1678 ->setFromStoreValue($slug) 1679 ->sendToWriteStore(); 1680 return $this; 1681 } 1682 1683 1684 public 1685 function getUrlId() 1686 { 1687 return DokuPath::toDokuwikiId($this->getUrlPath()); 1688 } 1689 1690 1691 /** 1692 * @throws ExceptionCombo 1693 */ 1694 public 1695 function setQualityMonitoringIndicator($boolean): Page 1696 { 1697 $this->qualityMonitoringIndicator 1698 ->setFromStoreValue($boolean) 1699 ->sendToWriteStore(); 1700 return $this; 1701 } 1702 1703 /** 1704 * 1705 * @param $aliasPath - third information - the alias used to build this page 1706 */ 1707 public 1708 function setBuildAliasPath($aliasPath) 1709 { 1710 $this->buildAliasPath = $aliasPath; 1711 } 1712 1713 public 1714 function getBuildAlias(): ?Alias 1715 { 1716 if ($this->buildAliasPath === null) return null; 1717 foreach ($this->getAliases() as $alias) { 1718 if ($alias->getPath() === $this->buildAliasPath) { 1719 return $alias; 1720 } 1721 } 1722 return null; 1723 } 1724 1725 public 1726 function isDynamicQualityMonitored(): bool 1727 { 1728 if ($this->getQualityMonitoringIndicator() !== null) { 1729 return $this->getQualityMonitoringIndicator(); 1730 } 1731 return $this->getDefaultQualityMonitoring(); 1732 } 1733 1734 public 1735 function getDefaultQualityMonitoring(): bool 1736 { 1737 if (PluginUtility::getConfValue(action_plugin_combo_qualitymessage::CONF_DISABLE_QUALITY_MONITORING) === 1) { 1738 return false; 1739 } else { 1740 return true; 1741 } 1742 } 1743 1744 /** 1745 * @param MetadataStore|string $store 1746 * @return $this 1747 */ 1748 public 1749 function setReadStore($store): Page 1750 { 1751 $this->readStore = $store; 1752 return $this; 1753 } 1754 1755 1756 /** 1757 * @param array $usages 1758 * @return Image[] 1759 */ 1760 public 1761 function getImagesOrDefaultForTheFollowingUsages(array $usages): array 1762 { 1763 $usages = array_merge($usages, [PageImageUsage::ALL]); 1764 $images = []; 1765 foreach ($this->getPageImagesOrDefault() as $pageImage) { 1766 foreach ($usages as $usage) { 1767 if (in_array($usage, $pageImage->getUsages())) { 1768 $images[] = $pageImage->getImage(); 1769 continue 2; 1770 } 1771 } 1772 } 1773 return $images; 1774 1775 } 1776 1777 1778 public 1779 function getKeywords(): ?array 1780 { 1781 return $this->keywords->getValue(); 1782 } 1783 1784 public 1785 function getKeywordsOrDefault(): array 1786 { 1787 return $this->keywords->getValueOrDefaults(); 1788 } 1789 1790 1791 /** 1792 * @throws ExceptionCombo 1793 */ 1794 public 1795 function setKeywords($value): Page 1796 { 1797 $this->keywords 1798 ->setFromStoreValue($value) 1799 ->sendToWriteStore(); 1800 return $this; 1801 } 1802 1803 /** 1804 * @return DateTime|null 1805 * @deprecated for {@link CacheExpirationDate} 1806 */ 1807 public 1808 function getCacheExpirationDate(): ?DateTime 1809 { 1810 return $this->cacheExpirationDate->getValue(); 1811 } 1812 1813 /** 1814 * @return DateTime|null 1815 * @deprecated for {@link CacheExpirationDate} 1816 */ 1817 public 1818 function getDefaultCacheExpirationDate(): ?DateTime 1819 { 1820 return $this->cacheExpirationDate->getDefaultValue(); 1821 } 1822 1823 /** 1824 * @return string|null 1825 * @deprecated for {@link CacheExpirationFrequency} 1826 */ 1827 public 1828 function getCacheExpirationFrequency(): ?string 1829 { 1830 return $this->cacheExpirationFrequency->getValue(); 1831 } 1832 1833 1834 /** 1835 * @param DateTime $cacheExpirationDate 1836 * @return $this 1837 * @deprecated for {@link CacheExpirationDate} 1838 */ 1839 public 1840 function setCacheExpirationDate(DateTime $cacheExpirationDate): Page 1841 { 1842 $this->cacheExpirationDate->setValue($cacheExpirationDate); 1843 return $this; 1844 } 1845 1846 /** 1847 * @return bool - true if the page has changed 1848 * @deprecated use {@link Page::getInstructionsDocument()} instead 1849 */ 1850 public 1851 function isParseCacheUsable(): bool 1852 { 1853 return $this->getInstructionsDocument()->shouldProcess() === false; 1854 } 1855 1856 /** 1857 * @return $this 1858 * @deprecated use {@link Page::getInstructionsDocument()} instead 1859 * Parse a page and put the instructions in the cache 1860 */ 1861 public 1862 function parse(): Page 1863 { 1864 1865 $this->getInstructionsDocument() 1866 ->process(); 1867 1868 return $this; 1869 1870 } 1871 1872 /** 1873 * 1874 */ 1875 public 1876 function getInstructionsDocument(): InstructionsDocument 1877 { 1878 if ($this->instructionsDocument === null) { 1879 $this->instructionsDocument = new InstructionsDocument($this); 1880 } 1881 return $this->instructionsDocument; 1882 1883 } 1884 1885 public 1886 function delete() 1887 { 1888 1889 Index::getOrCreate()->deletePage($this); 1890 saveWikiText($this->getDokuwikiId(), "", "Delete"); 1891 1892 } 1893 1894 /** 1895 * @return string|null -the absolute canonical url 1896 */ 1897 public 1898 function getAbsoluteCanonicalUrl(): ?string 1899 { 1900 return $this->getCanonicalUrl([], true); 1901 } 1902 1903 1904 public 1905 function getReadStoreOrDefault(): MetadataStore 1906 { 1907 if ($this->readStore === null) { 1908 /** 1909 * No cache please if not set 1910 * Cache should be in the MetadataDokuWikiStore 1911 * that is page requested scoped and not by slot 1912 */ 1913 return MetadataDokuWikiStore::getOrCreateFromResource($this); 1914 } 1915 if (!($this->readStore instanceof MetadataStore)) { 1916 $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this); 1917 } 1918 return $this->readStore; 1919 } 1920 1921 /** 1922 * @return DokuPath 1923 */ 1924 public 1925 function getPath(): Path 1926 { 1927 return $this->dokuPath; 1928 } 1929 1930 1931 /** 1932 * A shortcut for {@link Page::getPath()::getDokuwikiId()} 1933 */ 1934 public 1935 function getDokuwikiId() 1936 { 1937 return $this->getPath()->getDokuwikiId(); 1938 } 1939 1940 public 1941 function getUid(): Metadata 1942 { 1943 return $this->pageId; 1944 } 1945 1946 1947 public 1948 function getAbsolutePath(): string 1949 { 1950 return DokuPath::PATH_SEPARATOR . $this->getDokuwikiId(); 1951 } 1952 1953 function getType(): string 1954 { 1955 return self::TYPE; 1956 } 1957 1958 public 1959 function getUrlPathObject(): PageUrlPath 1960 { 1961 return $this->pageUrlPath; 1962 } 1963 1964 public function getMainFooterSlot(): ?Page 1965 { 1966 if ($this->isSecondarySlot() || $this->isRootHomePage()) { 1967 return null; 1968 } 1969 1970 try { 1971 Site::loadStrapUtilityTemplateIfPresentAndSameVersion(); 1972 } catch (ExceptionCombo $e) { 1973 LogUtility::msg("We can't load strap. The nearest main footer slot could not be detected, Error: {$e->getMessage()}"); 1974 return null; 1975 } 1976 1977 $nearestMainFooter = $this->findNearest(TplUtility::SLOT_MAIN_FOOTER_NAME); 1978 if ($nearestMainFooter === false) { 1979 return null; 1980 } 1981 return Page::createPageFromId($nearestMainFooter); 1982 1983 1984 } 1985 1986 public function getSideSlot(): ?Page 1987 { 1988 if ($this->isSecondarySlot() || $this->isRootHomePage()) { 1989 return null; 1990 } 1991 1992 $nearestMainFooter = $this->findNearest(Site::getSidebarName()); 1993 if ($nearestMainFooter === false) { 1994 return null; 1995 } 1996 return Page::createPageFromId($nearestMainFooter); 1997 1998 1999 } 2000 2001 /** 2002 * @param $pageName 2003 * @return false|string 2004 */ 2005 private function findNearest($pageName) 2006 { 2007 global $ID; 2008 $keep = $ID; 2009 try { 2010 $ID = $this->getDokuwikiId(); 2011 return page_findnearest($pageName); 2012 } finally { 2013 $ID = $keep; 2014 } 2015 2016 } 2017 2018 /** 2019 * @return Page[] 2020 */ 2021 public function getSecondarySlots(): array 2022 { 2023 $secondarySlots = []; 2024 $sideSlot = $this->getSideSlot(); 2025 if ($sideSlot !== null) { 2026 $secondarySlots[] = $sideSlot; 2027 } 2028 $footerSlot = $this->getMainFooterSlot(); 2029 if ($footerSlot !== null) { 2030 $secondarySlots[] = $footerSlot; 2031 } 2032 return $secondarySlots; 2033 } 2034 2035 2036 public function isHidden(): bool 2037 { 2038 return isHiddenPage($this->getDokuwikiId()); 2039 } 2040 2041 2042} 2043