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 /** 158 * We store only the static public pages 159 * without messages (they are dynamically insert) 160 */ 161 $hasMessages = $this->pageTemplate->hasMessages(); 162 if ($isPublicStaticPage && !$hasMessages) { 163 $cache->storeCache($htmlDocumentString); 164 } 165 166 return $htmlDocumentString; 167 168 } 169 170 171 /** 172 * 173 */ 174 function getBuster(): string 175 { 176 return ""; 177 } 178 179 public function getMime(): Mime 180 { 181 return Mime::create(Mime::HTML); 182 } 183 184 public function getFetcherName(): string 185 { 186 return self::NAME; 187 } 188 189 /** 190 * @throws ExceptionBadSyntax 191 * @throws ExceptionNotFound 192 * @throws ExceptionBadArgument 193 */ 194 public function getFetchAsHtmlDom(): XmlDocument 195 { 196 $content = $this->getFetchString(); 197 return XmlDocument::createHtmlDocFromMarkup($content); 198 } 199 200 201 /** 202 * 203 */ 204 private function buildObjectIfNeeded(): void 205 { 206 207 if ($this->build) { 208 if ($this->closed) { 209 throw new ExceptionRuntimeInternal("This fetcher page object has already been close and cannot be reused", self::NAME); 210 } 211 return; 212 } 213 214 $this->build = true; 215 216 $this->requestedMarkupPath = MarkupPath::createPageFromPathObject($this->getRequestedPath()); 217 218 $pageLang = Lang::createForMarkup($this->getRequestedPage()); 219 $title = PageTitle::createForMarkup($this->getRequestedPage())->getValueOrDefault(); 220 221 $layoutName = $this->getRequestedTemplateOrDefault(); 222 $this->pageTemplate = TemplateForWebPage::create() 223 ->setRequestedTemplateName($layoutName) 224 ->setRequestedContextPath($this->getRequestedPath()) 225 ->setRequestedLang($pageLang) 226 ->setRequestedTitle($title); 227 228 /** 229 * Build the cache 230 * The template is mandatory as cache key 231 * If the user change it, the cache should be disabled 232 */ 233 $this->fetcherCache = FetcherCache::createFrom($this, [$layoutName]); 234 // the requested page 235 $this->fetcherCache->addFileDependency($this->getRequestedPath()); 236 if (PluginUtility::isDevOrTest()) { 237 /** 238 * The hbs template dependency 239 * (only on test/dev) 240 */ 241 try { 242 $this->fetcherCache->addFileDependency($this->pageTemplate->getCssPath()); 243 } catch (ExceptionNotFound $e) { 244 // no css file 245 } 246 try { 247 $this->fetcherCache->addFileDependency($this->pageTemplate->getJsPath()); 248 } catch (ExceptionNotFound $e) { 249 // no js 250 } 251 // mandatory, should not throw 252 try { 253 $this->fetcherCache->addFileDependency($this->pageTemplate->getHtmlTemplatePath()); 254 } catch (ExceptionNotFound $e) { 255 LogUtility::internalError("The html template should be found", self::CANONICAL, $e); 256 } 257 } 258 /** 259 * The Slots of the requested template 260 */ 261 foreach ($this->pageTemplate->getSlots() as $templateSlot) { 262 try { 263 $this->fetcherCache->addFileDependency($templateSlot->getMarkupFetcher()->getSourcePath()); 264 } catch (ExceptionNotFound $e) { 265 // no slot page found 266 } 267 } 268 269 } 270 271 public function getRequestedPath(): WikiPath 272 { 273 return $this->getSourcePath(); 274 } 275 276 public function setRequestedLayout(string $layoutValue): FetcherPage 277 { 278 $this->requestedLayout = $layoutValue; 279 return $this; 280 } 281 282 /** 283 * @throws ExceptionNotFound 284 */ 285 private function getRequestedTemplate(): string 286 { 287 if (!isset($this->requestedLayout)) { 288 throw new ExceptionNotFound("No requested layout"); 289 } 290 return $this->requestedLayout; 291 } 292 293 294 public function setRequestedPath(Path $requestedPath): FetcherPage 295 { 296 try { 297 $requestedPath = WikiPath::createFromPathObject($requestedPath); 298 } catch (ExceptionBadArgument $e) { 299 throw new ExceptionRuntimeInternal("Not a local wiki path", self::NAME, 1, $e); 300 } 301 $this->setSourcePath($requestedPath); 302 return $this; 303 } 304 305 306 /** 307 * 308 */ 309 public function close(): FetcherPage 310 { 311 // nothing to do 312 return $this; 313 } 314 315 316 private function getRequestedPage(): MarkupPath 317 { 318 $this->buildObjectIfNeeded(); 319 return $this->requestedMarkupPath; 320 } 321 322 323 /** 324 * The cache stores only public pages. 325 * 326 * ie when the user is unknown 327 * and there is no railbar 328 * (railbar is dynamically created even for the public 329 * and the javascript for the menu item expects to run after a window load event) 330 * 331 * @return bool 332 */ 333 private function isPublicStaticPage(): bool 334 { 335 $privateRailbar = SiteConfig::getConfValue(FetcherRailBar::CONF_PRIVATE_RAIL_BAR, FetcherRailBar::CONF_PRIVATE_RAIL_BAR_DEFAULT); 336 $isLoggedIn = Identity::isLoggedIn(); 337 return $privateRailbar === 1 && !$isLoggedIn; 338 } 339 340 private function getRequestedTemplateOrDefault(): string 341 { 342 try { 343 return $this->getRequestedTemplate(); 344 } catch (ExceptionNotFound $e) { 345 return PageTemplateName::createFromPage($this->getRequestedPage())->getValueOrDefault(); 346 } 347 } 348 349 public function getContentCachePath(): LocalPath 350 { 351 $this->buildObjectIfNeeded(); 352 return $this->fetcherCache->getFile(); 353 } 354 355 public function getLabel(): string 356 { 357 $sourcePath = $this->getSourcePath(); 358 return ResourceName::getFromPath($sourcePath); 359 } 360 361 362} 363