xref: /plugin/combo/ComboStrap/FetcherPage.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\Meta\Field\PageTemplateName;
7use ComboStrap\Web\Url;
8use ComboStrap\Web\UrlEndpoint;
9use ComboStrap\Xml\XmlDocument;
10
11class FetcherPage extends IFetcherAbs implements IFetcherSource, IFetcherString
12{
13
14    use FetcherTraitWikiPath;
15
16    const NAME = "page";
17    const CANONICAL = "page";
18    const PURGE = "purge";
19
20    private string $requestedLayout;
21    private bool $build = false;
22    private bool $closed = false;
23
24
25    private MarkupPath $requestedMarkupPath;
26    private string $requestedLayoutName;
27    private TemplateForWebPage $pageTemplate;
28    private FetcherCache $fetcherCache;
29
30
31    public static function createPageFetcherFromPath(Path $path): FetcherPage
32    {
33        $fetcherPage = new FetcherPage();
34        $fetcherPage->setRequestedPath($path);
35        return $fetcherPage;
36    }
37
38    public static function createPageFetcherFromId(string $wikiId): FetcherPage
39    {
40        $wikiPath = WikiPath::createMarkupPathFromId($wikiId);
41        return self::createPageFetcherFromPath($wikiPath);
42    }
43
44    /**
45     * @throws ExceptionBadArgument
46     */
47    public static function createPageFragmentFetcherFromUrl(Url $fetchUrl): FetcherPage
48    {
49        $pageFragment = new FetcherPage();
50        $pageFragment->buildFromUrl($fetchUrl);
51        return $pageFragment;
52    }
53
54    /**
55     * @param Url|null $url
56     * @return Url
57     *
58     * Note: The fetch url is the {@link FetcherCache keyCache}
59     */
60    function getFetchUrl(Url $url = null): Url
61    {
62        /**
63         * Overwrite default fetcher endpoint
64         * that is {@link UrlEndpoint::createFetchUrl()}
65         */
66        $url = UrlEndpoint::createDokuUrl();
67        $url = parent::getFetchUrl($url);
68        try {
69            $template = $this->getRequestedTemplate();
70            $url->addQueryParameter(PageTemplateName::PROPERTY_NAME, $template);
71        } catch (ExceptionNotFound $e) {
72            // ok
73        }
74
75        $this->addLocalPathParametersToFetchUrl($url, DokuwikiId::DOKUWIKI_ID_ATTRIBUTE);
76
77        // the drive is not needed
78        $url->deleteQueryParameter(WikiPath::DRIVE_ATTRIBUTE);
79
80        // this is the default fetcher, no need to add it as parameter
81        $url->deleteQueryParameter(IFetcher::FETCHER_KEY);
82
83        return $url;
84    }
85
86    /**
87     * @throws ExceptionBadArgument
88     * @throws ExceptionBadSyntax
89     * @throws ExceptionNotExists
90     * @throws ExceptionNotFound
91     */
92    public function buildFromTagAttributes(TagAttributes $tagAttributes): IFetcher
93    {
94        parent::buildFromTagAttributes($tagAttributes);
95        $this->buildOriginalPathFromTagAttributes($tagAttributes);
96        $layout = $tagAttributes->getValueAndRemoveIfPresent(PageTemplateName::PROPERTY_NAME);
97        if ($layout !== null) {
98            $this->setRequestedLayout($layout);
99        }
100        /**
101         * Purge the cache
102         */
103        $tagAttributes->getValueAndRemoveIfPresent(self::PURGE);
104        return $this;
105    }
106
107
108    public static function createPageFetcherFromMarkupPath(MarkupPath $markupPath): FetcherPage
109    {
110        return self::createPageFetcherFromPath($markupPath->getPathObject());
111    }
112
113
114    /**
115     * @return string
116     */
117    public function getFetchString(): string
118    {
119
120        $this->buildObjectIfNeeded();
121
122        $cache = $this->fetcherCache;
123
124        /**
125         * Public static cache
126         * (Do we create the page or return the cache)
127         */
128        $isPublicStaticPage = $this->isPublicStaticPage();
129        $isCacheUsable = $cache->isCacheUsable();
130        if ($isCacheUsable && $isPublicStaticPage) {
131            try {
132                return FileSystems::getContent($cache->getFile());
133            } catch (ExceptionNotFound $e) {
134                // the cache file should exists
135                LogUtility::internalError("The cache HTML fragment file was not found", self::NAME);
136            }
137        }
138
139        /**
140         * Generate the whole html page via the layout
141         */
142        $htmlDocumentString = $this->pageTemplate->render();
143
144        /**
145         * We store only the static public pages
146         * without messages (they are dynamically insert)
147         */
148        $hasMessages = $this->pageTemplate->hasMessages();
149        if ($isPublicStaticPage && !$hasMessages) {
150            $cache->storeCache($htmlDocumentString);
151        }
152
153        return $htmlDocumentString;
154
155    }
156
157
158    /**
159     *
160     */
161    function getBuster(): string
162    {
163        return "";
164    }
165
166    public function getMime(): Mime
167    {
168        return Mime::create(Mime::HTML);
169    }
170
171    public function getFetcherName(): string
172    {
173        return self::NAME;
174    }
175
176    /**
177     * @throws ExceptionBadSyntax
178     * @throws ExceptionNotFound
179     * @throws ExceptionBadArgument
180     */
181    public function getFetchAsHtmlDom(): XmlDocument
182    {
183        $content = $this->getFetchString();
184        return XmlDocument::createHtmlDocFromMarkup($content);
185    }
186
187
188    /**
189     *
190     */
191    private function buildObjectIfNeeded(): void
192    {
193
194        if ($this->build) {
195            if ($this->closed) {
196                throw new ExceptionRuntimeInternal("This fetcher page object has already been close and cannot be reused", self::NAME);
197            }
198            return;
199        }
200
201        $this->build = true;
202
203        $this->requestedMarkupPath = MarkupPath::createPageFromPathObject($this->getRequestedPath());
204
205        $pageLang = Lang::createForMarkup($this->getRequestedPage());
206        $title = PageTitle::createForMarkup($this->getRequestedPage())->getValueOrDefault();
207
208        $layoutName = $this->getRequestedTemplateOrDefault();
209        $this->pageTemplate = TemplateForWebPage::create()
210            ->setRequestedTemplateName($layoutName)
211            ->setRequestedContextPath($this->getRequestedPath())
212            ->setRequestedLang($pageLang)
213            ->setRequestedTitle($title);
214
215        /**
216         * Build the cache
217         * The template is mandatory as cache key
218         * If the user change it, the cache should be disabled
219         */
220        $this->fetcherCache = FetcherCache::createFrom($this, [$layoutName]);
221        // the requested page
222        $this->fetcherCache->addFileDependency($this->getRequestedPath());
223        if (PluginUtility::isDevOrTest()) {
224            /**
225             * The hbs template dependency
226             * (only on test/dev)
227             */
228            try {
229                $this->fetcherCache->addFileDependency($this->pageTemplate->getCssPath());
230            } catch (ExceptionNotFound $e) {
231                // no css file
232            }
233            try {
234                $this->fetcherCache->addFileDependency($this->pageTemplate->getJsPath());
235            } catch (ExceptionNotFound $e) {
236                // no js
237            }
238            // mandatory, should not throw
239            try {
240                $this->fetcherCache->addFileDependency($this->pageTemplate->getHtmlTemplatePath());
241            } catch (ExceptionNotFound $e) {
242                LogUtility::internalError("The html template should be found", self::CANONICAL, $e);
243            }
244        }
245        /**
246         * The Slots of the requested template
247         */
248        foreach ($this->pageTemplate->getSlots() as $templateSlot) {
249            try {
250                $this->fetcherCache->addFileDependency($templateSlot->getMarkupFetcher()->getSourcePath());
251            } catch (ExceptionNotFound $e) {
252                // no slot page found
253            }
254        }
255
256    }
257
258    public function getRequestedPath(): WikiPath
259    {
260        return $this->getSourcePath();
261    }
262
263    public function setRequestedLayout(string $layoutValue): FetcherPage
264    {
265        $this->requestedLayout = $layoutValue;
266        return $this;
267    }
268
269    /**
270     * @throws ExceptionNotFound
271     */
272    private function getRequestedTemplate(): string
273    {
274        if (!isset($this->requestedLayout)) {
275            throw new ExceptionNotFound("No requested layout");
276        }
277        return $this->requestedLayout;
278    }
279
280
281    public function setRequestedPath(Path $requestedPath): FetcherPage
282    {
283        try {
284            $requestedPath = WikiPath::createFromPathObject($requestedPath);
285        } catch (ExceptionBadArgument $e) {
286            throw new ExceptionRuntimeInternal("Not a local wiki path", self::NAME, 1, $e);
287        }
288        $this->setSourcePath($requestedPath);
289        return $this;
290    }
291
292
293    /**
294     *
295     */
296    public function close(): FetcherPage
297    {
298        // nothing to do
299        return $this;
300    }
301
302
303    private function getRequestedPage(): MarkupPath
304    {
305        $this->buildObjectIfNeeded();
306        return $this->requestedMarkupPath;
307    }
308
309
310    /**
311     * The cache stores only public pages.
312     *
313     * ie when the user is unknown
314     * and there is no railbar
315     * (railbar is dynamically created even for the public
316     * and the javascript for the menu item expects to run after a window load event)
317     *
318     * @return bool
319     */
320    private function isPublicStaticPage(): bool
321    {
322        $privateRailbar = SiteConfig::getConfValue(FetcherRailBar::CONF_PRIVATE_RAIL_BAR, 0);
323        $isLoggedIn = Identity::isLoggedIn();
324        return $privateRailbar === 1 && !$isLoggedIn;
325    }
326
327    private function getRequestedTemplateOrDefault(): string
328    {
329        try {
330            return $this->getRequestedTemplate();
331        } catch (ExceptionNotFound $e) {
332            return PageTemplateName::createFromPage($this->getRequestedPage())->getValueOrDefault();
333        }
334    }
335
336    public function getContentCachePath(): LocalPath
337    {
338        $this->buildObjectIfNeeded();
339        return $this->fetcherCache->getFile();
340    }
341
342    public function getLabel(): string
343    {
344        $sourcePath = $this->getSourcePath();
345        return ResourceName::getFromPath($sourcePath);
346    }
347
348
349}
350