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 ]; 1069 1070 foreach ($metadataNames as $metadataName) { 1071 $metadata = Metadata::getForName($metadataName); 1072 if ($metadata === null) { 1073 LogUtility::msg("The metadata ($metadata) should be defined"); 1074 continue; 1075 } 1076 /** 1077 * The Value or Default is returned 1078 * 1079 * Because the title/h1 should never be null 1080 * otherwise a template link such as [[$path|$title]] will return a link without an description 1081 * and therefore will be not visible 1082 * 1083 * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility} 1084 * If we want the native value, we need to change the pipeline 1085 */ 1086 $value = $metadata 1087 ->setResource($this) 1088 ->setWriteStore(TemplateStore::class) 1089 ->toStoreValueOrDefault(); 1090 if ($metadata->getDataType() === DataType::TEXT_TYPE_VALUE) { 1091 1092 /** 1093 * Hack: Replace every " by a ' to be able to detect/parse the title/h1 on a pipeline 1094 * @see {@link \syntax_plugin_combo_pipeline} 1095 */ 1096 $value = str_replace('"', "'", $value); 1097 } 1098 $array[$metadataName] = $value; 1099 } 1100 $array["url"] = $this->getCanonicalUrl(); 1101 return $array; 1102 1103 } 1104 1105 public 1106 function __toString() 1107 { 1108 return $this->dokuPath->toUriString(); 1109 } 1110 1111 1112 public 1113 function getPublishedTimeAsString(): ?string 1114 { 1115 return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null; 1116 } 1117 1118 public 1119 function getEndDateAsString(): ?string 1120 { 1121 return $this->getEndDate() !== null ? $this->getEndDate()->format(Iso8601Date::getFormat()) : null; 1122 } 1123 1124 public 1125 function getEndDate(): ?DateTime 1126 { 1127 return $this->endDate->getValueFromStore(); 1128 } 1129 1130 public 1131 function getStartDateAsString(): ?string 1132 { 1133 return $this->getStartDate() !== null ? $this->getStartDate()->format(Iso8601Date::getFormat()) : null; 1134 } 1135 1136 public 1137 function getStartDate(): ?DateTime 1138 { 1139 return $this->startDate->getValueFromStore(); 1140 } 1141 1142 /** 1143 * A page id or null if the page id does not exists 1144 * @return string|null 1145 */ 1146 public 1147 function getPageId(): ?string 1148 { 1149 1150 return $this->pageId->getValue(); 1151 1152 } 1153 1154 1155 public 1156 function getAnalyticsDocument(): AnalyticsDocument 1157 { 1158 return new AnalyticsDocument($this); 1159 } 1160 1161 public 1162 function getDatabasePage(): DatabasePageRow 1163 { 1164 if ($this->databasePage == null) { 1165 $this->databasePage = DatabasePageRow::createFromPageObject($this); 1166 } 1167 return $this->databasePage; 1168 } 1169 1170 public 1171 function canBeUpdatedByCurrentUser(): bool 1172 { 1173 return Identity::isWriter($this->getDokuwikiId()); 1174 } 1175 1176 1177 public 1178 function isRootHomePage(): bool 1179 { 1180 global $conf; 1181 $startPageName = $conf['start']; 1182 return $this->getPath()->toString() === ":$startPageName"; 1183 1184 } 1185 1186 /** 1187 * Used when the page is moved to take the Page Id of the source 1188 * @param string|null $pageId 1189 * @return Page 1190 * @throws ExceptionCombo 1191 */ 1192 public 1193 function setPageId(?string $pageId): Page 1194 { 1195 1196 $this->pageId 1197 ->setValue($pageId) 1198 ->sendToWriteStore(); 1199 1200 return $this; 1201 1202 } 1203 1204 public 1205 function getPageType(): ?string 1206 { 1207 return $this->type->getValueFromStore(); 1208 } 1209 1210 public 1211 function getCanonical(): ?string 1212 { 1213 return $this->canonical->getValueFromStore(); 1214 } 1215 1216 /** 1217 * Create a canonical from the last page path part. 1218 * 1219 * @return string|null 1220 */ 1221 public 1222 function getDefaultCanonical(): ?string 1223 { 1224 return $this->canonical->getDefaultValue(); 1225 } 1226 1227 public 1228 function getLayout() 1229 { 1230 return $this->layout->getValueFromStore(); 1231 } 1232 1233 public 1234 function getDefaultPageName(): string 1235 { 1236 return $this->pageName->getDefaultValue(); 1237 } 1238 1239 public 1240 function getDefaultTitle(): ?string 1241 { 1242 return $this->title->getDefaultValue(); 1243 } 1244 1245 public 1246 function getDefaultH1() 1247 { 1248 return $this->h1->getValueOrDefault(); 1249 } 1250 1251 public 1252 function getDefaultType(): string 1253 { 1254 return $this->type->getDefaultValue(); 1255 } 1256 1257 public 1258 function getDefaultLayout(): string 1259 { 1260 return $this->layout->getDefaultValue(); 1261 } 1262 1263 1264 /** 1265 * @throws ExceptionCombo 1266 */ 1267 public 1268 function setLowQualityIndicatorCalculation($bool): Page 1269 { 1270 return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool); 1271 } 1272 1273 1274 /** 1275 * Change the quality indicator 1276 * and if the quality level has become low 1277 * and that the protection is on, delete the cache 1278 * @param MetadataBoolean $lowQualityAttributeName 1279 * @param bool $value 1280 * @return Page 1281 * @throws ExceptionCombo 1282 */ 1283 private 1284 function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): Page 1285 { 1286 $actualValue = $lowQualityAttributeName->getValue(); 1287 if ($actualValue === null || $value !== $actualValue) { 1288 $lowQualityAttributeName 1289 ->setValue($value) 1290 ->persist(); 1291 } 1292 return $this; 1293 } 1294 1295 1296 public 1297 function getLowQualityIndicatorCalculated() 1298 { 1299 1300 return $this->lowQualityIndicatorCalculated->getValueOrDefault(); 1301 1302 } 1303 1304 /** 1305 * @return PageImage[] 1306 */ 1307 public 1308 function getPageImages(): ?array 1309 { 1310 return $this->pageImages->getValueAsPageImages(); 1311 } 1312 1313 1314 /** 1315 * @return array|null 1316 * @deprecated for {@link LdJson} 1317 */ 1318 public 1319 function getLdJson(): ?string 1320 { 1321 return $this->ldJson->getValue(); 1322 1323 } 1324 1325 /** 1326 * @param array|string $jsonLd 1327 * @return $this 1328 * @throws ExceptionCombo 1329 * @deprecated for {@link LdJson} 1330 */ 1331 public 1332 function setJsonLd($jsonLd): Page 1333 { 1334 $this->ldJson 1335 ->setValue($jsonLd) 1336 ->sendToWriteStore(); 1337 return $this; 1338 } 1339 1340 /** 1341 * @throws ExceptionCombo 1342 */ 1343 public 1344 function setPageType(string $value): Page 1345 { 1346 $this->type 1347 ->setValue($value) 1348 ->sendToWriteStore(); 1349 return $this; 1350 } 1351 1352 1353 /** 1354 * @param $aliasPath 1355 * @param string $aliasType 1356 * @return Alias 1357 * @deprecated for {@link Aliases} 1358 */ 1359 public 1360 function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias 1361 { 1362 1363 return $this->aliases->addAndGetAlias($aliasPath, $aliasType); 1364 1365 } 1366 1367 1368 /** 1369 * @return Alias[] 1370 */ 1371 public 1372 function getAliases(): ?array 1373 { 1374 return $this->aliases->getValueAsAlias(); 1375 } 1376 1377 /** 1378 * @return string|null 1379 * 1380 */ 1381 public 1382 function getSlugOrDefault(): ?string 1383 { 1384 1385 if ($this->getSlug() !== null) { 1386 return $this->getSlug(); 1387 } 1388 return $this->getDefaultSlug(); 1389 } 1390 1391 /** 1392 * 1393 * @return string|null 1394 * 1395 */ 1396 public 1397 function getDefaultSlug(): ?string 1398 { 1399 return $this->slug->getDefaultValue(); 1400 } 1401 1402 /** 1403 * The parent page is the parent in the page tree directory 1404 * 1405 * If the page is at the root, the parent page is the root home 1406 * Only the root home does not have any parent page and return null. 1407 * 1408 * @return Page|null 1409 */ 1410 public 1411 function getParentPage(): ?Page 1412 { 1413 1414 $names = $this->getPath()->getNames(); 1415 if (sizeof($names) == 0) { 1416 return null; 1417 } 1418 $slice = 1; 1419 if ($this->isHomePage()) { 1420 /** 1421 * The parent of a home page 1422 * is in the parent directory 1423 */ 1424 $slice = 2; 1425 } 1426 /** 1427 * Delete the last or the two last parts 1428 */ 1429 if (sizeof($names) < $slice) { 1430 return null; 1431 } 1432 /** 1433 * Get the actual directory for a page 1434 * or the parent directory for a home page 1435 */ 1436 $parentNames = array_slice($names, 0, sizeof($names) - $slice); 1437 /** 1438 * Create the parent namespace id 1439 */ 1440 $parentNamespaceId = implode(DokuPath::PATH_SEPARATOR, $parentNames); 1441 return self::getHomePageFromNamespace($parentNamespaceId); 1442 1443 } 1444 1445 /** 1446 * @throws ExceptionCombo 1447 */ 1448 public 1449 function setDescription($description): Page 1450 { 1451 1452 $this->description 1453 ->setValue($description) 1454 ->sendToWriteStore(); 1455 return $this; 1456 } 1457 1458 /** 1459 * @throws ExceptionCombo 1460 * @deprecated uses {@link EndDate} instead 1461 */ 1462 public 1463 function setEndDate($value): Page 1464 { 1465 $this->endDate 1466 ->setFromStoreValue($value) 1467 ->sendToWriteStore(); 1468 return $this; 1469 } 1470 1471 /** 1472 * @throws ExceptionCombo 1473 * @deprecated uses {@link StartDate} instead 1474 */ 1475 public 1476 function setStartDate($value): Page 1477 { 1478 $this->startDate 1479 ->setFromStoreValue($value) 1480 ->sendToWriteStore(); 1481 return $this; 1482 } 1483 1484 /** 1485 * @throws ExceptionCombo 1486 */ 1487 public 1488 function setPublishedDate($value): Page 1489 { 1490 $this->publishedDate 1491 ->setFromStoreValue($value) 1492 ->sendToWriteStore(); 1493 return $this; 1494 } 1495 1496 /** 1497 * Utility to {@link ResourceName::setValue()} 1498 * Used mostly to create page in test 1499 * @throws ExceptionCombo 1500 */ 1501 public 1502 function setPageName($value): Page 1503 { 1504 $this->pageName 1505 ->setValue($value) 1506 ->sendToWriteStore(); 1507 return $this; 1508 } 1509 1510 1511 /** 1512 * @throws ExceptionCombo 1513 */ 1514 public 1515 function setTitle($value): Page 1516 { 1517 $this->title 1518 ->setValue($value) 1519 ->sendToWriteStore(); 1520 return $this; 1521 } 1522 1523 /** 1524 * @throws ExceptionCombo 1525 */ 1526 public 1527 function setH1($value): Page 1528 { 1529 $this->h1 1530 ->setValue($value) 1531 ->sendToWriteStore(); 1532 return $this; 1533 } 1534 1535 /** 1536 * @throws Exception 1537 */ 1538 public 1539 function setRegion($value): Page 1540 { 1541 $this->region 1542 ->setFromStoreValue($value) 1543 ->sendToWriteStore(); 1544 return $this; 1545 } 1546 1547 /** 1548 * @throws ExceptionCombo 1549 */ 1550 public 1551 function setLang($value): Page 1552 { 1553 1554 $this->lang 1555 ->setFromStoreValue($value) 1556 ->sendToWriteStore(); 1557 return $this; 1558 } 1559 1560 /** 1561 * @throws ExceptionCombo 1562 */ 1563 public 1564 function setLayout($value): Page 1565 { 1566 $this->layout 1567 ->setValue($value) 1568 ->sendToWriteStore(); 1569 return $this; 1570 } 1571 1572 1573 /** 1574 * 1575 * We manage the properties by setter and getter 1576 * 1577 * Why ? 1578 * * Because we can capture the updates 1579 * * Because setter are the entry point to good quality data 1580 * * Because dokuwiki may cache the metadata (see below) 1581 * 1582 * Note all properties have been migrated 1583 * but they should be initialized below 1584 * 1585 * Dokuwiki cache: the data may be cached without our consent 1586 * The method {@link p_get_metadata()} does it with this logic 1587 * ``` 1588 * $cache = ($ID == $id); 1589 * $meta = p_read_metadata($id, $cache); 1590 * ``` 1591 */ 1592 private 1593 function buildPropertiesFromFileSystem() 1594 { 1595 1596 /** 1597 * New meta system 1598 * Even if it does not exist, the metadata object should be instantiated 1599 * otherwise, there is a null exception 1600 */ 1601 $this->cacheExpirationDate = CacheExpirationDate::createForPage($this); 1602 $this->aliases = Aliases::createForPage($this); 1603 $this->pageImages = PageImages::createForPage($this); 1604 $this->pageName = ResourceName::createForResource($this); 1605 $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this); 1606 $this->ldJson = LdJson::createForPage($this); 1607 $this->canonical = Canonical::createForPage($this); 1608 $this->pageId = PageId::createForPage($this); 1609 $this->description = PageDescription::createForPage($this); 1610 $this->h1 = PageH1::createForPage($this); 1611 $this->type = PageType::createForPage($this); 1612 $this->creationTime = PageCreationDate::createForPage($this); 1613 $this->title = PageTitle::createForPage($this); 1614 $this->keywords = PageKeywords::createForPage($this); 1615 $this->publishedDate = PagePublicationDate::createFromPage($this); 1616 $this->startDate = StartDate::createFromPage($this); 1617 $this->endDate = EndDate::createFromPage($this); 1618 $this->locale = Locale::createForPage($this); 1619 $this->lang = Lang::createForPage($this); 1620 $this->region = Region::createForPage($this); 1621 $this->slug = Slug::createForPage($this); 1622 $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this); 1623 $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this); 1624 $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this); 1625 $this->modifiedTime = ModificationDate::createForPage($this); 1626 $this->pageUrlPath = PageUrlPath::createForPage($this); 1627 $this->layout = PageLayout::createFromPage($this); 1628 1629 } 1630 1631 1632 function getPageIdAbbr() 1633 { 1634 1635 if ($this->getPageId() === null) return null; 1636 return substr($this->getPageId(), 0, PageId::PAGE_ID_ABBREV_LENGTH); 1637 1638 } 1639 1640 public 1641 function setDatabasePage(DatabasePageRow $databasePage): Page 1642 { 1643 $this->databasePage = $databasePage; 1644 return $this; 1645 } 1646 1647 /** 1648 * 1649 * TODO: Move to {@link HtmlDocument} ? 1650 */ 1651 public function getUrlPath(): string 1652 { 1653 1654 return $this->pageUrlPath->getValueOrDefault(); 1655 1656 } 1657 1658 1659 /** 1660 * @return string|null 1661 * 1662 */ 1663 public 1664 function getSlug(): ?string 1665 { 1666 return $this->slug->getValue(); 1667 } 1668 1669 1670 /** 1671 * @throws ExceptionCombo 1672 */ 1673 public 1674 function setSlug($slug): Page 1675 { 1676 $this->slug 1677 ->setFromStoreValue($slug) 1678 ->sendToWriteStore(); 1679 return $this; 1680 } 1681 1682 1683 public 1684 function getUrlId() 1685 { 1686 return DokuPath::toDokuwikiId($this->getUrlPath()); 1687 } 1688 1689 1690 /** 1691 * @throws ExceptionCombo 1692 */ 1693 public 1694 function setQualityMonitoringIndicator($boolean): Page 1695 { 1696 $this->qualityMonitoringIndicator 1697 ->setFromStoreValue($boolean) 1698 ->sendToWriteStore(); 1699 return $this; 1700 } 1701 1702 /** 1703 * 1704 * @param $aliasPath - third information - the alias used to build this page 1705 */ 1706 public 1707 function setBuildAliasPath($aliasPath) 1708 { 1709 $this->buildAliasPath = $aliasPath; 1710 } 1711 1712 public 1713 function getBuildAlias(): ?Alias 1714 { 1715 if ($this->buildAliasPath === null) return null; 1716 foreach ($this->getAliases() as $alias) { 1717 if ($alias->getPath() === $this->buildAliasPath) { 1718 return $alias; 1719 } 1720 } 1721 return null; 1722 } 1723 1724 public 1725 function isDynamicQualityMonitored(): bool 1726 { 1727 if ($this->getQualityMonitoringIndicator() !== null) { 1728 return $this->getQualityMonitoringIndicator(); 1729 } 1730 return $this->getDefaultQualityMonitoring(); 1731 } 1732 1733 public 1734 function getDefaultQualityMonitoring(): bool 1735 { 1736 if (PluginUtility::getConfValue(action_plugin_combo_qualitymessage::CONF_DISABLE_QUALITY_MONITORING) === 1) { 1737 return false; 1738 } else { 1739 return true; 1740 } 1741 } 1742 1743 /** 1744 * @param MetadataStore|string $store 1745 * @return $this 1746 */ 1747 public 1748 function setReadStore($store): Page 1749 { 1750 $this->readStore = $store; 1751 return $this; 1752 } 1753 1754 1755 /** 1756 * @param array $usages 1757 * @return Image[] 1758 */ 1759 public 1760 function getImagesOrDefaultForTheFollowingUsages(array $usages): array 1761 { 1762 $usages = array_merge($usages, [PageImageUsage::ALL]); 1763 $images = []; 1764 foreach ($this->getPageImagesOrDefault() as $pageImage) { 1765 foreach ($usages as $usage) { 1766 if (in_array($usage, $pageImage->getUsages())) { 1767 $images[] = $pageImage->getImage(); 1768 continue 2; 1769 } 1770 } 1771 } 1772 return $images; 1773 1774 } 1775 1776 1777 public 1778 function getKeywords(): ?array 1779 { 1780 return $this->keywords->getValues(); 1781 } 1782 1783 public 1784 function getKeywordsOrDefault(): array 1785 { 1786 return $this->keywords->getValueOrDefaults(); 1787 } 1788 1789 1790 /** 1791 * @throws ExceptionCombo 1792 */ 1793 public 1794 function setKeywords($value): Page 1795 { 1796 $this->keywords 1797 ->setFromStoreValue($value) 1798 ->sendToWriteStore(); 1799 return $this; 1800 } 1801 1802 /** 1803 * @return DateTime|null 1804 * @deprecated for {@link CacheExpirationDate} 1805 */ 1806 public 1807 function getCacheExpirationDate(): ?DateTime 1808 { 1809 return $this->cacheExpirationDate->getValue(); 1810 } 1811 1812 /** 1813 * @return DateTime|null 1814 * @deprecated for {@link CacheExpirationDate} 1815 */ 1816 public 1817 function getDefaultCacheExpirationDate(): ?DateTime 1818 { 1819 return $this->cacheExpirationDate->getDefaultValue(); 1820 } 1821 1822 /** 1823 * @return string|null 1824 * @deprecated for {@link CacheExpirationFrequency} 1825 */ 1826 public 1827 function getCacheExpirationFrequency(): ?string 1828 { 1829 return $this->cacheExpirationFrequency->getValue(); 1830 } 1831 1832 1833 /** 1834 * @param DateTime $cacheExpirationDate 1835 * @return $this 1836 * @deprecated for {@link CacheExpirationDate} 1837 */ 1838 public 1839 function setCacheExpirationDate(DateTime $cacheExpirationDate): Page 1840 { 1841 $this->cacheExpirationDate->setValue($cacheExpirationDate); 1842 return $this; 1843 } 1844 1845 /** 1846 * @return bool - true if the page has changed 1847 * @deprecated use {@link Page::getInstructionsDocument()} instead 1848 */ 1849 public 1850 function isParseCacheUsable(): bool 1851 { 1852 return $this->getInstructionsDocument()->shouldProcess() === false; 1853 } 1854 1855 /** 1856 * @return $this 1857 * @deprecated use {@link Page::getInstructionsDocument()} instead 1858 * Parse a page and put the instructions in the cache 1859 */ 1860 public 1861 function parse(): Page 1862 { 1863 1864 $this->getInstructionsDocument() 1865 ->process(); 1866 1867 return $this; 1868 1869 } 1870 1871 /** 1872 * 1873 */ 1874 public 1875 function getInstructionsDocument(): InstructionsDocument 1876 { 1877 if ($this->instructionsDocument === null) { 1878 $this->instructionsDocument = new InstructionsDocument($this); 1879 } 1880 return $this->instructionsDocument; 1881 1882 } 1883 1884 public 1885 function delete() 1886 { 1887 1888 Index::getOrCreate()->deletePage($this); 1889 saveWikiText($this->getDokuwikiId(), "", "Delete"); 1890 1891 } 1892 1893 /** 1894 * @return string|null -the absolute canonical url 1895 */ 1896 public 1897 function getAbsoluteCanonicalUrl(): ?string 1898 { 1899 return $this->getCanonicalUrl([], true); 1900 } 1901 1902 1903 public 1904 function getReadStoreOrDefault(): MetadataStore 1905 { 1906 if ($this->readStore === null) { 1907 /** 1908 * No cache please if not set 1909 * Cache should be in the MetadataDokuWikiStore 1910 * that is page requested scoped and not by slot 1911 */ 1912 return MetadataDokuWikiStore::getOrCreateFromResource($this); 1913 } 1914 if (!($this->readStore instanceof MetadataStore)) { 1915 $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this); 1916 } 1917 return $this->readStore; 1918 } 1919 1920 /** 1921 * @return DokuPath 1922 */ 1923 public 1924 function getPath(): Path 1925 { 1926 return $this->dokuPath; 1927 } 1928 1929 1930 /** 1931 * A shortcut for {@link Page::getPath()::getDokuwikiId()} 1932 */ 1933 public 1934 function getDokuwikiId() 1935 { 1936 return $this->getPath()->getDokuwikiId(); 1937 } 1938 1939 public 1940 function getUid(): Metadata 1941 { 1942 return $this->pageId; 1943 } 1944 1945 1946 public 1947 function getAbsolutePath(): string 1948 { 1949 return DokuPath::PATH_SEPARATOR . $this->getDokuwikiId(); 1950 } 1951 1952 function getType(): string 1953 { 1954 return self::TYPE; 1955 } 1956 1957 public 1958 function getUrlPathObject(): PageUrlPath 1959 { 1960 return $this->pageUrlPath; 1961 } 1962 1963 public function getMainFooterSlot(): ?Page 1964 { 1965 if ($this->isSecondarySlot() || $this->isRootHomePage()) { 1966 return null; 1967 } 1968 1969 try { 1970 Site::loadStrapUtilityTemplateIfPresentAndSameVersion(); 1971 } catch (ExceptionCombo $e) { 1972 LogUtility::msg("We can't load strap. The nearest main footer slot could not be detected, Error: {$e->getMessage()}"); 1973 return null; 1974 } 1975 1976 $nearestMainFooter = $this->findNearest(TplUtility::SLOT_MAIN_FOOTER_NAME); 1977 if ($nearestMainFooter === false) { 1978 return null; 1979 } 1980 return Page::createPageFromId($nearestMainFooter); 1981 1982 1983 } 1984 1985 public function getSideSlot(): ?Page 1986 { 1987 if ($this->isSecondarySlot() || $this->isRootHomePage()) { 1988 return null; 1989 } 1990 1991 $nearestMainFooter = $this->findNearest(Site::getSidebarName()); 1992 if ($nearestMainFooter === false) { 1993 return null; 1994 } 1995 return Page::createPageFromId($nearestMainFooter); 1996 1997 1998 } 1999 2000 /** 2001 * @param $pageName 2002 * @return false|string 2003 */ 2004 private function findNearest($pageName) 2005 { 2006 global $ID; 2007 $keep = $ID; 2008 try { 2009 $ID = $this->getDokuwikiId(); 2010 return page_findnearest($pageName); 2011 } finally { 2012 $ID = $keep; 2013 } 2014 2015 } 2016 2017 /** 2018 * @return Page[] 2019 */ 2020 public function getSecondarySlots(): array 2021 { 2022 $secondarySlots = []; 2023 $sideSlot = $this->getSideSlot(); 2024 if ($sideSlot !== null) { 2025 $secondarySlots[] = $sideSlot; 2026 } 2027 $footerSlot = $this->getMainFooterSlot(); 2028 if ($footerSlot !== null) { 2029 $secondarySlots[] = $footerSlot; 2030 } 2031 return $secondarySlots; 2032 } 2033 2034 2035 public function isHidden(): bool 2036 { 2037 return isHiddenPage($this->getDokuwikiId()); 2038 } 2039 2040 2041} 2042