xref: /plugin/combo/ComboStrap/MarkupPath.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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();
913        }
914
915        try {
916            return UrlEndpoint::createDokuUrl()
917                ->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->getWikiId());
918        } catch (ExceptionBadArgument $e) {
919            LogUtility::error("This markup path ($this) can not be accessed externaly");
920            return UrlEndpoint::createBaseUrl();
921        }
922
923
924    }
925
926
927
928    /**
929     *
930     * @return string|null - the locale facebook way
931     * @throws ExceptionNotFound
932     * @deprecated for {@link Locale}
933     */
934    public
935    function getLocale($default = null): ?string
936    {
937        $value = $this->locale->getValueFromStore();
938        if ($value === null) {
939            return $default;
940        }
941        return $value;
942    }
943
944
945    /**
946     *
947     * @deprecated use a {@link FetcherMarkup::getFetchString()} instead
948     */
949    public function toXhtml(): string
950    {
951
952        $fetcherMarkup = $this->createHtmlFetcherWithItselfAsContextPath();
953        return $fetcherMarkup->getFetchString();
954
955
956    }
957
958
959    public
960    function getHtmlAnchorLink($logicalTag = null): string
961    {
962        $id = $this->getPathObject()->getWikiId();
963        try {
964            return LinkMarkup::createFromPageIdOrPath($id)
965                    ->toAttributes($logicalTag)
966                    ->toHtmlEnterTag("a")
967                . $this->getNameOrDefault()
968                . "</a>";
969        } catch (ExceptionCompile $e) {
970            LogUtility::msg("The markup ref returns an error for the creation of the page anchor html link ($this). Error: {$e->getMessage()}");
971            return "<a href=\"{$this->getCanonicalUrl()}\" data-wiki-id=\"$id\">{$this->getNameOrDefault()}</a>";
972        }
973    }
974
975
976    /**
977     * Without the `:` at the end
978     * @return string
979     * @throws ExceptionNotFound
980     * @deprecated / shortcut for {@link WikiPath::getParent()}
981     * Because a page has always a parent, the string is never null.
982     */
983    public function getNamespacePath(): string
984    {
985
986        return $this->getParent()->toAbsoluteId();
987
988    }
989
990
991    /**
992     * @return $this
993     * @deprecated use {@link MetadataDokuWikiStore::deleteAndFlush()}
994     */
995    public
996    function deleteMetadatasAndFlush(): MarkupPath
997    {
998        MetadataDokuWikiStore::getOrCreateFromResource($this)
999            ->deleteAndFlush();
1000        return $this;
1001    }
1002
1003    /**
1004     * @throws ExceptionNotFound
1005     */
1006    public
1007    function getName(): string
1008    {
1009
1010        return $this->pageName->getValue();
1011
1012    }
1013
1014    public
1015    function getNameOrDefault(): string
1016    {
1017
1018        return ResourceName::createForResource($this)->getValueOrDefault();
1019
1020
1021    }
1022
1023    /**
1024     * @param $property
1025     */
1026    public
1027    function unsetMetadata($property)
1028    {
1029        $meta = p_read_metadata($this->getPathObject()->getWikiId());
1030        if (isset($meta['persistent'][$property])) {
1031            unset($meta['persistent'][$property]);
1032        }
1033        p_save_metadata($this->getPathObject()->getWikiId(), $meta);
1034
1035    }
1036
1037    /**
1038     * @return array - return the standard / generated metadata
1039     * used to create a variable environment (context) in rendering
1040     */
1041    public
1042    function getMetadataForRendering(): array
1043    {
1044
1045        $metadataNames = [
1046            PageH1::PROPERTY_NAME,
1047            PageTitle::PROPERTY_NAME,
1048            Lead::PROPERTY_NAME,
1049            Canonical::PROPERTY_NAME,
1050            PagePath::PROPERTY_NAME,
1051            Label::PROPERTY_NAME,
1052            PageDescription::PROPERTY_NAME,
1053            ResourceName::PROPERTY_NAME,
1054            PageType::PROPERTY_NAME,
1055            Slug::PROPERTY_NAME,
1056            PageTemplateName::PROPERTY_NAME,
1057            DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, // Dokuwiki id is deprecated for path
1058            PageLevel::PROPERTY_NAME,
1059            PageKeywords::PROPERTY_NAME
1060        ];
1061
1062        /**
1063         * The metadata that works only
1064         * if the file exists
1065         */
1066        if (FileSystems::exists($this)) {
1067            $metadataThatNeedsExistingFile = [
1068                PageId::PROPERTY_NAME,
1069                CreationDate::PROPERTY_NAME,
1070                ModificationDate::PROPERTY_NAME,
1071                PagePublicationDate::PROPERTY_NAME,
1072                StartDate::PROPERTY_NAME,
1073                EndDate::PROPERTY_NAME,
1074            ];
1075            $metadataNames = array_merge($metadataNames, $metadataThatNeedsExistingFile);
1076        }
1077
1078
1079        foreach ($metadataNames as $metadataName) {
1080            try {
1081                $metadata = Meta\Api\MetadataSystem::getForName($metadataName);
1082            } catch (ExceptionNotFound $e) {
1083                LogUtility::msg("The metadata ($metadataName) should be defined");
1084                continue;
1085            }
1086            /**
1087             * The Value or Default is returned
1088             *
1089             * Because the title/h1 should never be null
1090             * otherwise a template link such as [[$path|$title]] will return a link without an description
1091             * and therefore will be not visible
1092             *
1093             * ToStoreValue to get the string format of date/boolean in the {@link PipelineUtility}
1094             * If we want the native value, we need to change the pipeline
1095             */
1096            $value = $metadata
1097                ->setResource($this)
1098                ->setReadStore(MetadataDokuWikiStore::class)
1099                ->setWriteStore(TemplateStore::class)
1100                ->buildFromReadStore()
1101                ->toStoreValueOrDefault();
1102            $array[$metadataName] = $value;
1103        }
1104
1105        $array["url"] = $this->getCanonicalUrl()->toAbsoluteUrl()->toString();
1106        $array["now"] = Iso8601Date::createFromNow()->toString();
1107        return $array;
1108
1109    }
1110
1111
1112    public
1113    function getPublishedTimeAsString(): ?string
1114    {
1115        return $this->getPublishedTime() !== null ? $this->getPublishedTime()->format(Iso8601Date::getFormat()) : null;
1116    }
1117
1118
1119    /**
1120     * @throws ExceptionNotFound
1121     */
1122    public
1123    function getEndDate(): DateTime
1124    {
1125        return $this->endDate->getValue();
1126    }
1127
1128
1129
1130    /**
1131     * @throws ExceptionNotFound
1132     */
1133    public
1134    function getStartDate(): DateTime
1135    {
1136        return $this->startDate->getValue();
1137    }
1138
1139    /**
1140     * A page id
1141     * @return string
1142     * @throws ExceptionNotFound - when the page does not exist
1143     */
1144    public
1145    function getPageId(): string
1146    {
1147        return PageId::createForPage($this)->getValue();
1148    }
1149
1150
1151    /**
1152     * @throws ExceptionNotExists
1153     */
1154    public
1155    function fetchAnalyticsDocument(): FetcherMarkup
1156    {
1157        return renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this);
1158    }
1159
1160    /**
1161     * @throws ExceptionCompile
1162     * @throws ExceptionNotExists
1163     */
1164    public
1165    function fetchAnalyticsPath(): Path
1166    {
1167        $fetcher = renderer_plugin_combo_analytics::createAnalyticsFetcherForPageFragment($this);
1168        return $fetcher->processIfNeededAndGetFetchPath();
1169
1170    }
1171
1172    /**
1173     */
1174    public
1175    function getDatabasePage(): DatabasePageRow
1176    {
1177
1178        return DatabasePageRow::getFromPageObject($this);
1179
1180    }
1181
1182    /**
1183     * @throws ExceptionSqliteNotAvailable
1184     */
1185    public
1186    function getOrCreateDatabasePage(): DatabasePageRow
1187    {
1188
1189        return DatabasePageRow::getOrCreateFromPageObject($this);
1190
1191    }
1192
1193    public
1194    function canBeUpdatedByCurrentUser(): bool
1195    {
1196        return Identity::isWriter($this->getWikiId());
1197    }
1198
1199
1200    public
1201    function isRootHomePage(): bool
1202    {
1203        global $conf;
1204        $startPageName = $conf['start'];
1205        return $this->getPathObject()->toAbsoluteId() === ":$startPageName";
1206
1207    }
1208
1209
1210    /**
1211     * @throws ExceptionNotFound
1212     */
1213    public
1214    function getPageType(): string
1215    {
1216        return $this->type->getValueFromStore();
1217    }
1218
1219    /**
1220     * @throws ExceptionNotFound
1221     * @deprecated
1222     */
1223    public
1224    function getCanonical(): WikiPath
1225    {
1226        return $this->canonical->getValue();
1227    }
1228
1229    /**
1230     * Create a canonical from the last page path part.
1231     *
1232     * @return string|null
1233     * @throws ExceptionNotFound
1234     */
1235    public
1236    function getDefaultCanonical(): ?string
1237    {
1238        return $this->canonical->getDefaultValue();
1239    }
1240
1241    /**
1242     * @throws ExceptionNotFound
1243     */
1244    public
1245    function getLayout()
1246    {
1247        return $this->layout->getValueFromStore();
1248    }
1249
1250    /**
1251     * @throws ExceptionNotFound
1252     */
1253    public
1254    function getDefaultPageName(): string
1255    {
1256        return $this->pageName->getDefaultValue();
1257    }
1258
1259    public
1260    function getDefaultTitle(): string
1261    {
1262        return $this->title->getDefaultValue();
1263    }
1264
1265    /**
1266     * @throws ExceptionNotFound
1267     */
1268    public
1269    function getDefaultH1()
1270    {
1271        return $this->h1->getValueOrDefault();
1272    }
1273
1274    public
1275    function getDefaultType(): string
1276    {
1277        return $this->type->getDefaultValue();
1278    }
1279
1280    public
1281    function getDefaultLayout(): string
1282    {
1283        return $this->layout->getDefaultValue();
1284    }
1285
1286
1287    /**
1288     *
1289     * @throws ExceptionCompile
1290     */
1291    public
1292    function setLowQualityIndicatorCalculation($bool): MarkupPath
1293    {
1294        return $this->setQualityIndicatorAndDeleteCacheIfNeeded($this->lowQualityIndicatorCalculated, $bool);
1295    }
1296
1297
1298    /**
1299     * Change the quality indicator
1300     * and if the quality level has become low
1301     * and that the protection is on, delete the cache
1302     * @param MetadataBoolean $lowQualityAttributeName
1303     * @param bool $value
1304     * @return MarkupPath
1305     * @throws ExceptionBadArgument - if the value cannot be persisted
1306     */
1307    private
1308    function setQualityIndicatorAndDeleteCacheIfNeeded(MetadataBoolean $lowQualityAttributeName, bool $value): MarkupPath
1309    {
1310        try {
1311            $actualValue = $lowQualityAttributeName->getValue();
1312        } catch (ExceptionNotFound $e) {
1313            $actualValue = null;
1314        }
1315        if ($value !== $actualValue) {
1316            $lowQualityAttributeName
1317                ->setValue($value)
1318                ->persist();
1319        }
1320        return $this;
1321    }
1322
1323
1324    /**
1325     * @throws ExceptionNotFound
1326     */
1327    public
1328    function getLowQualityIndicatorCalculated()
1329    {
1330
1331        return $this->lowQualityIndicatorCalculated->getValueOrDefault();
1332
1333    }
1334
1335    /**
1336     * @return PageImage[]
1337     * @deprecated
1338     */
1339    public
1340    function getPageMetadataImages(): array
1341    {
1342        return $this->pageImages->getValueAsPageImages();
1343    }
1344
1345
1346    /**
1347     * @param array|string $jsonLd
1348     * @return $this
1349     * @throws ExceptionCompile
1350     * @deprecated for {@link LdJson}
1351     */
1352    public
1353    function setJsonLd($jsonLd): MarkupPath
1354    {
1355        $this->ldJson
1356            ->setValue($jsonLd)
1357            ->sendToWriteStore();
1358        return $this;
1359    }
1360
1361    /**
1362     * @throws ExceptionCompile
1363     */
1364    public
1365    function setPageType(string $value): MarkupPath
1366    {
1367        $this->type
1368            ->setValue($value)
1369            ->sendToWriteStore();
1370        return $this;
1371    }
1372
1373
1374    /**
1375     * @param $aliasPath
1376     * @param string $aliasType
1377     * @return Alias
1378     * @deprecated for {@link Aliases}
1379     */
1380    public
1381    function addAndGetAlias($aliasPath, string $aliasType = AliasType::REDIRECT): Alias
1382    {
1383
1384        return $this->aliases->addAndGetAlias($aliasPath, $aliasType);
1385
1386    }
1387
1388
1389    /**
1390     * @return Alias[]
1391     * @throws ExceptionNotFound
1392     */
1393    public
1394    function getAliases(): array
1395    {
1396        return $this->aliases->getValueAsAlias();
1397    }
1398
1399    /**
1400     * @return string
1401     */
1402    public
1403    function getSlugOrDefault(): string
1404    {
1405        try {
1406            return $this->getSlug();
1407        } catch (ExceptionNotFound $e) {
1408            return $this->getDefaultSlug();
1409        }
1410
1411    }
1412
1413    /**
1414     *
1415     * @return string
1416     *
1417     */
1418    public
1419    function getDefaultSlug(): string
1420    {
1421        return $this->slug->getDefaultValue();
1422    }
1423
1424    /**
1425     * The parent page is the parent in the page tree directory
1426     *
1427     * If the page is at the root, the parent page is the root home
1428     * Only the root home does not have any parent page and return null.
1429     *
1430     * @return MarkupPath
1431     * @throws ExceptionNotFound
1432     */
1433    public function getParent(): MarkupPath
1434    {
1435
1436        $names = $this->getNames();
1437        if (sizeof($names) == 0) {
1438            throw new ExceptionNotFound("No parent page");
1439        }
1440        $slice = 1;
1441        if ($this->isIndexPage()) {
1442            /**
1443             * The parent of a home page
1444             * is in the parent directory
1445             */
1446            $slice = 2;
1447        }
1448        /**
1449         * Delete the last or the two last parts
1450         */
1451        if (sizeof($names) < $slice) {
1452            throw new ExceptionNotFound("No parent page");
1453        }
1454        /**
1455         * Get the actual directory for a page
1456         * or the parent directory for a home page
1457         */
1458        $parentNames = array_slice($names, 0, sizeof($names) - $slice);
1459        /**
1460         * Create the parent namespace id
1461         */
1462        $parentNamespaceId = implode(WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $parentNames) . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT;
1463        try {
1464            return self::getIndexPageFromNamespace($parentNamespaceId);
1465        } catch (ExceptionBadSyntax $e) {
1466            $message = "Error on getParentPage, null returned - Error: {$e->getMessage()}";
1467            LogUtility::internalError($message);
1468            throw new ExceptionNotFound($message);
1469        }
1470
1471    }
1472
1473    /**
1474     * @throws ExceptionCompile
1475     */
1476    public
1477    function setDescription($description): MarkupPath
1478    {
1479
1480        $this->description
1481            ->setValue($description)
1482            ->sendToWriteStore();
1483        return $this;
1484    }
1485
1486    /**
1487     * @throws ExceptionCompile
1488     * @deprecated uses {@link EndDate} instead
1489     */
1490    public
1491    function setEndDate($value): MarkupPath
1492    {
1493        $this->endDate
1494            ->setFromStoreValue($value)
1495            ->sendToWriteStore();
1496        return $this;
1497    }
1498
1499    /**
1500     * @throws ExceptionCompile
1501     * @deprecated uses {@link StartDate} instead
1502     */
1503    public
1504    function setStartDate($value): MarkupPath
1505    {
1506        $this->startDate
1507            ->setFromStoreValue($value)
1508            ->sendToWriteStore();
1509        return $this;
1510    }
1511
1512    /**
1513     * @throws ExceptionCompile
1514     */
1515    public
1516    function setPublishedDate($value): MarkupPath
1517    {
1518        $this->publishedDate
1519            ->setFromStoreValue($value)
1520            ->sendToWriteStore();
1521        return $this;
1522    }
1523
1524    /**
1525     * Utility to {@link ResourceName::setValue()}
1526     * Used mostly to create page in test
1527     * @throws ExceptionCompile
1528     * @deprecated use not persist
1529     */
1530    public
1531    function setPageName($value): MarkupPath
1532    {
1533        $this->pageName
1534            ->setValue($value)
1535            ->sendToWriteStore();
1536        return $this;
1537    }
1538
1539
1540    /**
1541     * @throws ExceptionCompile
1542     */
1543    public
1544    function setTitle($value): MarkupPath
1545    {
1546        $this->title
1547            ->setValue($value)
1548            ->sendToWriteStore();
1549        return $this;
1550    }
1551
1552    /**
1553     * @throws ExceptionCompile
1554     */
1555    public
1556    function setH1($value): MarkupPath
1557    {
1558        $this->h1
1559            ->setValue($value)
1560            ->sendToWriteStore();
1561        return $this;
1562    }
1563
1564    /**
1565     * @throws Exception
1566     */
1567    public
1568    function setRegion($value): MarkupPath
1569    {
1570        $this->region
1571            ->setFromStoreValue($value)
1572            ->sendToWriteStore();
1573        return $this;
1574    }
1575
1576    /**
1577     * @throws ExceptionCompile
1578     */
1579    public
1580    function setLang($value): MarkupPath
1581    {
1582
1583        $this->lang
1584            ->setFromStoreValue($value)
1585            ->sendToWriteStore();
1586        return $this;
1587    }
1588
1589    /**
1590     * @throws ExceptionCompile
1591     */
1592    public
1593    function setLayout($value): MarkupPath
1594    {
1595        $this->layout
1596            ->setValue($value)
1597            ->sendToWriteStore();
1598        return $this;
1599    }
1600
1601
1602    /**
1603     *
1604     * We manage the properties by setter and getter
1605     *
1606     * Why ?
1607     *   * Because we can capture the updates
1608     *   * Because setter are the entry point to good quality data
1609     *   * Because dokuwiki may cache the metadata (see below)
1610     *
1611     * Note all properties have been migrated
1612     * but they should be initialized below
1613     *
1614     * Dokuwiki cache: the data may be cached without our consent
1615     * The method {@link p_get_metadata()} does it with this logic
1616     * ```
1617     * $cache = ($ID == $id);
1618     * $meta = p_read_metadata($id, $cache);
1619     * ```
1620     */
1621    private
1622    function buildPropertiesFromFileSystem()
1623    {
1624
1625        /**
1626         * New meta system
1627         * Even if it does not exist, the metadata object should be instantiated
1628         * otherwise, there is a null exception
1629         */
1630        $this->cacheExpirationDate = CacheExpirationDate::createForPage($this);
1631        $this->aliases = Aliases::createForPage($this);
1632        $this->pageImages = PageImages::createForPage($this);
1633        $this->pageName = ResourceName::createForResource($this);
1634        $this->cacheExpirationFrequency = CacheExpirationFrequency::createForPage($this);
1635        $this->ldJson = LdJson::createForPage($this);
1636        $this->canonical = Canonical::createForPage($this);
1637        $this->description = PageDescription::createForPage($this);
1638        $this->h1 = PageH1::createForPage($this);
1639        $this->type = PageType::createForPage($this);
1640        $this->creationTime = CreationDate::createForPage($this);
1641        $this->title = PageTitle::createForMarkup($this);
1642        $this->keywords = PageKeywords::createForPage($this);
1643        $this->publishedDate = PagePublicationDate::createFromPage($this);
1644        $this->startDate = StartDate::createFromPage($this);
1645        $this->endDate = EndDate::createFromPage($this);
1646        $this->locale = Locale::createForPage($this);
1647        $this->lang = Lang::createForMarkup($this);
1648        $this->region = Region::createForPage($this);
1649        $this->slug = \ComboStrap\Slug::createForPage($this);
1650        $this->canBeOfLowQuality = LowQualityPageOverwrite::createForPage($this);
1651        $this->lowQualityIndicatorCalculated = LowQualityCalculatedIndicator::createFromPage($this);
1652        $this->qualityMonitoringIndicator = QualityDynamicMonitoringOverwrite::createFromPage($this);
1653        $this->modifiedTime = ModificationDate::createForPage($this);
1654        $this->pageUrlPath = PageUrlPath::createForPage($this);
1655        $this->layout = PageTemplateName::createFromPage($this);
1656
1657    }
1658
1659
1660    function getPageIdAbbr()
1661    {
1662
1663        if ($this->getPageId() === null) return null;
1664        return PageId::getAbbreviated($this->getPageId());
1665
1666    }
1667
1668    public
1669    function setDatabasePage(DatabasePageRow $databasePage): MarkupPath
1670    {
1671        $this->databasePage = $databasePage;
1672        return $this;
1673    }
1674
1675
1676    /**
1677     * @return string|null
1678     *
1679     * @throws ExceptionNotFound
1680     */
1681    public
1682    function getSlug(): string
1683    {
1684        return $this->slug->getValue();
1685    }
1686
1687
1688    /**
1689     * @throws ExceptionCompile
1690     */
1691    public
1692    function setSlug($slug): MarkupPath
1693    {
1694        $this->slug
1695            ->setFromStoreValue($slug)
1696            ->sendToWriteStore();
1697        return $this;
1698    }
1699
1700
1701    /**
1702     * @return string - the id in the Url
1703     */
1704    public function getUrlId(): string
1705    {
1706        return $this->pageUrlPath->getValueOrDefaultAsWikiId();
1707    }
1708
1709
1710    /**
1711     * @throws ExceptionCompile
1712     */
1713    public
1714    function setQualityMonitoringIndicator($boolean): MarkupPath
1715    {
1716        $this->qualityMonitoringIndicator
1717            ->setFromStoreValue($boolean)
1718            ->sendToWriteStore();
1719        return $this;
1720    }
1721
1722    /**
1723     *
1724     * @param $aliasPath - third information - the alias used to build this page
1725     */
1726    public
1727    function setBuildAliasPath($aliasPath)
1728    {
1729        $this->buildAliasPath = $aliasPath;
1730    }
1731
1732    public
1733    function getBuildAlias(): ?Alias
1734    {
1735        if ($this->buildAliasPath === null) return null;
1736        try {
1737            $aliases = $this->getAliases();
1738        } catch (ExceptionNotFound $e) {
1739            // should not
1740            return null;
1741        }
1742        foreach ($aliases as $alias) {
1743            if ($alias->getPath() === $this->buildAliasPath) {
1744                return $alias;
1745            }
1746        }
1747        return null;
1748    }
1749
1750    public
1751    function isDynamicQualityMonitored(): bool
1752    {
1753        if ($this->getQualityMonitoringIndicator() !== null) {
1754            return $this->getQualityMonitoringIndicator();
1755        }
1756        return $this->getDefaultQualityMonitoring();
1757    }
1758
1759    public
1760    function getDefaultQualityMonitoring(): bool
1761    {
1762        if (SiteConfig::getConfValue(QualityMessageHandler::CONF_DISABLE_QUALITY_MONITORING) === 1) {
1763            return false;
1764        } else {
1765            return true;
1766        }
1767    }
1768
1769    /**
1770     * @param MetadataStore|string $store
1771     * @return $this
1772     */
1773    public
1774    function setReadStore($store): MarkupPath
1775    {
1776        $this->readStore = $store;
1777        return $this;
1778    }
1779
1780
1781    /**
1782     * @param array $usages
1783     * @return IFetcherLocalImage[]
1784     */
1785    public
1786    function getImagesForTheFollowingUsages(array $usages): array
1787    {
1788        $usages = array_merge($usages, [PageImageUsage::ALL]);
1789        $images = [];
1790        foreach ($this->getPageMetadataImages() as $pageImage) {
1791            foreach ($usages as $usage) {
1792                if (in_array($usage, $pageImage->getUsages())) {
1793                    $path = $pageImage->getImagePath();
1794                    try {
1795                        $images[] = IFetcherLocalImage::createImageFetchFromPath($path);
1796                    } catch (ExceptionBadArgument $e) {
1797                        LogUtility::error(`The page image $path of the page $this is not an image`);
1798                    } catch (ExceptionBadSyntax $e) {
1799                        LogUtility::error(`The page image $path has a bad syntax`);
1800                    } catch (ExceptionNotExists $e) {
1801                        LogUtility::error(`The page image $path does not exists`);
1802                    }
1803                    continue 2;
1804                }
1805            }
1806        }
1807        return $images;
1808
1809    }
1810
1811
1812    /**
1813     * @throws ExceptionNotFound
1814     */
1815    public
1816    function getKeywords(): array
1817    {
1818        return $this->keywords->getValue();
1819    }
1820
1821    /**
1822     * @throws ExceptionNotFound
1823     */
1824    public function getKeywordsOrDefault(): array
1825    {
1826        return $this->keywords->getValueOrDefaults();
1827    }
1828
1829
1830    /**
1831     * @throws ExceptionCompile
1832     */
1833    public
1834    function setKeywords($value): MarkupPath
1835    {
1836        $this->keywords
1837            ->setFromStoreValue($value)
1838            ->sendToWriteStore();
1839        return $this;
1840    }
1841
1842    /**
1843     * @return DateTime|null
1844     * @throws ExceptionNotFound
1845     * @deprecated for {@link CacheExpirationDate}
1846     */
1847    public
1848    function getCacheExpirationDate(): ?DateTime
1849    {
1850        return $this->cacheExpirationDate->getValue();
1851    }
1852
1853    /**
1854     * @return DateTime|null
1855     * @throws ExceptionNotFound
1856     * @deprecated for {@link CacheExpirationDate}
1857     */
1858    public
1859    function getDefaultCacheExpirationDate(): ?DateTime
1860    {
1861        return $this->cacheExpirationDate->getDefaultValue();
1862    }
1863
1864    /**
1865     * @return string|null
1866     * @throws ExceptionNotFound
1867     * @deprecated for {@link CacheExpirationFrequency}
1868     */
1869    public
1870    function getCacheExpirationFrequency(): string
1871    {
1872        return $this->cacheExpirationFrequency->getValue();
1873    }
1874
1875
1876    /**
1877     * @param DateTime $cacheExpirationDate
1878     * @return $this
1879     * @deprecated for {@link CacheExpirationDate}
1880     */
1881    public
1882    function setCacheExpirationDate(DateTime $cacheExpirationDate): MarkupPath
1883    {
1884        $this->cacheExpirationDate->setValue($cacheExpirationDate);
1885        return $this;
1886    }
1887
1888
1889    /**
1890     * Utility class
1891     * Get the instructions document as if it was the main page.
1892     * Ie the context path is:
1893     *  * the markup path itself)
1894     *  * or the default context path if the path cannot be transformed as wiki path.
1895     */
1896    public function getInstructionsDocument(): FetcherMarkup
1897    {
1898
1899        $path = $this->getPathObject();
1900        try {
1901            $contextPath = $path->toWikiPath();
1902        } catch (ExceptionCast $e) {
1903            $contextPath = ExecutionContext::getActualOrCreateFromEnv()
1904                ->getDefaultContextPath();
1905        }
1906        return FetcherMarkup::confRoot()
1907            ->setRequestedExecutingPath($path)
1908            ->setRequestedContextPath($contextPath)
1909            ->setRequestedMimeToInstructions()
1910            ->build();
1911
1912    }
1913
1914    public
1915    function delete()
1916    {
1917
1918        Index::getOrCreate()->deletePage($this);
1919        saveWikiText($this->getWikiId(), "", "Delete");
1920
1921    }
1922
1923    /**
1924     * @return Url -the absolute canonical url
1925     */
1926    public
1927    function getAbsoluteCanonicalUrl(): Url
1928    {
1929        return $this->getCanonicalUrl()->toAbsoluteUrl();
1930    }
1931
1932
1933    public
1934    function getReadStoreOrDefault(): MetadataStore
1935    {
1936        if ($this->readStore === null) {
1937            /**
1938             * No cache please if not set
1939             * Cache should be in the MetadataDokuWikiStore
1940             * that is page requested scoped and not by slot
1941             */
1942            return MetadataDokuWikiStore::getOrCreateFromResource($this);
1943        }
1944        if (!($this->readStore instanceof MetadataStore)) {
1945            $this->readStore = MetadataStoreAbs::toMetadataStore($this->readStore, $this);
1946        }
1947        return $this->readStore;
1948    }
1949
1950    /**
1951     * @return Path
1952     * A markup path wraps a path
1953     */
1954    public function getPathObject(): Path
1955    {
1956        return $this->path;
1957    }
1958
1959
1960    /**
1961     * A shortcut for {@link MarkupPath::getPathObject()::getDokuwikiId()}
1962     *
1963     * @throws ExceptionBadArgument - if the markup path is not a {@link WikiPath}
1964     */
1965    public
1966    function getWikiId(): string
1967    {
1968        $path = $this->getPathObject();
1969        return WikiPath::createFromPathObject($path)->getWikiId();
1970    }
1971
1972    public
1973    function getUid(): Metadata
1974    {
1975        return PageId::createForPage($this);
1976    }
1977
1978
1979    public
1980    function getAbsolutePath(): string
1981    {
1982        return WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $this->getWikiId();
1983    }
1984
1985    /**
1986     * Todo, it should be a property of the markup not every markup file are main page markup.
1987     * @return string
1988     */
1989    function getType(): string
1990    {
1991        return self::TYPE;
1992    }
1993
1994    /**
1995     * @return PageUrlPath
1996     * @deprecated use {@link PageUrlPath} instead
1997     */
1998    public
1999    function getUrlPathObject(): PageUrlPath
2000    {
2001        return $this->pageUrlPath;
2002    }
2003
2004
2005    public function getSideSlot(): ?MarkupPath
2006    {
2007
2008        /**
2009         * Only primary slot have a side slot
2010         * Root Home page does not have one either
2011         */
2012        if ($this->isSlot()) {
2013            return null;
2014        }
2015
2016        $nearestMainFooter = $this->findNearest(SlotSystem::getSidebarName());
2017        if ($nearestMainFooter === false) {
2018            return null;
2019        }
2020        return MarkupPath::createMarkupFromId($nearestMainFooter);
2021
2022
2023    }
2024
2025    /**
2026     * @param $pageName
2027     * @return false|string
2028     */
2029    private function findNearest($pageName)
2030    {
2031        global $ID;
2032        $keep = $ID;
2033        try {
2034            $ID = $this->getWikiId();
2035            return page_findnearest($pageName);
2036        } finally {
2037            $ID = $keep;
2038        }
2039
2040    }
2041
2042    /**
2043     * The slots that are independent from the primary slot
2044     *
2045     * @return MarkupPath[]
2046     * @deprecated should be {@link TemplateForWebPage} based
2047     */
2048    public function getPrimaryIndependentSlots(): array
2049    {
2050        $secondarySlots = [];
2051        $sideSlot = $this->getSideSlot();
2052        if ($sideSlot !== null) {
2053            $secondarySlots[] = $sideSlot;
2054        }
2055        return $secondarySlots;
2056    }
2057
2058
2059    public function isHidden(): bool
2060    {
2061        return isHiddenPage($this->getWikiId());
2062    }
2063
2064
2065    public function getPrimaryHeaderPage(): ?MarkupPath
2066    {
2067        $nearest = page_findnearest(SlotSystem::getMainHeaderSlotName());
2068        if ($nearest === false) {
2069            return null;
2070        }
2071        return MarkupPath::createMarkupFromId($nearest);
2072    }
2073
2074    public function createPageFetcherHtml(): FetcherPage
2075    {
2076        return FetcherPage::createPageFetcherFromMarkupPath($this);
2077    }
2078
2079    public function getHttpResponse(): HttpResponse
2080    {
2081        return HttpRequest::fetchXhtmlPageResponse($this->getWikiId());
2082    }
2083
2084    /**
2085     * @return Outline
2086     * @deprecated uses {@link FetcherMarkup::getOutline()} instead
2087     */
2088    public function getOutline(): Outline
2089    {
2090
2091        return $this->getInstructionsDocument()->getOutline();
2092
2093    }
2094
2095
2096    public function persistToDefaultMetaStore(): MarkupPath
2097    {
2098        $this->getReadStoreOrDefault()->persist();
2099        return $this;
2100    }
2101
2102    public function getInstructionsPath(): LocalPath
2103    {
2104
2105        $instructionsDocument = $this->getInstructionsDocument();
2106        return $instructionsDocument->getInstructionsPath();
2107
2108    }
2109
2110    public function setContent(string $textContent): MarkupPath
2111    {
2112        FileSystems::setContent($this, $textContent);
2113        return $this;
2114    }
2115
2116    /**
2117     * @throws ExceptionNotExists - if the path does not exist
2118     */
2119    public function createHtmlFetcherWithRequestedPathAsContextPath(): FetcherMarkup
2120    {
2121        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
2122        $executingPath = $this->getPathObject();
2123        $requestedPath = $executionContext->getRequestedPath();
2124        $requestedMarkupPath = MarkupPath::createPageFromPathObject($requestedPath);
2125
2126        if ($requestedMarkupPath->isSlot()) {
2127            try {
2128                $markupContextPath = SlotSystem::getContextPath();
2129                SlotSystem::sendContextPathMessage($markupContextPath);
2130                $requestedPath = $markupContextPath->toWikiPath();
2131            } catch (\Exception $e) {
2132                // should not
2133            }
2134        }
2135        return FetcherMarkup::confRoot()
2136            ->setRequestedMimeToXhtml()
2137            ->setRequestedContextPath($requestedPath)
2138            ->setRequestedExecutingPath($executingPath)
2139            ->build();
2140    }
2141
2142    public
2143    function isRootItemPage(): bool
2144    {
2145        try {
2146            if ($this->isIndexPage()) {
2147                return false;
2148            }
2149            $parent = $this->getParent();
2150            if ($parent->isRootHomePage()) {
2151                return true;
2152            }
2153            return false;
2154        } catch (ExceptionNotFound $e) {
2155            return false;
2156        }
2157    }
2158
2159    private
2160    function getPrimaryFooterPage(): ?MarkupPath
2161    {
2162        $nearest = page_findnearest(SlotSystem::getMainFooterSlotName());
2163        if ($nearest === false) {
2164            return null;
2165        }
2166        return MarkupPath::createMarkupFromId($nearest);
2167    }
2168
2169    /**
2170     * Set the page path to an index page for a directory path
2171     * @return void
2172     */
2173    private
2174    function setCorrectPathForDirectoryToIndexPage(): void
2175    {
2176
2177
2178        if (!($this->path instanceof WikiPath)) {
2179            return;
2180        }
2181        /**
2182         * @var $path WikiPath
2183         */
2184        $path = $this->path;
2185
2186        /**
2187         * We correct the path
2188         * We don't return a page because it does not work in a constructor
2189         */
2190        $startPageName = Site::getIndexPageName();
2191        $indexPath = $path->resolveId($startPageName);
2192        if (FileSystems::exists($indexPath)) {
2193            // start page inside namespace
2194            $this->path = $indexPath;
2195            return;
2196        }
2197
2198        // page named like the NS inside the NS
2199        try {
2200            $parentName = $this->getLastNameWithoutExtension();
2201            $nsInsideNsIndex = $this->path->resolveId($parentName);
2202            if (FileSystems::exists($nsInsideNsIndex)) {
2203                $this->path = $nsInsideNsIndex;
2204                return;
2205            }
2206        } catch (ExceptionNotFound $e) {
2207            // no last name
2208        }
2209
2210        // We don't support the child page
2211        // Does not exist but can be used by hierarchical function
2212        $this->path = $indexPath;
2213    }
2214
2215
2216    public
2217    function getUidObject(): Metadata
2218    {
2219        if ($this->uidObject === null) {
2220            try {
2221                $this->uidObject = Meta\Api\MetadataSystem::toMetadataObject($this->getUid())
2222                    ->setResource($this);
2223            } catch (ExceptionBadArgument $e) {
2224                throw new ExceptionRuntimeInternal("Uid object is a metadata object. It should not happen.", self::CANONICAL_PAGE, 1, $e);
2225            }
2226        }
2227
2228        return $this->uidObject;
2229    }
2230
2231    function getExtension(): string
2232    {
2233        return $this->path->getExtension();
2234    }
2235
2236    function getLastNameWithoutExtension(): string
2237    {
2238        return $this->path->getLastNameWithoutExtension();
2239    }
2240
2241    function getScheme(): string
2242    {
2243        return MarkupFileSystem::SCHEME;
2244    }
2245
2246    function getLastName(): string
2247    {
2248        return $this->path->getLastName();
2249    }
2250
2251    function getNames()
2252    {
2253        return $this->path->getNames();
2254    }
2255
2256    function toAbsoluteId(): string
2257    {
2258        return $this->path->toAbsoluteId();
2259    }
2260
2261    function toUriString(): string
2262    {
2263        return $this->path->toUriString();
2264    }
2265
2266    function toAbsolutePath(): Path
2267    {
2268        return $this->path->toAbsolutePath();
2269    }
2270
2271
2272    function resolve(string $name): Path
2273    {
2274        return $this->path->resolve($name);
2275    }
2276
2277
2278    function getUrl(): Url
2279    {
2280        return FetcherPage::createPageFetcherFromMarkupPath($this)
2281            ->getFetchUrl();
2282    }
2283
2284    function getHost(): string
2285    {
2286        return $this->path->getHost();
2287    }
2288
2289    public
2290    function __toString(): string
2291    {
2292        return $this->path->__toString();
2293    }
2294
2295    /**
2296     * @throws ExceptionBadSyntax
2297     * @throws ExceptionBadArgument
2298     */
2299    public
2300    static function createFromUri(string $uri): MarkupPath
2301    {
2302        $path = FileSystems::createPathFromUri($uri);
2303        return new MarkupPath($path);
2304    }
2305
2306
2307}
2308