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