1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\Api\QualityMessageHandler;
7use ComboStrap\Meta\Api\Metadata;
8use ComboStrap\Meta\Api\MetadataBoolean;
9use ComboStrap\Meta\Api\MetadataStore;
10use ComboStrap\Meta\Api\MetadataStoreAbs;
11use ComboStrap\Meta\Field\Alias;
12use ComboStrap\Meta\Field\Aliases;
13use ComboStrap\Meta\Field\AliasType;
14use ComboStrap\Meta\Field\PageH1;
15use ComboStrap\Meta\Field\PageImage;
16use ComboStrap\Meta\Field\PageImages;
17use ComboStrap\Meta\Field\PageTemplateName;
18use ComboStrap\Meta\Field\Region;
19use ComboStrap\Meta\Store\MetadataDokuWikiStore;
20use ComboStrap\Web\Url;
21use ComboStrap\Web\UrlEndpoint;
22use DateTime;
23use dokuwiki\ChangeLog\ChangeLog;
24use Exception;
25use renderer_plugin_combo_analytics;
26
27
28/**
29 *
30 * A markup is a logical unit that represents a markup file.
31 *
32 * It has its own file system {@link MarkupFileSystem} explained in the
33 * https://combostrap.com/page/system (or system.txt file).
34 * ie the {@link Path::getParent()} is not the same than on an normal file system.
35 *
36 * This should be an extension of {@link WikiPath} but for now, we are not extending {@link WikiPath}
37 * for the following old reasons:
38 *   * we want to be able to return a {@link MarkupPath} in the {@link MarkupPath::getParent()} function
39 * otherwise if we do, we get a hierarchical error.
40 *   * we can then accepts also {@link LocalPath}
41 *
42 * But because this is a {@link ResourceCombo}, we see tht this is part of the {@link WikiPath}
43 * system with an {@link ResourceCombo::getUid()} unique uid.
44 *
45 * We should find a way to be able to create a wiki path with a {@link LocalPath}
46 * via the {@link WikiPath::getDrive()} ?
47 *
48 */
49class MarkupPath extends PathAbs implements ResourceCombo, Path
50{
51
52    const CANONICAL_PAGE = "markup";
53
54
55    const TYPE = "page";
56
57    /**
58     * @var Canonical
59     */
60    private $canonical;
61    /**
62     * @var PageH1
63     */
64    private $h1;
65    /**
66     * @var ResourceName
67     */
68    private $pageName;
69    /**
70     * @var PageType
71     */
72    private $type;
73    /**
74     * @var PageTitle $title
75     */
76    private $title;
77
78    private $uidObject;
79
80    private LowQualityPageOverwrite $canBeOfLowQuality;
81    /**
82     * @var Region
83     */
84    private $region;
85    /**
86     * @var Lang
87     */
88    private $lang;
89    /**
90     * @var PageId
91     */
92    private $pageId;
93
94    /**
95     * @var LowQualityCalculatedIndicator
96     */
97    private $lowQualityIndicatorCalculated;
98
99    /**
100     * @var PageTemplateName
101     */
102    private $layout;
103    /**
104     * @var Aliases
105     */
106    private $aliases;
107    /**
108     * @var Slug a slug path
109     */
110    private $slug;
111
112
113    /**
114     * @var QualityDynamicMonitoringOverwrite
115     */
116    private $qualityMonitoringIndicator;
117
118    /**
119     * @var string the alias used to build this page
120     */
121    private $buildAliasPath;
122    /**
123     * @var PagePublicationDate
124     */
125    private $publishedDate;
126    /**
127     * @var StartDate
128     */
129    private $startDate;
130    /**
131     * @var EndDate
132     */
133    private $endDate;
134    /**
135     * @var PageImages
136     */
137    private $pageImages;
138
139    private PageKeywords $keywords;
140    /**
141     * @var CacheExpirationFrequency
142     */
143    private $cacheExpirationFrequency;
144    /**
145     * @var CacheExpirationDate
146     */
147    private $cacheExpirationDate;
148    /**
149     *
150     * @var LdJson
151     */
152    private $ldJson;
153
154
155    /**
156     * @var PageDescription $description
157     */
158    private $description;
159    /**
160     * @var CreationDate
161     */
162    private $creationTime;
163    /**
164     * @var Locale
165     */
166    private $locale;
167    /**
168     * @var ModificationDate
169     */
170    private $modifiedTime;
171    /**
172     * @var PageUrlPath
173     */
174    private $pageUrlPath;
175    /**
176     * @var MetadataStore|string
177     */
178    private $readStore;
179
180    /**
181     * @var Path -  {@link MarkupPath} has other hierachy system in regards with parent
182     * May be we just should extends {@link WikiPath} but it was a way to be able to locate
183     * default markup path file that were not in any drive
184     * TODO: Just extends WikiPath and add private drive when data should be accessed locally ?
185     */
186    private Path $path;
187
188    /**
189     * Page constructor.
190     *
191     */
192    public function __construct(Path $path)
193    {
194
195        $this->path = $path;
196        if (FileSystems::isDirectory($path)) {
197            $this->setCorrectPathForDirectoryToIndexPage();
198        }
199        $this->buildPropertiesFromFileSystem();
200
201    }
202
203    /**
204     * The current running rendering markup
205     * @throws ExceptionNotFound
206     */
207    public static function createPageFromExecutingId(): MarkupPath
208    {
209        $wikiPath = WikiPath::createExecutingMarkupWikiPath();
210        return self::createPageFromPathObject($wikiPath);
211    }
212
213
214    public static function createMarkupFromId($id): MarkupPath
215    {
216        return new MarkupPath(WikiPath::createMarkupPathFromId($id));
217    }
218
219    /**
220     * @param string $path -  relative or absolute
221     * @return MarkupPath
222     */
223    public static function createMarkupFromStringPath(string $path): MarkupPath
224    {
225        $wikiPath = WikiPath::createMarkupPathFromPath($path);
226        return new MarkupPath($wikiPath);
227
228    }
229
230    /**
231     * @return MarkupPath - the requested page
232     * @throws ExceptionNotFound
233     */
234    public static function createFromRequestedPage(): MarkupPath
235    {
236        $path = WikiPath::createRequestedPagePathFromRequest();
237        return MarkupPath::createPageFromPathObject($path);
238    }
239
240
241    public static function createPageFromPathObject(Path $path): MarkupPath
242    {
243        if ($path instanceof MarkupPath) {
244            return $path;
245        }
246        return new MarkupPath($path);
247    }
248
249
250    /**
251     *
252     * @throws ExceptionBadSyntax - if this is not a
253     * @deprecated just pass a namespace path to the page creation and you will get the index page in return
254     */
255    public static function getIndexPageFromNamespace(string $namespacePath): MarkupPath
256    {
257        WikiPath::checkNamespacePath($namespacePath);
258
259        return MarkupPath::createMarkupFromId($namespacePath);
260    }
261
262
263    static function createPageFromAbsoluteId($qualifiedPath): MarkupPath
264    {
265        $path = WikiPath::createMarkupPathFromId($qualifiedPath);
266        return new MarkupPath($path);
267    }
268
269
270    /**
271     *
272     * @throws ExceptionCompile
273     */
274    public
275    function setCanonical($canonical): MarkupPath
276    {
277        $this->canonical
278            ->setValue($canonical)
279            ->sendToWriteStore();
280        return $this;
281    }
282
283
284    /**
285     * @return bool true if this is a fragment markup
286     */
287    public function isSlot(): bool
288    {
289        $slotNames = SlotSystem::getSlotNames();
290        try {
291            $name = $this->getPathObject()->getLastNameWithoutExtension();
292        } catch (ExceptionNotFound $e) {
293            // root case
294            return false;
295        }
296        return in_array($name, $slotNames, true);
297    }
298
299    /**
300     * @return bool true if this is the side slot
301     */
302    public function isSideSlot(): bool
303    {
304        $slotNames = SlotSystem::getSidebarName();
305        try {
306            $name = $this->getPathObject()->getLastNameWithoutExtension();
307        } catch (ExceptionNotFound $e) {
308            // root case
309            return false;
310        }
311        return $name === $slotNames;
312    }
313
314    /**
315     * @return bool true if this is the main
316     */
317    public function isMainHeaderFooterSlot(): bool
318    {
319
320        $slotNames = [SlotSystem::getMainHeaderSlotName(), SlotSystem::getMainFooterSlotName()];
321        try {
322            $name = $this->getPathObject()->getLastNameWithoutExtension();
323        } catch (ExceptionNotFound $e) {
324            // root case
325            return false;
326        }
327
328        return in_array($name, $slotNames, true);
329    }
330
331
332    /**
333     * Return a canonical if set
334     * otherwise derive it from the id
335     * by taking the last two parts
336     *
337     * @return WikiPath
338     * @throws ExceptionNotFound
339     * @deprecated for {@link Canonical::getValueOrDefault()}
340     */
341    public
342    function getCanonicalOrDefault(): WikiPath
343    {
344        return $this->canonical->getValueFromStoreOrDefault();
345
346    }
347
348
349    /**
350     * Rebuild the page
351     * (refresh from disk, reset object to null)
352     * @return $this
353     */
354    public
355    function rebuild(): MarkupPath
356    {
357        $this->readStore = null;
358        $this->buildPropertiesFromFileSystem();
359        return $this;
360    }
361
362    /**
363     *
364     * @return MarkupPath[]|null the internal links or null
365     */
366    public
367    function getLinkReferences(): ?array
368    {
369        $store = $this->getReadStoreOrDefault();
370        if (!($store instanceof MetadataDokuWikiStore)) {
371            return null;
372        }
373        $metadata = $store->getCurrentFromName('relation');
374        if ($metadata === null) {
375            /**
376             * Happens when no rendering has been made
377             */
378            return null;
379        }
380        if (!key_exists('references', $metadata)) {
381            return null;
382        }
383
384        $pages = [];
385        foreach (array_keys($metadata['references']) as $referencePageId) {
386            $pages[$referencePageId] = MarkupPath::createMarkupFromId($referencePageId);
387        }
388        return $pages;
389
390    }
391
392
393    /**
394     *
395     * @throws ExceptionNotExists - if the path does not exists
396     * @throws ExceptionCast - if the path is not a wiki path which is mandatory for the context
397     */
398    public function createHtmlFetcherWithItselfAsContextPath(): FetcherMarkup
399    {
400        $path = $this->getPathObject();
401        return FetcherMarkup::createXhtmlMarkupFetcherFromPath($path, $path->toWikiPath());
402    }
403
404    /**
405     * @throws ExceptionCompile
406     */
407    public function getHtmlPath(): LocalPath
408    {
409
410        $fetcher = $this->createHtmlFetcherWithItselfAsContextPath();
411        return $fetcher->processIfNeededAndGetFetchPath();
412
413    }
414
415    /**
416     * Set the page quality
417     * @param boolean $value true if this is a low quality page rank false otherwise
418     * @throws ExceptionCompile
419     */
420    public
421    function setCanBeOfLowQuality(bool $value): MarkupPath
422    {
423        return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->canBeOfLowQuality, $value);
424    }
425
426    /**
427     * @return MarkupPath[] the backlinks
428     * Duplicate of related
429     *
430     * Same as {@link WikiPath::getReferencedBy()} ?
431     */
432    public
433    function getBacklinks(): array
434    {
435        $backlinks = array();
436        /**
437         * Same as
438         * idx_get_indexer()->lookupKey('relation_references', $ID);
439         */
440        $ft_backlinks = ft_backlinks($this->getWikiId());
441        foreach ($ft_backlinks as $backlinkId) {
442            $backlinks[$backlinkId] = MarkupPath::createMarkupFromId($backlinkId);
443        }
444        return $backlinks;
445    }
446
447
448    /**
449     * Low page quality
450     * @return bool true if this is a low quality page
451     */
452    function isLowQualityPage(): bool
453    {
454
455
456        if (!$this->getCanBeOfLowQuality()) {
457            return false;
458        }
459
460        if (!Site::isLowQualityProtectionEnable()) {
461            return false;
462        }
463        try {
464            return $this->getLowQualityIndicatorCalculated();
465        } catch (ExceptionNotFound $e) {
466            // We were returning null but null used in a condition is falsy
467            // we return false
468            return false;
469        }
470
471    }
472
473
474    /**
475     *
476     */
477    public function getCanBeOfLowQuality(): bool
478    {
479
480        return $this->canBeOfLowQuality->getValueOrDefault();
481
482    }
483
484
485    /**
486     * Return the Title
487     * @deprecated for {@link PageTitle::getValue()}
488     */
489    public
490    function getTitle(): ?string
491    {
492        return $this->title->getValueFromStore();
493    }
494
495    /**
496     * If true, the page is quality monitored (a note is shown to the writer)
497     * @return null|bool
498     */
499    public
500    function getQualityMonitoringIndicator(): ?bool
501    {
502        return $this->qualityMonitoringIndicator->getValueFromStore();
503    }
504
505    /**
506     * @return string the title, or h1 if empty or the id if empty
507     * Shortcut to {@link PageTitle::getValueOrDefault()}
508     *
509     */
510    public
511    function getTitleOrDefault(): string
512    {
513        try {
514            return $this->title->getValueOrDefault();
515        } catch (ExceptionNotFound $e) {
516            LogUtility::internalError("Internal Error: The page ($this) does not have any default title");
517            return $this->getPathObject()->getLastNameWithoutExtension();
518        }
519
520    }
521
522
523    public function getH1OrDefault(): string
524    {
525
526        return $this->h1->getValueOrDefault();
527
528    }
529
530    /**
531     * @return string
532     * @throws ExceptionNotFound
533     */
534    public
535    function getDescription(): string
536    {
537        return $this->description->getValue();
538    }
539
540
541    /**
542     * @return string - the description or the dokuwiki generated description
543     */
544    public
545    function getDescriptionOrElseDokuWiki(): string
546    {
547        return $this->description->getValueOrDefault();
548    }
549
550
551    /**
552     * @return string
553     * The content / markup that should be parsed by the parser
554     */
555    public
556    function getMarkup(): string
557    {
558
559        try {
560            return FileSystems::getContent($this->getPathObject());
561        } catch (ExceptionNotFound $e) {
562            LogUtility::msg("The page ($this) was not found");
563            return "";
564        }
565
566    }
567
568
569    public
570    function isInIndex(): bool
571    {
572        $Indexer = idx_get_indexer();
573        $pages = $Indexer->getPages();
574        $return = array_search($this->getPathObject()->getWikiId(), $pages, true);
575        return $return !== false;
576    }
577
578
579    /**
580     * Save the content with the {@link ChangeLog}
581     * @param string $content
582     * @param string $summary
583     * @return $this
584     * Use {@link FileSystems::setContent()} if you don't want any log
585     * This function wraps {@link saveWikiText()} it implements the events system and may have side-effects
586     */
587    public
588    function setContentWithLog(string $content, string $summary = "Default"): MarkupPath
589    {
590        $path = $this->getPathObject();
591        if (!($path instanceof WikiPath)) {
592            throw new ExceptionRuntime("The path of this markup is not a wiki path");
593        }
594        saveWikiText($path->getWikiId(), $content, $summary);
595        return $this;
596    }
597
598    public
599    function addToIndex()
600    {
601        /**
602         * Add to index check the metadata cache
603         * Because we log the cache at the requested page level, we need to
604         * set the global ID
605         */
606        global $ID;
607        $keep = $ID;
608        global $ACT;
609        $keepACT = $ACT;
610        try {
611            $ACT = "show";
612            $ID = $this->getPathObject()->toWikiPath()->getWikiId();
613            idx_addPage($ID);
614        } finally {
615            $ID = $keep;
616            $ACT = $keepACT;
617        }
618        return $this;
619
620    }
621
622    /**
623     * @return mixed
624     */
625    public
626    function getTypeOrDefault()
627    {
628        return $this->type->getValueFromStoreOrDefault();
629    }
630
631
632    /**
633     * @throws ExceptionNotFound
634     */
635    public
636    function getFirstImage(): IFetcherLocalImage
637    {
638        try {
639            return IFetcherLocalImage::createImageFetchFromPath(FirstRasterImage::createForPage($this)->getValue());
640        } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
641            LogUtility::error("First Raster Image error. Error: " . $e->getMessage(), self::CANONICAL_PAGE, $e);
642            throw new ExceptionNotFound();
643        } catch (ExceptionNotExists $e) {
644            throw new ExceptionNotFound();
645        }
646
647    }
648
649    /**
650     * Return the media stored during parsing
651     *
652     * They are saved via the function {@link \Doku_Renderer_metadata::_recordMediaUsage()}
653     * called by the {@link \Doku_Renderer_metadata::internalmedia()}
654     *
655     *
656     * {@link \Doku_Renderer_metadata::externalmedia()} does not save them
657     */
658    public
659    function getMediasMetadata(): ?array
660    {
661
662        $store = $this->getReadStoreOrDefault();
663        if (!($store instanceof MetadataDokuWikiStore)) {
664            return null;
665        }
666        $medias = [];
667
668        $relation = $store->getCurrentFromName('relation');
669        if (isset($relation['media'])) {
670            /**
671             * The relation is
672             * $this->meta['relation']['media'][$src] = $exists;
673             *
674             */
675            foreach ($relation['media'] as $src => $exists) {
676                if ($exists) {
677                    $medias[] = $src;
678                }
679            }
680        }
681        return $medias;
682    }
683
684
685    /**
686     * Get author name
687     *
688     * @return string
689     */
690    public
691    function getAuthor(): ?string
692    {
693        $store = $this->getReadStoreOrDefault();
694        if (!($store instanceof MetadataDokuWikiStore)) {
695            return null;
696        }
697
698        return $store->getFromName('creator');
699    }
700
701    /**
702     * Get author ID
703     *
704     * @return string
705     */
706    public
707    function getAuthorID(): ?string
708    {
709
710        $store = $this->getReadStoreOrDefault();
711        if (!($store instanceof MetadataDokuWikiStore)) {
712            return null;
713        }
714
715        return $store->getFromName('user');
716
717    }
718
719
720    /**
721     * Get the create date of page
722     *
723     * @return DateTime
724     * @throws ExceptionNotFound
725     */
726    public
727    function getCreatedTime(): ?DateTime
728    {
729        return $this->creationTime->getValue();
730    }
731
732
733    /**
734     *
735     * @return DateTime
736     */
737    public
738    function getModifiedTime(): DateTime
739    {
740        return $this->modifiedTime->getValueFromStore();
741    }
742
743    /**
744     * @throws ExceptionNotFound
745     */
746    public
747    function getModifiedTimeOrDefault(): DateTime
748    {
749        return $this->modifiedTime->getValueFromStoreOrDefault();
750    }
751
752
753    /**
754     * Utility class, refresh the metadata (used only in test)
755     * @deprecated if possible used {@link FetcherMarkup} instead
756     */
757    public function renderMetadataAndFlush(): MarkupPath
758    {
759
760        if (!FileSystems::exists($this)) {
761            if (PluginUtility::isDevOrTest()) {
762                LogUtility::msg("You can't render the metadata of a markup path that does not exist ($this)");
763            }
764            return $this;
765        }
766
767        try {
768            $wikiPath = $this->getPathObject()->toWikiPath();
769            FetcherMarkup::confRoot()
770                ->setRequestedContextPath($wikiPath)
771                ->setRequestedExecutingPath($wikiPath)
772                ->setRequestedMimeToMetadata()
773                ->build()
774                ->processMetadataIfNotYetDone();
775        } catch (ExceptionCast|ExceptionNotExists $e) {
776            // not a wiki path, no meta
777        }
778
779
780        return $this;
781
782    }
783
784    /**
785     * @return string|null
786     * @deprecated for {@link Region}
787     */
788    public
789    function getLocaleRegion(): ?string
790    {
791        return $this->region->getValueFromStore();
792    }
793
794    public
795    function getRegionOrDefault()
796    {
797
798        return $this->region->getValueFromStoreOrDefault();
799
800    }
801
802    public
803    function getLang(): ?string
804    {
805        return $this->lang->getValueFromStore();
806    }
807
808    public function getLangOrDefault(): string
809    {
810        return $this->lang->getValueOrDefault();
811    }
812
813    /**
814     * The home page is an index page
815     * Adapted from {@link FsWikiUtility::getHomePagePath()}
816     * @return bool
817     */
818    public function isIndexPage(): bool
819    {
820
821        $startPageName = Site::getIndexPageName();
822        try {
823            if ($this->getPathObject()->getLastNameWithoutExtension() === $startPageName) {
824                return true;
825            }
826        } catch (ExceptionNotFound $e) {
827            // ok
828        }
829
830        try {
831            /**
832             * page named like the NS inside the NS
833             * ie ns:ns
834             */
835            $objectPath = $this->path;
836            $parentPath = $this->path->getParent();
837            if (!($parentPath instanceof WikiPath)) {
838                return false;
839            }
840            if ($parentPath->getLastNameWithoutExtension() === $objectPath->getLastNameWithoutExtension()) {
841                /**
842                 * If the start page does not exists, this is the index page
843                 */
844                $startPage = $parentPath->resolveId($startPageName);
845                if (!FileSystems::exists($startPage)) {
846                    return true;
847                }
848            }
849        } catch (ExceptionNotFound $e) {
850            // no parent, no last name, etc
851        }
852
853        return false;
854    }
855
856
857    /**
858     * @throws ExceptionNotFound
859     */
860    public
861    function getPublishedTime(): DateTime
862    {
863        return $this->publishedDate->getValueFromStore();
864    }
865
866    /**
867     * @return bool
868     * @deprecated for {@link FileSystems::exists()}
869     */
870    public function exists(): bool
871    {
872        return FileSystems::exists($this);
873    }
874
875    /**
876     * @return DateTime
877     * @throws ExceptionNotFound
878     */
879    public
880    function getPublishedElseCreationTime(): DateTime
881    {
882        return $this->publishedDate->getValueFromStoreOrDefault();
883    }
884
885
886    public
887    function isLatePublication(): bool
888    {
889        try {
890            $dateTime = $this->getPublishedElseCreationTime();
891        } catch (ExceptionNotFound $e) {
892            return false;
893        }
894        return $dateTime > new DateTime('now');
895    }
896
897    /**
898     * The unique page Url (also known as Canonical URL) used:
899     *   * in the link
900     *   * in the canonical ref
901     *   * in the site map
902     * @return Url
903     */
904    public
905    function getCanonicalUrl(): Url
906    {
907
908        /**
909         * Dokuwiki Methodology Taken from {@link tpl_metaheaders()}
910         */
911        if ($this->isRootHomePage()) {
912            return UrlEndpoint::createBaseUrl()->toAbsoluteUrl();
913        }
914
915        try {
916            return UrlEndpoint::createDokuUrl()
917                ->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->getWikiId())
918                ->toAbsoluteUrl();
919        } catch (ExceptionBadArgument $e) {
920            LogUtility::error("This markup path ($this) can not be accessed externaly");
921            return UrlEndpoint::createBaseUrl();
922        }
923
924
925    }
926
927
928
929    /**
930     *
931     * @return string|null - the locale facebook way
932     * @throws ExceptionNotFound
933     * @deprecated for {@link Locale}
934     */
935    public
936    function getLocale($default = null): ?string
937    {
938        $value = $this->locale->getValueFromStore();
939        if ($value === null) {
940            return $default;
941        }
942        return $value;
943    }
944
945
946    /**
947     *
948     * @deprecated use a {@link FetcherMarkup::getFetchString()} instead
949     */
950    public function toXhtml(): string
951    {
952
953        $fetcherMarkup = $this->createHtmlFetcherWithItselfAsContextPath();
954        return $fetcherMarkup->getFetchString();
955
956
957    }
958
959
960    public
961    function getHtmlAnchorLink($logicalTag = null): string
962    {
963        $id = $this->getPathObject()->getWikiId();
964        try {
965            return LinkMarkup::createFromPageIdOrPath($id)
966                    ->toAttributes($logicalTag)
967                    ->toHtmlEnterTag("a")
968                . $this->getNameOrDefault()
969                . "</a>";
970        } catch (ExceptionCompile $e) {
971            LogUtility::msg("The markup ref returns an error for the creation of the page anchor html link ($this). Error: {$e->getMessage()}");
972            return "<a href=\"{$this->getCanonicalUrl()}\" data-wiki-id=\"$id\">{$this->getNameOrDefault()}</a>";
973        }
974    }
975
976
977    /**
978     * Without the `:` at the end
979     * @return string
980     * @throws ExceptionNotFound
981     * @deprecated / shortcut for {@link WikiPath::getParent()}
982     * Because a page has always a parent, the string is never null.
983     */
984    public function getNamespacePath(): string
985    {
986
987        return $this->getParent()->toAbsoluteId();
988
989    }
990
991
992    /**
993     * @return $this
994     * @deprecated use {@link MetadataDokuWikiStore::deleteAndFlush()}
995     */
996    public
997    function deleteMetadatasAndFlush(): MarkupPath
998    {
999        MetadataDokuWikiStore::getOrCreateFromResource($this)
1000            ->deleteAndFlush();
1001        return $this;
1002    }
1003
1004    /**
1005     * @throws ExceptionNotFound
1006     */
1007    public
1008    function getName(): string
1009    {
1010
1011        return $this->pageName->getValue();
1012
1013    }
1014
1015    public
1016    function getNameOrDefault(): string
1017    {
1018
1019        return ResourceName::createForResource($this)->getValueOrDefault();
1020
1021
1022    }
1023
1024    /**
1025     * @param $property
1026     */
1027    public
1028    function unsetMetadata($property)
1029    {
1030        $meta = p_read_metadata($this->getPathObject()->getWikiId());
1031        if (isset($meta['persistent'][$property])) {
1032            unset($meta['persistent'][$property]);
1033        }
1034        p_save_metadata($this->getPathObject()->getWikiId(), $meta);
1035
1036    }
1037
1038    /**
1039     * @return array - return the standard / generated metadata
1040     * used to create a variable environment (context) in rendering
1041     */
1042    public
1043    function getMetadataForRendering(): array
1044    {
1045
1046        $metadataNames = [
1047            PageH1::PROPERTY_NAME,
1048            PageTitle::PROPERTY_NAME,
1049            Lead::PROPERTY_NAME,
1050            Canonical::PROPERTY_NAME,
1051            PagePath::PROPERTY_NAME,
1052            Label::PROPERTY_NAME,
1053            PageDescription::PROPERTY_NAME,
1054            ResourceName::PROPERTY_NAME,
1055            PageType::PROPERTY_NAME,
1056            Slug::PROPERTY_NAME,
1057            PageTemplateName::PROPERTY_NAME,
1058            DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, // Dokuwiki id is deprecated for path
1059            PageLevel::PROPERTY_NAME,
1060            PageKeywords::PROPERTY_NAME
1061        ];
1062
1063        /**
1064         * The metadata that works only
1065         * if the file exists
1066         */
1067        if (FileSystems::exists($this)) {
1068            $metadataThatNeedsExistingFile = [
1069                PageId::PROPERTY_NAME,
1070                CreationDate::PROPERTY_NAME,
1071                ModificationDate::PROPERTY_NAME,
1072                PagePublicationDate::PROPERTY_NAME,
1073                StartDate::PROPERTY_NAME,
1074                EndDate::PROPERTY_NAME,
1075            ];
1076            $metadataNames = array_merge($metadataNames, $metadataThatNeedsExistingFile);
1077        }
1078
1079
1080        foreach ($metadataNames as $metadataName) {
1081            try {
1082                $metadata = Meta\Api\MetadataSystem::getForName($metadataName);
1083            } catch (ExceptionNotFound $e) {
1084                LogUtility::msg("The metadata ($metadataName) should be defined");
1085                continue;
1086            }
1087            /**
1088             * The Value or Default is returned
1089             *
1090             * Because the title/h1 should never be null
1091             * otherwise a template link such as [[$path|$title]] will return a link without an description
1092             * and therefore will be not visible
1093             *
1094             * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility}
1095             * If we want the native value, we need to change the pipeline
1096             */
1097            $value = $metadata
1098                ->setResource($this)
1099                ->setReadStore(MetadataDokuWikiStore::class)
1100                ->setWriteStore(TemplateStore::class)
1101                ->buildFromReadStore()
1102                ->toStoreValueOrDefault();
1103            $array[$metadataName] = $value;
1104        }
1105
1106        $array["url"] = $this->getCanonicalUrl()->toAbsoluteUrl()->toString();
1107        $array["now"] = Iso8601Date::createFromNow()->toString();
1108        return $array;
1109
1110    }
1111
1112
1113    public
1114    function getPublishedTimeAsString(): ?string
1115    {
1116        return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null;
1117    }
1118
1119
1120    /**
1121     * @throws ExceptionNotFound
1122     */
1123    public
1124    function getEndDate(): DateTime
1125    {
1126        return $this->endDate->getValue();
1127    }
1128
1129
1130
1131    /**
1132     * @throws ExceptionNotFound
1133     */
1134    public
1135    function getStartDate(): DateTime
1136    {
1137        return $this->startDate->getValue();
1138    }
1139
1140    /**
1141     * A page id
1142     * @return string
1143     * @throws ExceptionNotFound - when the page does not exist
1144     */
1145    public
1146    function getPageId(): string
1147    {
1148        return PageId::createForPage($this)->getValue();
1149    }
1150
1151
1152    /**
1153     * @throws ExceptionNotExists
1154     */
1155    public
1156    function fetchAnalyticsDocument(): FetcherMarkup
1157    {
1158        return renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this);
1159    }
1160
1161    /**
1162     * @throws ExceptionCompile
1163     * @throws ExceptionNotExists
1164     */
1165    public
1166    function fetchAnalyticsPath(): Path
1167    {
1168        $fetcher = renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this);
1169        return $fetcher->processIfNeededAndGetFetchPath();
1170
1171    }
1172
1173    /**
1174     */
1175    public
1176    function getDatabasePage(): DatabasePageRow
1177    {
1178
1179        return DatabasePageRow::getFromPageObject($this);
1180
1181    }
1182
1183    /**
1184     * @throws ExceptionSqliteNotAvailable
1185     */
1186    public
1187    function getOrCreateDatabasePage(): DatabasePageRow
1188    {
1189
1190        return DatabasePageRow::getOrCreateFromPageObject($this);
1191
1192    }
1193
1194    public
1195    function canBeUpdatedByCurrentUser(): bool
1196    {
1197        return Identity::isWriter($this->getWikiId());
1198    }
1199
1200
1201    public
1202    function isRootHomePage(): bool
1203    {
1204        global $conf;
1205        $startPageName = $conf['start'];
1206        return $this->getPathObject()->toAbsoluteId() === ":$startPageName";
1207
1208    }
1209
1210
1211    /**
1212     * @throws ExceptionNotFound
1213     */
1214    public
1215    function getPageType(): string
1216    {
1217        return $this->type->getValueFromStore();
1218    }
1219
1220    /**
1221     * @throws ExceptionNotFound
1222     * @deprecated
1223     */
1224    public
1225    function getCanonical(): WikiPath
1226    {
1227        return $this->canonical->getValue();
1228    }
1229
1230    /**
1231     * Create a canonical from the last page path part.
1232     *
1233     * @return string|null
1234     * @throws ExceptionNotFound
1235     */
1236    public
1237    function getDefaultCanonical(): ?string
1238    {
1239        return $this->canonical->getDefaultValue();
1240    }
1241
1242    /**
1243     * @throws ExceptionNotFound
1244     */
1245    public
1246    function getLayout()
1247    {
1248        return $this->layout->getValueFromStore();
1249    }
1250
1251    /**
1252     * @throws ExceptionNotFound
1253     */
1254    public
1255    function getDefaultPageName(): string
1256    {
1257        return $this->pageName->getDefaultValue();
1258    }
1259
1260    public
1261    function getDefaultTitle(): string
1262    {
1263        return $this->title->getDefaultValue();
1264    }
1265
1266    /**
1267     * @throws ExceptionNotFound
1268     */
1269    public
1270    function getDefaultH1()
1271    {
1272        return $this->h1->getValueOrDefault();
1273    }
1274
1275    public
1276    function getDefaultType(): string
1277    {
1278        return $this->type->getDefaultValue();
1279    }
1280
1281    public
1282    function getDefaultLayout(): string
1283    {
1284        return $this->layout->getDefaultValue();
1285    }
1286
1287
1288    /**
1289     *
1290     * @throws ExceptionCompile
1291     */
1292    public
1293    function setLowQualityIndicatorCalculation($bool): MarkupPath
1294    {
1295        return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool);
1296    }
1297
1298
1299    /**
1300     * Change the quality indicator
1301     * and if the quality level has become low
1302     * and that the protection is on, delete the cache
1303     * @param MetadataBoolean $lowQualityAttributeName
1304     * @param bool $value
1305     * @return MarkupPath
1306     * @throws ExceptionBadArgument - if the value cannot be persisted
1307     */
1308    private
1309    function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): MarkupPath
1310    {
1311        try {
1312            $actualValue = $lowQualityAttributeName->getValue();
1313        } catch (ExceptionNotFound $e) {
1314            $actualValue = null;
1315        }
1316        if ($value !== $actualValue) {
1317            $lowQualityAttributeName
1318                ->setValue($value)
1319                ->persist();
1320        }
1321        return $this;
1322    }
1323
1324
1325    /**
1326     * @throws ExceptionNotFound
1327     */
1328    public
1329    function getLowQualityIndicatorCalculated()
1330    {
1331
1332        return $this->lowQualityIndicatorCalculated->getValueOrDefault();
1333
1334    }
1335
1336    /**
1337     * @return PageImage[]
1338     * @deprecated
1339     */
1340    public
1341    function getPageMetadataImages(): array
1342    {
1343        return $this->pageImages->getValueAsPageImages();
1344    }
1345
1346
1347    /**
1348     * @param array|string $jsonLd
1349     * @return $this
1350     * @throws ExceptionCompile
1351     * @deprecated for {@link LdJson}
1352     */
1353    public
1354    function setJsonLd($jsonLd): MarkupPath
1355    {
1356        $this->ldJson
1357            ->setValue($jsonLd)
1358            ->sendToWriteStore();
1359        return $this;
1360    }
1361
1362    /**
1363     * @throws ExceptionCompile
1364     */
1365    public
1366    function setPageType(string $value): MarkupPath
1367    {
1368        $this->type
1369            ->setValue($value)
1370            ->sendToWriteStore();
1371        return $this;
1372    }
1373
1374
1375    /**
1376     * @param $aliasPath
1377     * @param string $aliasType
1378     * @return Alias
1379     * @deprecated for {@link Aliases}
1380     */
1381    public
1382    function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias
1383    {
1384
1385        return $this->aliases->addAndGetAlias($aliasPath, $aliasType);
1386
1387    }
1388
1389
1390    /**
1391     * @return Alias[]
1392     * @throws ExceptionNotFound
1393     */
1394    public
1395    function getAliases(): array
1396    {
1397        return $this->aliases->getValueAsAlias();
1398    }
1399
1400    /**
1401     * @return string
1402     */
1403    public
1404    function getSlugOrDefault(): string
1405    {
1406        try {
1407            return $this->getSlug();
1408        } catch (ExceptionNotFound $e) {
1409            return $this->getDefaultSlug();
1410        }
1411
1412    }
1413
1414    /**
1415     *
1416     * @return string
1417     *
1418     */
1419    public
1420    function getDefaultSlug(): string
1421    {
1422        return $this->slug->getDefaultValue();
1423    }
1424
1425    /**
1426     * The parent page is the parent in the page tree directory
1427     *
1428     * If the page is at the root, the parent page is the root home
1429     * Only the root home does not have any parent page and return null.
1430     *
1431     * @return MarkupPath
1432     * @throws ExceptionNotFound
1433     */
1434    public function getParent(): MarkupPath
1435    {
1436
1437        $names = $this->getNames();
1438        if (sizeof($names) == 0) {
1439            throw new ExceptionNotFound("No parent page");
1440        }
1441        $slice = 1;
1442        if ($this->isIndexPage()) {
1443            /**
1444             * The parent of a home page
1445             * is in the parent directory
1446             */
1447            $slice = 2;
1448        }
1449        /**
1450         * Delete the last or the two last parts
1451         */
1452        if (sizeof($names) < $slice) {
1453            throw new ExceptionNotFound("No parent page");
1454        }
1455        /**
1456         * Get the actual directory for a page
1457         * or the parent directory for a home page
1458         */
1459        $parentNames = array_slice($names, 0, sizeof($names) - $slice);
1460        /**
1461         * Create the parent namespace id
1462         */
1463        $parentNamespaceId = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $parentNames) . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
1464        try {
1465            return self::getIndexPageFromNamespace($parentNamespaceId);
1466        } catch (ExceptionBadSyntax $e) {
1467            $message = "Error on getParentPage, null returned - Error: {$e->getMessage()}";
1468            LogUtility::internalError($message);
1469            throw new ExceptionNotFound($message);
1470        }
1471
1472    }
1473
1474    /**
1475     * @throws ExceptionCompile
1476     */
1477    public
1478    function setDescription($description): MarkupPath
1479    {
1480
1481        $this->description
1482            ->setValue($description)
1483            ->sendToWriteStore();
1484        return $this;
1485    }
1486
1487    /**
1488     * @throws ExceptionCompile
1489     * @deprecated uses {@link EndDate} instead
1490     */
1491    public
1492    function setEndDate($value): MarkupPath
1493    {
1494        $this->endDate
1495            ->setFromStoreValue($value)
1496            ->sendToWriteStore();
1497        return $this;
1498    }
1499
1500    /**
1501     * @throws ExceptionCompile
1502     * @deprecated uses {@link StartDate} instead
1503     */
1504    public
1505    function setStartDate($value): MarkupPath
1506    {
1507        $this->startDate
1508            ->setFromStoreValue($value)
1509            ->sendToWriteStore();
1510        return $this;
1511    }
1512
1513    /**
1514     * @throws ExceptionCompile
1515     */
1516    public
1517    function setPublishedDate($value): MarkupPath
1518    {
1519        $this->publishedDate
1520            ->setFromStoreValue($value)
1521            ->sendToWriteStore();
1522        return $this;
1523    }
1524
1525    /**
1526     * Utility to {@link ResourceName::setValue()}
1527     * Used mostly to create page in test
1528     * @throws ExceptionCompile
1529     * @deprecated use not persist
1530     */
1531    public
1532    function setPageName($value): MarkupPath
1533    {
1534        $this->pageName
1535            ->setValue($value)
1536            ->sendToWriteStore();
1537        return $this;
1538    }
1539
1540
1541    /**
1542     * @throws ExceptionCompile
1543     */
1544    public
1545    function setTitle($value): MarkupPath
1546    {
1547        $this->title
1548            ->setValue($value)
1549            ->sendToWriteStore();
1550        return $this;
1551    }
1552
1553    /**
1554     * @throws ExceptionCompile
1555     */
1556    public
1557    function setH1($value): MarkupPath
1558    {
1559        $this->h1
1560            ->setValue($value)
1561            ->sendToWriteStore();
1562        return $this;
1563    }
1564
1565    /**
1566     * @throws Exception
1567     */
1568    public
1569    function setRegion($value): MarkupPath
1570    {
1571        $this->region
1572            ->setFromStoreValue($value)
1573            ->sendToWriteStore();
1574        return $this;
1575    }
1576
1577    /**
1578     * @throws ExceptionCompile
1579     */
1580    public
1581    function setLang($value): MarkupPath
1582    {
1583
1584        $this->lang
1585            ->setFromStoreValue($value)
1586            ->sendToWriteStore();
1587        return $this;
1588    }
1589
1590    /**
1591     * @throws ExceptionCompile
1592     */
1593    public
1594    function setLayout($value): MarkupPath
1595    {
1596        $this->layout
1597            ->setValue($value)
1598            ->sendToWriteStore();
1599        return $this;
1600    }
1601
1602
1603    /**
1604     *
1605     * We manage the properties by setter and getter
1606     *
1607     * Why ?
1608     *   * Because we can capture the updates
1609     *   * Because setter are the entry point to good quality data
1610     *   * Because dokuwiki may cache the metadata (see below)
1611     *
1612     * Note all properties have been migrated
1613     * but they should be initialized below
1614     *
1615     * Dokuwiki cache: the data may be cached without our consent
1616     * The method {@link p_get_metadata()} does it with this logic
1617     * ```
1618     * $cache = ($ID == $id);
1619     * $meta = p_read_metadata($id, $cache);
1620     * ```
1621     */
1622    private
1623    function buildPropertiesFromFileSystem()
1624    {
1625
1626        /**
1627         * New meta system
1628         * Even if it does not exist, the metadata object should be instantiated
1629         * otherwise, there is a null exception
1630         */
1631        $this->cacheExpirationDate = CacheExpirationDate::createForPage($this);
1632        $this->aliases = Aliases::createForPage($this);
1633        $this->pageImages = PageImages::createForPage($this);
1634        $this->pageName = ResourceName::createForResource($this);
1635        $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this);
1636        $this->ldJson = LdJson::createForPage($this);
1637        $this->canonical = Canonical::createForPage($this);
1638        $this->description = PageDescription::createForPage($this);
1639        $this->h1 = PageH1::createForPage($this);
1640        $this->type = PageType::createForPage($this);
1641        $this->creationTime = CreationDate::createForPage($this);
1642        $this->title = PageTitle::createForMarkup($this);
1643        $this->keywords = PageKeywords::createForPage($this);
1644        $this->publishedDate = PagePublicationDate::createFromPage($this);
1645        $this->startDate = StartDate::createFromPage($this);
1646        $this->endDate = EndDate::createFromPage($this);
1647        $this->locale = Locale::createForPage($this);
1648        $this->lang = Lang::createForMarkup($this);
1649        $this->region = Region::createForPage($this);
1650        $this->slug = \ComboStrap\Slug::createForPage($this);
1651        $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this);
1652        $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this);
1653        $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this);
1654        $this->modifiedTime = ModificationDate::createForPage($this);
1655        $this->pageUrlPath = PageUrlPath::createForPage($this);
1656        $this->layout = PageTemplateName::createFromPage($this);
1657
1658    }
1659
1660
1661    /**
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