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