104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeaunamespace ComboStrap; 404fd306cSNickeau 504fd306cSNickeau 604fd306cSNickeauuse ComboStrap\Meta\Field\PageTemplateName; 704fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 804fd306cSNickeauuse ComboStrap\Web\UrlEndpoint; 904fd306cSNickeauuse ComboStrap\Xml\XmlDocument; 1004fd306cSNickeauuse ComboStrap\Xml\XmlElement; 1104fd306cSNickeauuse Symfony\Component\Yaml\Yaml; 1204fd306cSNickeau 1304fd306cSNickeau/** 1404fd306cSNickeau * A page template is the object 1504fd306cSNickeau * that generates a HTML page 1604fd306cSNickeau * (ie the templating engine) 1704fd306cSNickeau * 1804fd306cSNickeau * It's used by Fetcher that creates pages such 1904fd306cSNickeau * as {@link FetcherPage}, {@link FetcherMarkupWebcode} or {@link FetcherPageBundler} 2004fd306cSNickeau */ 2104fd306cSNickeauclass TemplateForWebPage 2204fd306cSNickeau{ 2304fd306cSNickeau 2404fd306cSNickeau 2504fd306cSNickeau /** 2604fd306cSNickeau * An internal configuration 2704fd306cSNickeau * to tell if the page is social 2804fd306cSNickeau * (ie seo, search engine, friendly) 2904fd306cSNickeau */ 3004fd306cSNickeau const CONF_INTERNAL_IS_SOCIAL = "web-page-is-social"; 3104fd306cSNickeau 3204fd306cSNickeau /** 3304fd306cSNickeau * DocType is required by bootstrap and chrome 3404fd306cSNickeau * https://developer.chrome.com/docs/lighthouse/best-practices/doctype/ 3504fd306cSNickeau * https://getbootstrap.com/docs/5.0/getting-started/introduction/#html5-doctype 3604fd306cSNickeau * <!doctype html> 37*e3d00612Sgerardnico * 38*e3d00612Sgerardnico * The eol `\n` is needed for lightouse 3904fd306cSNickeau */ 40*e3d00612Sgerardnico const DOCTYPE = "<!doctype html>\n"; 4104fd306cSNickeau 4204fd306cSNickeau private array $templateDefinition; 4304fd306cSNickeau const CANONICAL = "template"; 4404fd306cSNickeau 4504fd306cSNickeau 4604fd306cSNickeau public const UTF_8_CHARSET_VALUE = "utf-8"; 4704fd306cSNickeau public const VIEWPORT_RESPONSIVE_VALUE = "width=device-width, initial-scale=1"; 4804fd306cSNickeau public const TASK_RUNNER_ID = "task-runner"; 4904fd306cSNickeau public const APPLE_TOUCH_ICON_REL_VALUE = "apple-touch-icon"; 5004fd306cSNickeau 5104fd306cSNickeau public const PRELOAD_TAG = "preload"; 5204fd306cSNickeau 5304fd306cSNickeau private string $templateName; 5404fd306cSNickeau 5504fd306cSNickeau 5604fd306cSNickeau private string $requestedTitle; 5704fd306cSNickeau 5804fd306cSNickeau 5904fd306cSNickeau private bool $requestedEnableTaskRunner = true; 6004fd306cSNickeau private WikiPath $requestedContextPath; 6104fd306cSNickeau private Lang $requestedLang; 6204fd306cSNickeau private Toc $toc; 6304fd306cSNickeau private bool $isSocial; 6404fd306cSNickeau private string $mainContent; 6504fd306cSNickeau private string $templateString; 6604fd306cSNickeau private array $model; 6704fd306cSNickeau private bool $hadMessages = false; 6804fd306cSNickeau private string $requestedTheme; 6904fd306cSNickeau private bool $isIframe = false; 7004fd306cSNickeau private array $slots; 7104fd306cSNickeau 7204fd306cSNickeau 7304fd306cSNickeau public static function create(): TemplateForWebPage 7404fd306cSNickeau { 7504fd306cSNickeau return new TemplateForWebPage(); 7604fd306cSNickeau } 7704fd306cSNickeau 7804fd306cSNickeau public static function config(): TemplateForWebPage 7904fd306cSNickeau { 8004fd306cSNickeau return new TemplateForWebPage(); 8104fd306cSNickeau } 8204fd306cSNickeau 8304fd306cSNickeau public static function getPoweredBy(): string 8404fd306cSNickeau { 8504fd306cSNickeau $domain = PluginUtility::$URL_APEX; 8604fd306cSNickeau $version = PluginUtility::$INFO_PLUGIN['version'] . " (" . PluginUtility::$INFO_PLUGIN['date'] . ")"; 8704fd306cSNickeau $poweredBy = "<div class=\"mx-auto\" style=\"width: 300px;text-align: center;margin-bottom: 1rem\">"; 8804fd306cSNickeau $poweredBy .= " <small><i>Powered by <a href=\"$domain\" title=\"ComboStrap " . $version . "\" style=\"color:#495057\">ComboStrap</a></i></small>"; 8904fd306cSNickeau $poweredBy .= '</div>'; 9004fd306cSNickeau return $poweredBy; 9104fd306cSNickeau } 9204fd306cSNickeau 9304fd306cSNickeau 9404fd306cSNickeau /** 9504fd306cSNickeau * @throws ExceptionNotFound 9604fd306cSNickeau */ 9704fd306cSNickeau public function getHtmlTemplatePath(): LocalPath 9804fd306cSNickeau { 9904fd306cSNickeau return $this->getEngine()->searchTemplateByName($this->templateName . "." . TemplateEngine::EXTENSION_HBS); 10004fd306cSNickeau } 10104fd306cSNickeau 10204fd306cSNickeau public function setTemplateString(string $templateString): TemplateForWebPage 10304fd306cSNickeau { 10404fd306cSNickeau $this->templateString = $templateString; 10504fd306cSNickeau return $this; 10604fd306cSNickeau } 10704fd306cSNickeau 10804fd306cSNickeau public function setModel(array $model): TemplateForWebPage 10904fd306cSNickeau { 11004fd306cSNickeau $this->model = $model; 11104fd306cSNickeau return $this; 11204fd306cSNickeau } 11304fd306cSNickeau 11404fd306cSNickeau /** 11504fd306cSNickeau * @return WikiPath from where the markup slot should be searched 11604fd306cSNickeau * @throws ExceptionNotFound 11704fd306cSNickeau */ 11804fd306cSNickeau public function getRequestedContextPath(): WikiPath 11904fd306cSNickeau { 12004fd306cSNickeau if (!isset($this->requestedContextPath)) { 12104fd306cSNickeau throw new ExceptionNotFound("A requested context path was not found"); 12204fd306cSNickeau } 12304fd306cSNickeau return $this->requestedContextPath; 12404fd306cSNickeau } 12504fd306cSNickeau 12604fd306cSNickeau /** 12704fd306cSNickeau * 12804fd306cSNickeau * @return string - the page as html string (not dom because that's not how works dokuwiki) 12904fd306cSNickeau * 13004fd306cSNickeau */ 13104fd306cSNickeau public function render(): string 13204fd306cSNickeau { 13304fd306cSNickeau 13404fd306cSNickeau $executionContext = (ExecutionContext::getActualOrCreateFromEnv()) 13504fd306cSNickeau ->setExecutingPageTemplate($this); 13604fd306cSNickeau try { 13704fd306cSNickeau 13804fd306cSNickeau 13904fd306cSNickeau $pageTemplateEngine = $this->getEngine(); 14004fd306cSNickeau if ($this->isTemplateStringExecutionMode()) { 14104fd306cSNickeau $template = $this->templateString; 14204fd306cSNickeau } else { 14304fd306cSNickeau $pageTemplateEngine = $this->getEngine(); 14404fd306cSNickeau $template = $this->getTemplateName(); 14504fd306cSNickeau if (!$pageTemplateEngine->templateExists($template)) { 14604fd306cSNickeau $defaultTemplate = PageTemplateName::HOLY_TEMPLATE_VALUE; 14704fd306cSNickeau LogUtility::warning("The template ($template) was not found, the default template ($defaultTemplate) was used instead."); 14804fd306cSNickeau $template = $defaultTemplate; 14904fd306cSNickeau $this->setRequestedTemplateName($template); 15004fd306cSNickeau } 15104fd306cSNickeau } 15204fd306cSNickeau 15304fd306cSNickeau /** 15404fd306cSNickeau * Get model should came after template validation 15504fd306cSNickeau * as the template definition is named dependent 15604fd306cSNickeau * (Create a builder, nom de dieu) 15704fd306cSNickeau */ 15804fd306cSNickeau $model = $this->getModel(); 15904fd306cSNickeau 16004fd306cSNickeau 16104fd306cSNickeau return self::DOCTYPE . $pageTemplateEngine->renderWebPage($template, $model); 16204fd306cSNickeau 16304fd306cSNickeau 16404fd306cSNickeau } finally { 16504fd306cSNickeau $executionContext 16604fd306cSNickeau ->closeExecutingPageTemplate(); 16704fd306cSNickeau } 16804fd306cSNickeau 16904fd306cSNickeau } 17004fd306cSNickeau 17104fd306cSNickeau /** 17204fd306cSNickeau * @return string[] 17304fd306cSNickeau */ 17404fd306cSNickeau public function getElementIds(): array 17504fd306cSNickeau { 17604fd306cSNickeau $definition = $this->getDefinition(); 17704fd306cSNickeau $elements = $definition['elements']; 17804fd306cSNickeau if ($elements == null) { 17904fd306cSNickeau return []; 18004fd306cSNickeau } 18104fd306cSNickeau return $elements; 18204fd306cSNickeau 18304fd306cSNickeau } 18404fd306cSNickeau 18504fd306cSNickeau 18604fd306cSNickeau /** 18704fd306cSNickeau * @throws ExceptionNotFound 18804fd306cSNickeau */ 18904fd306cSNickeau private function getRequestedLang(): Lang 19004fd306cSNickeau { 19104fd306cSNickeau if (!isset($this->requestedLang)) { 19204fd306cSNickeau throw new ExceptionNotFound("No requested lang"); 19304fd306cSNickeau } 19404fd306cSNickeau return $this->requestedLang; 19504fd306cSNickeau } 19604fd306cSNickeau 19704fd306cSNickeau 19804fd306cSNickeau public function getTemplateName(): string 19904fd306cSNickeau { 20004fd306cSNickeau if (isset($this->templateName)) { 20104fd306cSNickeau return $this->templateName; 20204fd306cSNickeau } 20304fd306cSNickeau try { 20404fd306cSNickeau $requestedPath = $this->getRequestedContextPath(); 20504fd306cSNickeau return PageTemplateName::createFromPage(MarkupPath::createPageFromPathObject($requestedPath)) 20604fd306cSNickeau ->getValueOrDefault(); 20704fd306cSNickeau } catch (ExceptionNotFound $e) { 20804fd306cSNickeau // no requested path 20904fd306cSNickeau } 21004fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv() 21104fd306cSNickeau ->getConfig() 21204fd306cSNickeau ->getDefaultLayoutName(); 21304fd306cSNickeau } 21404fd306cSNickeau 21504fd306cSNickeau 21604fd306cSNickeau public function __toString() 21704fd306cSNickeau { 21804fd306cSNickeau return $this->templateName; 21904fd306cSNickeau } 22004fd306cSNickeau 22104fd306cSNickeau /** 22204fd306cSNickeau * @throws ExceptionNotFound 22304fd306cSNickeau */ 22404fd306cSNickeau public function getCssPath(): LocalPath 22504fd306cSNickeau { 22604fd306cSNickeau return $this->getEngine()->searchTemplateByName("$this->templateName.css"); 22704fd306cSNickeau } 22804fd306cSNickeau 22904fd306cSNickeau /** 23004fd306cSNickeau * @throws ExceptionNotFound 23104fd306cSNickeau */ 23204fd306cSNickeau public function getJsPath(): LocalPath 23304fd306cSNickeau { 23404fd306cSNickeau $jsPath = $this->getEngine()->searchTemplateByName("$this->templateName.js"); 23504fd306cSNickeau if (!FileSystems::exists($jsPath)) { 23604fd306cSNickeau throw new ExceptionNotFound("No js file"); 23704fd306cSNickeau } 23804fd306cSNickeau return $jsPath; 23904fd306cSNickeau } 24004fd306cSNickeau 24104fd306cSNickeau public function hasMessages(): bool 24204fd306cSNickeau { 24304fd306cSNickeau return $this->hadMessages; 24404fd306cSNickeau } 24504fd306cSNickeau 24604fd306cSNickeau public function setRequestedTheme(string $themeName): TemplateForWebPage 24704fd306cSNickeau { 24804fd306cSNickeau $this->requestedTheme = $themeName; 24904fd306cSNickeau return $this; 25004fd306cSNickeau } 25104fd306cSNickeau 25204fd306cSNickeau public function hasElement(string $elementId): bool 25304fd306cSNickeau { 25404fd306cSNickeau return in_array($elementId, $this->getElementIds()); 25504fd306cSNickeau } 25604fd306cSNickeau 25704fd306cSNickeau public function isSocial(): bool 25804fd306cSNickeau { 25904fd306cSNickeau if (isset($this->isSocial)) { 26004fd306cSNickeau return $this->isSocial; 26104fd306cSNickeau } 26204fd306cSNickeau try { 26304fd306cSNickeau $path = $this->getRequestedContextPath(); 26404fd306cSNickeau if (!FileSystems::exists($path)) { 26504fd306cSNickeau return false; 26604fd306cSNickeau } 26704fd306cSNickeau $markup = MarkupPath::createPageFromPathObject($path); 26804fd306cSNickeau if ($markup->isSlot()) { 26904fd306cSNickeau // slot are not social 27004fd306cSNickeau return false; 27104fd306cSNickeau } 27204fd306cSNickeau } catch (ExceptionNotFound $e) { 27304fd306cSNickeau // not a path run 27404fd306cSNickeau return false; 27504fd306cSNickeau } 27604fd306cSNickeau if ($this->isIframe) { 27704fd306cSNickeau return false; 27804fd306cSNickeau } 27904fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv() 28004fd306cSNickeau ->getConfig() 28104fd306cSNickeau ->getBooleanValue(self::CONF_INTERNAL_IS_SOCIAL, true); 28204fd306cSNickeau 28304fd306cSNickeau } 28404fd306cSNickeau 28504fd306cSNickeau public function setIsIframe(bool $isIframe): TemplateForWebPage 28604fd306cSNickeau { 28704fd306cSNickeau $this->isIframe = $isIframe; 28804fd306cSNickeau return $this; 28904fd306cSNickeau } 29004fd306cSNickeau 29104fd306cSNickeau /** 29204fd306cSNickeau * @return TemplateSlot[] 29304fd306cSNickeau */ 29404fd306cSNickeau public function getSlots(): array 29504fd306cSNickeau { 29604fd306cSNickeau if (isset($this->slots)) { 29704fd306cSNickeau return $this->slots; 29804fd306cSNickeau } 29904fd306cSNickeau $this->slots = []; 30004fd306cSNickeau foreach ($this->getElementIds() as $elementId) { 30104fd306cSNickeau if ($elementId === TemplateSlot::MAIN_TOC_ID) { 30204fd306cSNickeau /** 30304fd306cSNickeau * Main toc element is not a slot 30404fd306cSNickeau */ 30504fd306cSNickeau continue; 30604fd306cSNickeau } 30704fd306cSNickeau 30804fd306cSNickeau try { 30904fd306cSNickeau $this->slots[] = TemplateSlot::createFromElementId($elementId, $this->getRequestedContextPath()); 31004fd306cSNickeau } catch (ExceptionNotFound $e) { 31104fd306cSNickeau LogUtility::internalError("This template is not for a markup path, it cannot have slots then."); 31204fd306cSNickeau } 31304fd306cSNickeau } 31404fd306cSNickeau return $this->slots; 31504fd306cSNickeau } 31604fd306cSNickeau 31704fd306cSNickeau 31804fd306cSNickeau /** 31904fd306cSNickeau * Character set 32004fd306cSNickeau * Note: avoid using {@link Html::encode() character entities} in your HTML, 32104fd306cSNickeau * provided their encoding matches that of the document (generally UTF-8) 32204fd306cSNickeau */ 32304fd306cSNickeau private function checkCharSetMeta(XmlElement $head) 32404fd306cSNickeau { 32504fd306cSNickeau $charsetValue = TemplateForWebPage::UTF_8_CHARSET_VALUE; 32604fd306cSNickeau try { 32704fd306cSNickeau $metaCharset = $head->querySelector("meta[charset]"); 32804fd306cSNickeau $charsetActualValue = $metaCharset->getAttribute("charset"); 32904fd306cSNickeau if ($charsetActualValue !== $charsetValue) { 33004fd306cSNickeau LogUtility::warning("The actual charset ($charsetActualValue) should be $charsetValue"); 33104fd306cSNickeau } 33204fd306cSNickeau } catch (ExceptionBadSyntax|ExceptionNotFound $e) { 33304fd306cSNickeau try { 33404fd306cSNickeau $metaCharset = $head->getDocument() 33504fd306cSNickeau ->createElement("meta") 33604fd306cSNickeau ->setAttribute("charset", $charsetValue); 33704fd306cSNickeau $head->appendChild($metaCharset); 33804fd306cSNickeau } catch (\DOMException $e) { 33904fd306cSNickeau throw new ExceptionRuntimeInternal("Bad local name meta, should not occur", self::CANONICAL, 1, $e); 34004fd306cSNickeau } 34104fd306cSNickeau } 34204fd306cSNickeau } 34304fd306cSNickeau 34404fd306cSNickeau /** 34504fd306cSNickeau * @param XmlElement $head 34604fd306cSNickeau * @return void 34704fd306cSNickeau * Adapted from {@link TplUtility::renderFaviconMetaLinks()} 34804fd306cSNickeau */ 34904fd306cSNickeau private function getPageIconHeadLinkHtml(): string 35004fd306cSNickeau { 35104fd306cSNickeau $html = $this->getShortcutFavIconHtmlLink(); 35204fd306cSNickeau $html .= $this->getIconHtmlLink(); 35304fd306cSNickeau $html .= $this->getAppleTouchIconHtmlLink(); 35404fd306cSNickeau return $html; 35504fd306cSNickeau } 35604fd306cSNickeau 35704fd306cSNickeau /** 35804fd306cSNickeau * Add a favIcon.ico 35904fd306cSNickeau * 36004fd306cSNickeau */ 36104fd306cSNickeau private function getShortcutFavIconHtmlLink(): string 36204fd306cSNickeau { 36304fd306cSNickeau 36404fd306cSNickeau $internalFavIcon = WikiPath::createComboResource('images:favicon.ico'); 36504fd306cSNickeau $iconPaths = array( 36604fd306cSNickeau WikiPath::createMediaPathFromId(':favicon.ico'), 36704fd306cSNickeau WikiPath::createMediaPathFromId(':wiki:favicon.ico'), 36804fd306cSNickeau $internalFavIcon 36904fd306cSNickeau ); 37004fd306cSNickeau try { 37104fd306cSNickeau /** 37204fd306cSNickeau * @var WikiPath $icoWikiPath - we give wiki paths, we get wiki path 37304fd306cSNickeau */ 37404fd306cSNickeau $icoWikiPath = FileSystems::getFirstExistingPath($iconPaths); 37504fd306cSNickeau } catch (ExceptionNotFound $e) { 37604fd306cSNickeau LogUtility::internalError("The internal fav icon ($internalFavIcon) should be at minimal found", self::CANONICAL); 37704fd306cSNickeau return ""; 37804fd306cSNickeau } 37904fd306cSNickeau 38004fd306cSNickeau return TagAttributes::createEmpty() 38104fd306cSNickeau ->addOutputAttributeValue("rel", "shortcut icon") 38204fd306cSNickeau ->addOutputAttributeValue("href", FetcherRawLocalPath::createFromPath($icoWikiPath)->getFetchUrl()->toAbsoluteUrl()->toString()) 38304fd306cSNickeau ->toHtmlEmptyTag("link"); 38404fd306cSNickeau 38504fd306cSNickeau } 38604fd306cSNickeau 38704fd306cSNickeau /** 38804fd306cSNickeau * Add Icon Png (16x16 and 32x32) 38904fd306cSNickeau * @return string 39004fd306cSNickeau */ 39104fd306cSNickeau private function getIconHtmlLink(): string 39204fd306cSNickeau { 39304fd306cSNickeau 39404fd306cSNickeau $html = ""; 39504fd306cSNickeau $sizeValues = ["32x32", "16x16"]; 39604fd306cSNickeau foreach ($sizeValues as $sizeValue) { 39704fd306cSNickeau 39804fd306cSNickeau $internalIcon = WikiPath::createComboResource(":images:favicon-$sizeValue.png"); 39904fd306cSNickeau $iconPaths = array( 40004fd306cSNickeau WikiPath::createMediaPathFromId(":favicon-$sizeValue.png"), 40104fd306cSNickeau WikiPath::createMediaPathFromId(":wiki:favicon-$sizeValue.png"), 40204fd306cSNickeau $internalIcon 40304fd306cSNickeau ); 40404fd306cSNickeau try { 40504fd306cSNickeau /** 40604fd306cSNickeau * @var WikiPath $iconPath - to say to the linter that this is a wiki path 40704fd306cSNickeau */ 40804fd306cSNickeau $iconPath = FileSystems::getFirstExistingPath($iconPaths); 40904fd306cSNickeau } catch (ExceptionNotFound $e) { 41004fd306cSNickeau LogUtility::internalError("The internal icon ($internalIcon) should be at minimal found", self::CANONICAL); 41104fd306cSNickeau continue; 41204fd306cSNickeau } 41304fd306cSNickeau $html .= TagAttributes::createEmpty() 41404fd306cSNickeau ->addOutputAttributeValue("rel", "icon") 41504fd306cSNickeau ->addOutputAttributeValue("sizes", $sizeValue) 41604fd306cSNickeau ->addOutputAttributeValue("type", Mime::PNG) 41704fd306cSNickeau ->addOutputAttributeValue("href", FetcherRawLocalPath::createFromPath($iconPath)->getFetchUrl()->toAbsoluteUrl()->toString()) 41804fd306cSNickeau ->toHtmlEmptyTag("link"); 41904fd306cSNickeau } 42004fd306cSNickeau return $html; 42104fd306cSNickeau } 42204fd306cSNickeau 42304fd306cSNickeau /** 42404fd306cSNickeau * Add Apple touch icon 42504fd306cSNickeau * 42604fd306cSNickeau * @return string 42704fd306cSNickeau */ 42804fd306cSNickeau private function getAppleTouchIconHtmlLink(): string 42904fd306cSNickeau { 43004fd306cSNickeau 43104fd306cSNickeau $internalIcon = WikiPath::createComboResource(":images:apple-touch-icon.png"); 43204fd306cSNickeau $iconPaths = array( 43304fd306cSNickeau WikiPath::createMediaPathFromId(":apple-touch-icon.png"), 43404fd306cSNickeau WikiPath::createMediaPathFromId(":wiki:apple-touch-icon.png"), 43504fd306cSNickeau $internalIcon 43604fd306cSNickeau ); 43704fd306cSNickeau try { 43804fd306cSNickeau /** 43904fd306cSNickeau * @var WikiPath $iconPath - to say to the linter that this is a wiki path 44004fd306cSNickeau */ 44104fd306cSNickeau $iconPath = FileSystems::getFirstExistingPath($iconPaths); 44204fd306cSNickeau } catch (ExceptionNotFound $e) { 44304fd306cSNickeau LogUtility::internalError("The internal apple icon ($internalIcon) should be at minimal found", self::CANONICAL); 44404fd306cSNickeau return ""; 44504fd306cSNickeau } 44604fd306cSNickeau try { 44704fd306cSNickeau $fetcherLocalPath = FetcherRaster::createImageRasterFetchFromPath($iconPath); 44804fd306cSNickeau $sizesValue = "{$fetcherLocalPath->getIntrinsicWidth()}x{$fetcherLocalPath->getIntrinsicHeight()}"; 44904fd306cSNickeau 45004fd306cSNickeau return TagAttributes::createEmpty() 45104fd306cSNickeau ->addOutputAttributeValue("rel", self::APPLE_TOUCH_ICON_REL_VALUE) 45204fd306cSNickeau ->addOutputAttributeValue("sizes", $sizesValue) 45304fd306cSNickeau ->addOutputAttributeValue("type", Mime::PNG) 45404fd306cSNickeau ->addOutputAttributeValue("href", $fetcherLocalPath->getFetchUrl()->toAbsoluteUrl()->toString()) 45504fd306cSNickeau ->toHtmlEmptyTag("link"); 45604fd306cSNickeau } catch (\Exception $e) { 45704fd306cSNickeau LogUtility::internalError("The file ($iconPath) should be found and the local name should be good. Error: {$e->getMessage()}"); 45804fd306cSNickeau return ""; 45904fd306cSNickeau } 46004fd306cSNickeau } 46104fd306cSNickeau 46204fd306cSNickeau public 46304fd306cSNickeau function getModel(): array 46404fd306cSNickeau { 46504fd306cSNickeau 46604fd306cSNickeau $executionConfig = ExecutionContext::getActualOrCreateFromEnv()->getConfig(); 46704fd306cSNickeau 46804fd306cSNickeau /** 46904fd306cSNickeau * Mandatory HTML attributes 47004fd306cSNickeau */ 47104fd306cSNickeau $model = 47204fd306cSNickeau [ 47304fd306cSNickeau PageTitle::PROPERTY_NAME => $this->getRequestedTitleOrDefault(), 47404fd306cSNickeau Lang::PROPERTY_NAME => $this->getRequestedLangOrDefault()->getValueOrDefault(), 47504fd306cSNickeau // The direction is not yet calculated from the page, we let the browser determine it from the lang 47604fd306cSNickeau // dokuwiki has a direction config also ... 47704fd306cSNickeau // "dir" => $this->getRequestedLangOrDefault()->getDirection() 47804fd306cSNickeau ]; 47904fd306cSNickeau 48004fd306cSNickeau if (isset($this->model)) { 48104fd306cSNickeau return array_merge($model, $this->model); 48204fd306cSNickeau } 48304fd306cSNickeau 48404fd306cSNickeau /** 48504fd306cSNickeau * The width of the layout 48604fd306cSNickeau */ 48704fd306cSNickeau $container = $executionConfig->getValue(ContainerTag::DEFAULT_LAYOUT_CONTAINER_CONF, ContainerTag::DEFAULT_LAYOUT_CONTAINER_DEFAULT_VALUE); 48804fd306cSNickeau $containerClass = ContainerTag::getClassName($container); 48904fd306cSNickeau $model["layout-container-class"] = $containerClass; 49004fd306cSNickeau 49104fd306cSNickeau 49204fd306cSNickeau /** 49304fd306cSNickeau * The rem 49404fd306cSNickeau */ 49504fd306cSNickeau try { 49604fd306cSNickeau $model["rem-size"] = $executionConfig->getRemFontSize(); 49704fd306cSNickeau } catch (ExceptionNotFound $e) { 49804fd306cSNickeau // ok none 49904fd306cSNickeau } 50004fd306cSNickeau 50104fd306cSNickeau 50204fd306cSNickeau /** 50304fd306cSNickeau * Body class 50404fd306cSNickeau * {@link tpl_classes} will add the dokuwiki class. 50504fd306cSNickeau * See https://www.dokuwiki.org/devel:templates#dokuwiki_class 50604fd306cSNickeau * dokuwiki__top ID is needed for the "Back to top" utility 50704fd306cSNickeau * used also by some plugins 50804fd306cSNickeau * dokwuiki as class is also needed as it's used by the linkwizard 50904fd306cSNickeau * to locate where to add the node (ie .appendTo('.dokuwiki:first')) 51004fd306cSNickeau */ 51104fd306cSNickeau $bodyDokuwikiClass = tpl_classes(); 51204fd306cSNickeau try { 51304fd306cSNickeau $bodyTemplateIdentifierClass = StyleAttribute::addComboStrapSuffix("{$this->getTheme()}-{$this->getTemplateName()}"); 51404fd306cSNickeau } catch (\Exception $e) { 51504fd306cSNickeau $bodyTemplateIdentifierClass = StyleAttribute::addComboStrapSuffix("template-string"); 51604fd306cSNickeau } 51704fd306cSNickeau // position relative is for the toast and messages that are in the corner 51804fd306cSNickeau $model['body-classes'] = "$bodyDokuwikiClass position-relative $bodyTemplateIdentifierClass"; 51904fd306cSNickeau 52004fd306cSNickeau /** 52104fd306cSNickeau * Data coupled to a page 52204fd306cSNickeau */ 52304fd306cSNickeau try { 52404fd306cSNickeau 52504fd306cSNickeau $contextPath = $this->getRequestedContextPath(); 52604fd306cSNickeau $markupPath = MarkupPath::createPageFromPathObject($contextPath); 52704fd306cSNickeau /** 52804fd306cSNickeau * Meta 52904fd306cSNickeau */ 53004fd306cSNickeau $metadata = $markupPath->getMetadataForRendering(); 53104fd306cSNickeau $model = array_merge($metadata, $model); 53204fd306cSNickeau 53304fd306cSNickeau 53404fd306cSNickeau /** 53504fd306cSNickeau * Railbar 53604fd306cSNickeau * You can define the layout type by page 53704fd306cSNickeau * This is not a handelbars helper because it needs some css snippet. 53804fd306cSNickeau */ 53904fd306cSNickeau $railBarLayout = $this->getRailbarLayout(); 54004fd306cSNickeau try { 54104fd306cSNickeau $model["railbar-html"] = FetcherRailBar::createRailBar() 54204fd306cSNickeau ->setRequestedLayout($railBarLayout) 54304fd306cSNickeau ->setRequestedPath($contextPath) 54404fd306cSNickeau ->getFetchString(); 54504fd306cSNickeau } catch (ExceptionBadArgument $e) { 54604fd306cSNickeau LogUtility::error("Error while creating the railbar layout"); 54704fd306cSNickeau } 54804fd306cSNickeau 54904fd306cSNickeau /** 55004fd306cSNickeau * Css Variables Colors 55104fd306cSNickeau * Added for now in `head-partial.hbs` 55204fd306cSNickeau */ 55304fd306cSNickeau try { 55404fd306cSNickeau $primaryColor = $executionConfig->getPrimaryColor(); 55504fd306cSNickeau $model[BrandingColors::PRIMARY_COLOR_TEMPLATE_ATTRIBUTE] = $primaryColor->toCssValue(); 55604fd306cSNickeau $model[BrandingColors::PRIMARY_COLOR_TEXT_ATTRIBUTE] = ColorSystem::toTextColor($primaryColor); 55704fd306cSNickeau $model[BrandingColors::PRIMARY_COLOR_TEXT_HOVER_ATTRIBUTE] = ColorSystem::toTextHoverColor($primaryColor); 55804fd306cSNickeau } catch (ExceptionNotFound $e) { 55904fd306cSNickeau // not found 56004fd306cSNickeau $model[BrandingColors::PRIMARY_COLOR_TEMPLATE_ATTRIBUTE] = null; 56104fd306cSNickeau } 56204fd306cSNickeau try { 56304fd306cSNickeau $secondaryColor = $executionConfig->getSecondaryColor(); 56404fd306cSNickeau $model[BrandingColors::SECONDARY_COLOR_TEMPLATE_ATTRIBUTE] = $secondaryColor->toCssValue(); 56504fd306cSNickeau } catch (ExceptionNotFound $e) { 56604fd306cSNickeau // not found 56704fd306cSNickeau } 56804fd306cSNickeau 56904fd306cSNickeau 57004fd306cSNickeau /** 57104fd306cSNickeau * Main 57204fd306cSNickeau */ 57304fd306cSNickeau if (isset($this->mainContent)) { 57404fd306cSNickeau $model["main-content-html"] = $this->mainContent; 57504fd306cSNickeau } else { 57604fd306cSNickeau try { 57704fd306cSNickeau if (!$markupPath->isSlot()) { 57804fd306cSNickeau $requestedContextPathForMain = $this->getRequestedContextPath(); 57904fd306cSNickeau } else { 58004fd306cSNickeau try { 58104fd306cSNickeau $markupContextPath = SlotSystem::getContextPath(); 58204fd306cSNickeau SlotSystem::sendContextPathMessage($markupContextPath); 58304fd306cSNickeau $requestedContextPathForMain = $markupContextPath->toWikiPath(); 58404fd306cSNickeau } catch (ExceptionNotFound|ExceptionCast $e) { 58504fd306cSNickeau $requestedContextPathForMain = $this->getRequestedContextPath(); 58604fd306cSNickeau } 58704fd306cSNickeau } 58804fd306cSNickeau $model["main-content-html"] = FetcherMarkup::confRoot() 58904fd306cSNickeau ->setRequestedMimeToXhtml() 59004fd306cSNickeau ->setRequestedContextPath($requestedContextPathForMain) 59104fd306cSNickeau ->setRequestedExecutingPath($this->getRequestedContextPath()) 59204fd306cSNickeau ->build() 59304fd306cSNickeau ->getFetchString(); 59404fd306cSNickeau } catch (ExceptionCompile|ExceptionNotExists|ExceptionNotExists $e) { 59504fd306cSNickeau LogUtility::error("Error while rendering the page content.", self::CANONICAL, $e); 59604fd306cSNickeau $model["main-content-html"] = "An error has occured. " . $e->getMessage(); 59704fd306cSNickeau } 59804fd306cSNickeau } 59904fd306cSNickeau 60004fd306cSNickeau /** 60104fd306cSNickeau * Toc (after main execution please) 60204fd306cSNickeau */ 60304fd306cSNickeau $model['toc-class'] = Toc::getClass(); 60404fd306cSNickeau $model['toc-html'] = $this->getTocOrDefault()->toXhtml(); 60504fd306cSNickeau 60604fd306cSNickeau /** 60704fd306cSNickeau * Slots 60804fd306cSNickeau */ 60904fd306cSNickeau foreach ($this->getSlots() as $slot) { 61004fd306cSNickeau 61104fd306cSNickeau $elementId = $slot->getElementId(); 61204fd306cSNickeau try { 61304fd306cSNickeau $model["$elementId-html"] = $slot->getMarkupFetcher()->getFetchString(); 61404fd306cSNickeau } catch (ExceptionNotFound|ExceptionNotExists $e) { 61504fd306cSNickeau // no slot found 61604fd306cSNickeau } catch (ExceptionCompile $e) { 61704fd306cSNickeau LogUtility::error("Error while rendering the slot $elementId for the template ($this)", self::CANONICAL, $e); 61804fd306cSNickeau $model["$elementId-html"] = LogUtility::wrapInRedForHtml("Error: " . $e->getMessage()); 61904fd306cSNickeau } 62004fd306cSNickeau } 62104fd306cSNickeau 62204fd306cSNickeau /** 62304fd306cSNickeau * Found in {@link tpl_content()} 62404fd306cSNickeau * Used to add html such as {@link \action_plugin_combo_routermessage} 62504fd306cSNickeau * Not sure if this is the right place to add it. 62604fd306cSNickeau */ 62704fd306cSNickeau ob_start(); 62804fd306cSNickeau global $ACT; 62904fd306cSNickeau \dokuwiki\Extension\Event::createAndTrigger('TPL_ACT_RENDER', $ACT); 63004fd306cSNickeau $tplActRenderOutput = ob_get_clean(); 63104fd306cSNickeau if (!empty($tplActRenderOutput)) { 63204fd306cSNickeau $model["main-content-afterbegin-html"] = $tplActRenderOutput; 63304fd306cSNickeau $this->hadMessages = true; 63404fd306cSNickeau } 63504fd306cSNickeau 63604fd306cSNickeau } catch (ExceptionNotFound $e) { 63704fd306cSNickeau // no context path 63804fd306cSNickeau if (isset($this->mainContent)) { 63904fd306cSNickeau $model["main-content-html"] = $this->mainContent; 64004fd306cSNickeau } 64104fd306cSNickeau } 64204fd306cSNickeau 64304fd306cSNickeau 64404fd306cSNickeau /** 64504fd306cSNickeau * Head Html 64604fd306cSNickeau * Snippet, Css and Js from the layout if any 64704fd306cSNickeau * 64804fd306cSNickeau * Note that head tag may be added during rendering and must be then called after rendering and toc 64904fd306cSNickeau * (ie at last then) 65004fd306cSNickeau */ 65104fd306cSNickeau $model['head-html'] = $this->getHeadHtml(); 65204fd306cSNickeau 65304fd306cSNickeau /** 65404fd306cSNickeau * Preloaded Css 65504fd306cSNickeau * (It must come after the head processing as this is where the preloaded script are defined) 65604fd306cSNickeau * (Not really useful but legacy) 65704fd306cSNickeau * We add it just before the end of the body tag 65804fd306cSNickeau */ 65904fd306cSNickeau try { 66004fd306cSNickeau $model['preloaded-stylesheet-html'] = $this->getHtmlForPreloadedStyleSheets(); 66104fd306cSNickeau } catch (ExceptionNotFound $e) { 66204fd306cSNickeau // no preloaded stylesheet resources 66304fd306cSNickeau } 66404fd306cSNickeau 66504fd306cSNickeau /** 66604fd306cSNickeau * Powered by 66704fd306cSNickeau */ 66804fd306cSNickeau $model['powered-by'] = self::getPoweredBy(); 66904fd306cSNickeau 67004fd306cSNickeau /** 67104fd306cSNickeau * Messages 67204fd306cSNickeau * (Should come just before the page creation 67304fd306cSNickeau * due to the $MSG_shown mechanism in {@link html_msgarea()} 67404fd306cSNickeau * We may also get messages in the head 67504fd306cSNickeau */ 67604fd306cSNickeau try { 67704fd306cSNickeau $model['messages-html'] = $this->getMessages(); 67804fd306cSNickeau /** 67904fd306cSNickeau * Because they must be problem and message with the {@link self::getHeadHtml()} 68004fd306cSNickeau * We process the messages at the end 68104fd306cSNickeau * It means that the needed script needs to be added manually 68204fd306cSNickeau */ 68304fd306cSNickeau $model['head-html'] .= Snippet::getOrCreateFromComponentId("toast", Snippet::EXTENSION_JS)->toXhtml(); 68404fd306cSNickeau } catch (ExceptionNotFound $e) { 68504fd306cSNickeau // no messages 68604fd306cSNickeau } catch (ExceptionBadState $e) { 68704fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The toast snippet should have been found", $e); 68804fd306cSNickeau } 68904fd306cSNickeau 69004fd306cSNickeau /** 69104fd306cSNickeau * Task runner needs the id 69204fd306cSNickeau */ 69304fd306cSNickeau if ($this->requestedEnableTaskRunner && isset($this->requestedContextPath)) { 69404fd306cSNickeau $model['task-runner-html'] = $this->getTaskRunnerImg(); 69504fd306cSNickeau } 69604fd306cSNickeau 69704fd306cSNickeau return $model; 69804fd306cSNickeau } 69904fd306cSNickeau 70004fd306cSNickeau 70104fd306cSNickeau private 70204fd306cSNickeau function getRequestedTitleOrDefault(): string 70304fd306cSNickeau { 70404fd306cSNickeau 70504fd306cSNickeau if (isset($this->requestedTitle)) { 70604fd306cSNickeau return $this->requestedTitle; 70704fd306cSNickeau } 70804fd306cSNickeau 70904fd306cSNickeau try { 71004fd306cSNickeau $path = $this->getRequestedContextPath(); 71104fd306cSNickeau $markupPath = MarkupPath::createPageFromPathObject($path); 71204fd306cSNickeau return PageTitle::createForMarkup($markupPath)->getValueOrDefault(); 71304fd306cSNickeau } catch (ExceptionNotFound $e) { 71404fd306cSNickeau // 71504fd306cSNickeau } 71604fd306cSNickeau throw new ExceptionBadSyntaxRuntime("A title is mandatory"); 71704fd306cSNickeau 71804fd306cSNickeau 71904fd306cSNickeau } 72004fd306cSNickeau 72104fd306cSNickeau 72204fd306cSNickeau /** 72304fd306cSNickeau * @throws ExceptionNotFound 72404fd306cSNickeau */ 72504fd306cSNickeau private 72604fd306cSNickeau function getTocOrDefault(): Toc 72704fd306cSNickeau { 72804fd306cSNickeau 72904fd306cSNickeau if (isset($this->toc)) { 73004fd306cSNickeau /** 73104fd306cSNickeau * The {@link FetcherPageBundler} 73204fd306cSNickeau * bundle pages can create a toc for multiples pages 73304fd306cSNickeau */ 73404fd306cSNickeau return $this->toc; 73504fd306cSNickeau } 73604fd306cSNickeau 73704fd306cSNickeau $wikiPath = $this->getRequestedContextPath(); 73804fd306cSNickeau if (FileSystems::isDirectory($wikiPath)) { 73904fd306cSNickeau LogUtility::error("We have a found an inconsistency. The context path is a directory and does have therefore no toc but the template ($this) has a toc."); 74004fd306cSNickeau } 74104fd306cSNickeau $markup = MarkupPath::createPageFromPathObject($wikiPath); 74204fd306cSNickeau return Toc::createForPage($markup); 74304fd306cSNickeau 74404fd306cSNickeau } 74504fd306cSNickeau 74604fd306cSNickeau public 74704fd306cSNickeau function setMainContent(string $mainContent): TemplateForWebPage 74804fd306cSNickeau { 74904fd306cSNickeau $this->mainContent = $mainContent; 75004fd306cSNickeau return $this; 75104fd306cSNickeau } 75204fd306cSNickeau 75304fd306cSNickeau 75404fd306cSNickeau /** 75504fd306cSNickeau * @throws ExceptionBadSyntax 75604fd306cSNickeau */ 75704fd306cSNickeau public 75804fd306cSNickeau function renderAsDom(): XmlDocument 75904fd306cSNickeau { 76004fd306cSNickeau return XmlDocument::createHtmlDocFromMarkup($this->render()); 76104fd306cSNickeau } 76204fd306cSNickeau 76304fd306cSNickeau /** 76404fd306cSNickeau * Add the preloaded CSS resources 76504fd306cSNickeau * at the end 76604fd306cSNickeau * @throws ExceptionNotFound 76704fd306cSNickeau */ 76804fd306cSNickeau private 76904fd306cSNickeau function getHtmlForPreloadedStyleSheets(): string 77004fd306cSNickeau { 77104fd306cSNickeau 77204fd306cSNickeau // For the preload if any 77304fd306cSNickeau try { 77404fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 77504fd306cSNickeau $preloadedCss = $executionContext->getRuntimeObject(self::PRELOAD_TAG); 77604fd306cSNickeau } catch (ExceptionNotFound $e) { 77704fd306cSNickeau throw new ExceptionNotFound("No preloaded resources found"); 77804fd306cSNickeau } 77904fd306cSNickeau 78004fd306cSNickeau // 78104fd306cSNickeau // Note: Adding this css in an animationFrame 78204fd306cSNickeau // such as https://github.com/jakearchibald/svgomg/blob/master/src/index.html#L183 78304fd306cSNickeau // would be difficult to test 78404fd306cSNickeau 78504fd306cSNickeau $class = StyleAttribute::addComboStrapSuffix(self::PRELOAD_TAG); 78604fd306cSNickeau $preloadHtml = "<div class=\"$class\">"; 78704fd306cSNickeau foreach ($preloadedCss as $link) { 78804fd306cSNickeau $htmlLink = '<link rel="stylesheet" href="' . $link['href'] . '" '; 78904fd306cSNickeau if ($link['crossorigin'] != "") { 79004fd306cSNickeau $htmlLink .= ' crossorigin="' . $link['crossorigin'] . '" '; 79104fd306cSNickeau } 79204fd306cSNickeau if (!empty($link['class'])) { 79304fd306cSNickeau $htmlLink .= ' class="' . $link['class'] . '" '; 79404fd306cSNickeau } 79504fd306cSNickeau // No integrity here 79604fd306cSNickeau $htmlLink .= '>'; 79704fd306cSNickeau $preloadHtml .= $htmlLink; 79804fd306cSNickeau } 79904fd306cSNickeau $preloadHtml .= "</div>"; 80004fd306cSNickeau return $preloadHtml; 80104fd306cSNickeau 80204fd306cSNickeau } 80304fd306cSNickeau 80404fd306cSNickeau /** 80504fd306cSNickeau * Variation of {@link html_msgarea()} 80604fd306cSNickeau * @throws ExceptionNotFound 80704fd306cSNickeau */ 80804fd306cSNickeau public 80904fd306cSNickeau function getMessages(): string 81004fd306cSNickeau { 81104fd306cSNickeau 81204fd306cSNickeau global $MSG; 81304fd306cSNickeau 81404fd306cSNickeau if (!isset($MSG)) { 81504fd306cSNickeau throw new ExceptionNotFound("No messages"); 81604fd306cSNickeau } 81704fd306cSNickeau 81804fd306cSNickeau // deduplicate and auth 81904fd306cSNickeau $uniqueMessages = []; 82004fd306cSNickeau foreach ($MSG as $msg) { 82104fd306cSNickeau if (!info_msg_allowed($msg)) { 82204fd306cSNickeau continue; 82304fd306cSNickeau } 82404fd306cSNickeau $hash = md5($msg['msg']); 82504fd306cSNickeau $uniqueMessages[$hash] = $msg; 82604fd306cSNickeau } 82704fd306cSNickeau 82804fd306cSNickeau $messagesByLevel = []; 82904fd306cSNickeau foreach ($uniqueMessages as $message) { 83004fd306cSNickeau $level = $message['lvl']; 83104fd306cSNickeau $messagesByLevel[$level][] = $message; 83204fd306cSNickeau } 83304fd306cSNickeau 83404fd306cSNickeau $toasts = ""; 83504fd306cSNickeau foreach ($messagesByLevel as $level => $messagesForLevel) { 83604fd306cSNickeau $level = ucfirst($level); 83704fd306cSNickeau switch ($level) { 83804fd306cSNickeau case "Error": 83904fd306cSNickeau $class = "text-danger"; 84004fd306cSNickeau $levelName = "Error"; 84104fd306cSNickeau break; 84204fd306cSNickeau case "Notify": 84304fd306cSNickeau $class = "text-warning"; 84404fd306cSNickeau $levelName = "Warning"; 84504fd306cSNickeau break; 84604fd306cSNickeau default: 84704fd306cSNickeau $levelName = $level; 84804fd306cSNickeau $class = "text-primary"; 84904fd306cSNickeau break; 85004fd306cSNickeau } 85104fd306cSNickeau $autoHide = "false"; // auto-hidding is really bad ui 85204fd306cSNickeau $toastMessage = ""; 85304fd306cSNickeau foreach ($messagesForLevel as $messageForLevel) { 85404fd306cSNickeau $toastMessage .= "<p>{$messageForLevel['msg']}</p>"; 85504fd306cSNickeau } 85604fd306cSNickeau 85704fd306cSNickeau 85804fd306cSNickeau $toasts .= <<<EOF 85904fd306cSNickeau<div role="alert" aria-live="assertive" aria-atomic="true" class="toast fade" data-bs-autohide="$autoHide"> 86004fd306cSNickeau <div class="toast-header"> 86104fd306cSNickeau <strong class="me-auto $class">{$levelName}</strong> 86204fd306cSNickeau <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> 86304fd306cSNickeau </div> 86404fd306cSNickeau <div class="toast-body"> 86504fd306cSNickeau $toastMessage 86604fd306cSNickeau </div> 86704fd306cSNickeau</div> 86804fd306cSNickeauEOF; 86904fd306cSNickeau } 87004fd306cSNickeau 87104fd306cSNickeau unset($GLOBALS['MSG']); 87204fd306cSNickeau 87304fd306cSNickeau if ($toasts === "") { 87404fd306cSNickeau throw new ExceptionNotFound("No messages"); 87504fd306cSNickeau } 87604fd306cSNickeau 87704fd306cSNickeau $this->hadMessages = true; 87804fd306cSNickeau 87904fd306cSNickeau // position fixed to not participate into the grid 88004fd306cSNickeau return <<<EOF 88104fd306cSNickeau<div class="toast-container position-fixed mb-3 me-3 bottom-0 end-0" id="toastPlacement" style="z-index:1060"> 88204fd306cSNickeau$toasts 88304fd306cSNickeau</div> 88404fd306cSNickeauEOF; 88504fd306cSNickeau 88604fd306cSNickeau } 88704fd306cSNickeau 88804fd306cSNickeau private 88904fd306cSNickeau function canBeCached(): bool 89004fd306cSNickeau { 89104fd306cSNickeau // no if message 89204fd306cSNickeau return true; 89304fd306cSNickeau } 89404fd306cSNickeau 89504fd306cSNickeau /** 89604fd306cSNickeau * Adapted from {@link tpl_indexerWebBug()} 89704fd306cSNickeau * @return string 89804fd306cSNickeau */ 89904fd306cSNickeau private 90004fd306cSNickeau function getTaskRunnerImg(): string 90104fd306cSNickeau { 90204fd306cSNickeau 90304fd306cSNickeau try { 90404fd306cSNickeau $htmlUrl = UrlEndpoint::createTaskRunnerUrl() 90504fd306cSNickeau ->addQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->getRequestedContextPath()->getWikiId()) 90604fd306cSNickeau ->addQueryParameter(time()) 90704fd306cSNickeau ->toString(); 90804fd306cSNickeau } catch (ExceptionNotFound $e) { 90904fd306cSNickeau throw new ExceptionRuntimeInternal("A request path is mandatory when adding a task runner. Disable it if you don't want one in the layout ($this)."); 91004fd306cSNickeau } 91104fd306cSNickeau 91204fd306cSNickeau // no more 1x1 px image because of ad blockers 91304fd306cSNickeau return TagAttributes::createEmpty() 91404fd306cSNickeau ->addOutputAttributeValue("id", TemplateForWebPage::TASK_RUNNER_ID) 91504fd306cSNickeau ->addClassName("d-none") 91604fd306cSNickeau ->addOutputAttributeValue('width', 2) 91704fd306cSNickeau ->addOutputAttributeValue('height', 1) 91804fd306cSNickeau ->addOutputAttributeValue('alt', 'Task Runner') 91904fd306cSNickeau ->addOutputAttributeValue('src', $htmlUrl) 92004fd306cSNickeau ->toHtmlEmptyTag("img"); 92104fd306cSNickeau } 92204fd306cSNickeau 92304fd306cSNickeau private 92404fd306cSNickeau function getRequestedLangOrDefault(): Lang 92504fd306cSNickeau { 92604fd306cSNickeau try { 92704fd306cSNickeau return $this->getRequestedLang(); 92804fd306cSNickeau } catch (ExceptionNotFound $e) { 92904fd306cSNickeau return Lang::createFromValue("en"); 93004fd306cSNickeau } 93104fd306cSNickeau } 93204fd306cSNickeau 93304fd306cSNickeau private 93404fd306cSNickeau function getTheme(): string 93504fd306cSNickeau { 93604fd306cSNickeau return $this->requestedTheme ?? ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getTheme(); 93704fd306cSNickeau } 93804fd306cSNickeau 93904fd306cSNickeau private 94004fd306cSNickeau function getHeadHtml(): string 94104fd306cSNickeau { 94204fd306cSNickeau $snippetManager = PluginUtility::getSnippetManager(); 94304fd306cSNickeau 94404fd306cSNickeau if (!$this->isTemplateStringExecutionMode()) { 94504fd306cSNickeau 94604fd306cSNickeau /** 94704fd306cSNickeau * Add the layout js and css first 94804fd306cSNickeau */ 94904fd306cSNickeau 95004fd306cSNickeau try { 95104fd306cSNickeau $cssPath = $this->getCssPath(); 95204fd306cSNickeau $content = FileSystems::getContent($cssPath); 95304fd306cSNickeau $snippetManager->attachCssInternalStylesheet(self::CANONICAL, $content); 95404fd306cSNickeau } catch (ExceptionNotFound $e) { 95504fd306cSNickeau // no css found, not a problem 95604fd306cSNickeau } 95704fd306cSNickeau try { 95804fd306cSNickeau $jsPath = $this->getJsPath(); 95904fd306cSNickeau $snippetManager->attachInternalJavascriptFromPathForRequest(self::CANONICAL, $jsPath); 96004fd306cSNickeau } catch (ExceptionNotFound $e) { 96104fd306cSNickeau // not found 96204fd306cSNickeau } 96304fd306cSNickeau 96404fd306cSNickeau 96504fd306cSNickeau } 96604fd306cSNickeau 96704fd306cSNickeau /** 96804fd306cSNickeau * Dokuwiki Smiley does not have any height 96904fd306cSNickeau */ 97004fd306cSNickeau $snippetManager->attachCssInternalStyleSheet("dokuwiki-smiley"); 97104fd306cSNickeau 97204fd306cSNickeau /** 97304fd306cSNickeau * Iframe 97404fd306cSNickeau */ 97504fd306cSNickeau if ($this->isIframe) { 97604fd306cSNickeau global $EVENT_HANDLER; 97704fd306cSNickeau $EVENT_HANDLER->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'onlyIframeHeadTags'); 97804fd306cSNickeau } 97904fd306cSNickeau /** 98004fd306cSNickeau * Start the meta headers 98104fd306cSNickeau */ 98204fd306cSNickeau ob_start(); 98304fd306cSNickeau try { 98404fd306cSNickeau tpl_metaheaders(); 98504fd306cSNickeau $headIcon = $this->getPageIconHeadLinkHtml(); 98604fd306cSNickeau return $headIcon . ob_get_contents(); 98704fd306cSNickeau } finally { 98804fd306cSNickeau ob_end_clean(); 98904fd306cSNickeau } 99004fd306cSNickeau 99104fd306cSNickeau } 99204fd306cSNickeau 99304fd306cSNickeau 99404fd306cSNickeau public 99504fd306cSNickeau function setRequestedTemplateName(string $templateName): TemplateForWebPage 99604fd306cSNickeau { 99704fd306cSNickeau $this->templateName = $templateName; 99804fd306cSNickeau return $this; 99904fd306cSNickeau } 100004fd306cSNickeau 100104fd306cSNickeau /** 100204fd306cSNickeau * Add or not the task runner / web bug call 100304fd306cSNickeau * @param bool $b 100404fd306cSNickeau * @return TemplateForWebPage 100504fd306cSNickeau */ 100604fd306cSNickeau public 100704fd306cSNickeau function setRequestedEnableTaskRunner(bool $b): TemplateForWebPage 100804fd306cSNickeau { 100904fd306cSNickeau $this->requestedEnableTaskRunner = $b; 101004fd306cSNickeau return $this; 101104fd306cSNickeau } 101204fd306cSNickeau 101304fd306cSNickeau 101404fd306cSNickeau /** 101504fd306cSNickeau * @param Lang $requestedLang 101604fd306cSNickeau * @return TemplateForWebPage 101704fd306cSNickeau */ 101804fd306cSNickeau public 101904fd306cSNickeau function setRequestedLang(Lang $requestedLang): TemplateForWebPage 102004fd306cSNickeau { 102104fd306cSNickeau $this->requestedLang = $requestedLang; 102204fd306cSNickeau return $this; 102304fd306cSNickeau } 102404fd306cSNickeau 102504fd306cSNickeau /** 102604fd306cSNickeau * @param string $requestedTitle 102704fd306cSNickeau * @return TemplateForWebPage 102804fd306cSNickeau */ 102904fd306cSNickeau public 103004fd306cSNickeau function setRequestedTitle(string $requestedTitle): TemplateForWebPage 103104fd306cSNickeau { 103204fd306cSNickeau $this->requestedTitle = $requestedTitle; 103304fd306cSNickeau return $this; 103404fd306cSNickeau } 103504fd306cSNickeau 103604fd306cSNickeau /** 103704fd306cSNickeau * Delete the social head tags 103804fd306cSNickeau * (ie the page should not be indexed) 103904fd306cSNickeau * This is used for iframe content for instance 104004fd306cSNickeau * @param bool $isSocial 104104fd306cSNickeau * @return TemplateForWebPage 104204fd306cSNickeau */ 104304fd306cSNickeau public 104404fd306cSNickeau function setIsSocial(bool $isSocial): TemplateForWebPage 104504fd306cSNickeau { 104604fd306cSNickeau $this->isSocial = $isSocial; 104704fd306cSNickeau return $this; 104804fd306cSNickeau } 104904fd306cSNickeau 105004fd306cSNickeau public 105104fd306cSNickeau function setRequestedContextPath(WikiPath $contextPath): TemplateForWebPage 105204fd306cSNickeau { 105304fd306cSNickeau $this->requestedContextPath = $contextPath; 105404fd306cSNickeau return $this; 105504fd306cSNickeau } 105604fd306cSNickeau 105704fd306cSNickeau public 105804fd306cSNickeau function setToc(Toc $toc): TemplateForWebPage 105904fd306cSNickeau { 106004fd306cSNickeau $this->toc = $toc; 106104fd306cSNickeau return $this; 106204fd306cSNickeau } 106304fd306cSNickeau 106404fd306cSNickeau /** 106504fd306cSNickeau * There is two mode of execution, via: 106604fd306cSNickeau * * a file template (theme) 106704fd306cSNickeau * * or a string template (string) 106804fd306cSNickeau * 106904fd306cSNickeau * @return bool - true if this a string template executions 107004fd306cSNickeau */ 107104fd306cSNickeau private 107204fd306cSNickeau function isTemplateStringExecutionMode(): bool 107304fd306cSNickeau { 107404fd306cSNickeau return isset($this->templateString); 107504fd306cSNickeau } 107604fd306cSNickeau 107704fd306cSNickeau private 107804fd306cSNickeau function getEngine(): TemplateEngine 107904fd306cSNickeau { 108004fd306cSNickeau if ($this->isTemplateStringExecutionMode()) { 108104fd306cSNickeau return TemplateEngine::createForString(); 108204fd306cSNickeau 108304fd306cSNickeau } else { 108404fd306cSNickeau $theme = $this->getTheme(); 108504fd306cSNickeau return TemplateEngine::createForTheme($theme); 108604fd306cSNickeau } 108704fd306cSNickeau } 108804fd306cSNickeau 108904fd306cSNickeau private 109004fd306cSNickeau function getDefinition(): array 109104fd306cSNickeau { 109204fd306cSNickeau try { 109304fd306cSNickeau if (isset($this->templateDefinition)) { 109404fd306cSNickeau return $this->templateDefinition; 109504fd306cSNickeau } 109604fd306cSNickeau $file = $this->getEngine()->searchTemplateByName("{$this->getTemplateName()}.yml"); 109704fd306cSNickeau if (!FileSystems::exists($file)) { 109804fd306cSNickeau return []; 109904fd306cSNickeau } 110004fd306cSNickeau $this->templateDefinition = Yaml::parseFile($file->toAbsoluteId()); 110104fd306cSNickeau return $this->templateDefinition; 110204fd306cSNickeau } catch (ExceptionNotFound $e) { 110304fd306cSNickeau // no template directory, not a theme run 110404fd306cSNickeau return []; 110504fd306cSNickeau } 110604fd306cSNickeau } 110704fd306cSNickeau 110804fd306cSNickeau private 110904fd306cSNickeau function getRailbarLayout(): string 111004fd306cSNickeau { 111104fd306cSNickeau $definition = $this->getDefinition(); 111204fd306cSNickeau if (isset($definition['railbar']['layout'])) { 111304fd306cSNickeau return $definition['railbar']['layout']; 111404fd306cSNickeau } 111504fd306cSNickeau return FetcherRailBar::BOTH_LAYOUT; 111604fd306cSNickeau } 111704fd306cSNickeau 111804fd306cSNickeau /** 111904fd306cSNickeau * Keep the only iframe head tag needed 112004fd306cSNickeau * @param $event 112104fd306cSNickeau * @return void 112204fd306cSNickeau */ 112304fd306cSNickeau public 112404fd306cSNickeau function onlyIframeHeadTags(&$event) 112504fd306cSNickeau { 112604fd306cSNickeau 112704fd306cSNickeau $data = &$event->data; 112804fd306cSNickeau foreach ($data as $tag => &$heads) { 112904fd306cSNickeau switch ($tag) { 113004fd306cSNickeau case "link": 113104fd306cSNickeau $deletedRel = ["manifest", "search", "start", "alternate", "canonical"]; 113204fd306cSNickeau foreach ($heads as $id => $headAttributes) { 113304fd306cSNickeau if (isset($headAttributes['rel'])) { 113404fd306cSNickeau $rel = $headAttributes['rel']; 113504fd306cSNickeau if (in_array($rel, $deletedRel)) { 113604fd306cSNickeau unset($heads[$id]); 113704fd306cSNickeau } 113804fd306cSNickeau if ($rel === "stylesheet") { 113904fd306cSNickeau $href = $headAttributes['href']; 114004fd306cSNickeau if (strpos($href, "lib/exe/css.php") !== false) { 114104fd306cSNickeau unset($heads[$id]); 114204fd306cSNickeau } 114304fd306cSNickeau } 114404fd306cSNickeau } 114504fd306cSNickeau } 114604fd306cSNickeau break; 114704fd306cSNickeau case "meta": 114804fd306cSNickeau $deletedMeta = ["og:url", "og:description", "description", "robots"]; 114904fd306cSNickeau foreach ($heads as $id => $headAttributes) { 115004fd306cSNickeau if (isset($headAttributes['name']) || isset($headAttributes['property'])) { 115104fd306cSNickeau $rel = $headAttributes['name']; 115204fd306cSNickeau if ($rel === null) { 115304fd306cSNickeau $rel = $headAttributes['property']; 115404fd306cSNickeau } 115504fd306cSNickeau if (in_array($rel, $deletedMeta)) { 115604fd306cSNickeau unset($heads[$id]); 115704fd306cSNickeau } 115804fd306cSNickeau } 115904fd306cSNickeau } 116004fd306cSNickeau break; 116104fd306cSNickeau case "script": 116204fd306cSNickeau foreach ($heads as $id => $headAttributes) { 116304fd306cSNickeau if (isset($headAttributes['src'])) { 116404fd306cSNickeau $src = $headAttributes['src']; 116504fd306cSNickeau if (strpos($src, "lib/exe/js.php") !== false) { 116604fd306cSNickeau unset($heads[$id]); 116704fd306cSNickeau } 116804fd306cSNickeau if (strpos($src, "lib/exe/jquery.php") !== false) { 116904fd306cSNickeau unset($heads[$id]); 117004fd306cSNickeau } 117104fd306cSNickeau } 117204fd306cSNickeau } 117304fd306cSNickeau break; 117404fd306cSNickeau } 117504fd306cSNickeau } 117604fd306cSNickeau } 117704fd306cSNickeau 117804fd306cSNickeau} 1179