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