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