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