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