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