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