104fd306cSNickeau<?php 204fd306cSNickeau/** 304fd306cSNickeau * DokuWiki Plugin Js Action 404fd306cSNickeau * 504fd306cSNickeau */ 604fd306cSNickeau 704fd306cSNickeauuse ComboStrap\ArrayUtility; 804fd306cSNickeauuse ComboStrap\Bootstrap; 904fd306cSNickeauuse ComboStrap\ExceptionNotFound; 1004fd306cSNickeauuse ComboStrap\ExecutionContext; 1104fd306cSNickeauuse ComboStrap\Identity; 1204fd306cSNickeauuse ComboStrap\LogUtility; 1304fd306cSNickeauuse ComboStrap\TemplateForWebPage; 1404fd306cSNickeauuse ComboStrap\SiteConfig; 1504fd306cSNickeau 1604fd306cSNickeau 1704fd306cSNickeau/** 1804fd306cSNickeau * 1904fd306cSNickeau * 2004fd306cSNickeau */ 2104fd306cSNickeauclass action_plugin_combo_snippetsbootstrap extends DokuWiki_Action_Plugin 2204fd306cSNickeau{ 2304fd306cSNickeau 2404fd306cSNickeau 2504fd306cSNickeau public const CONF_PRELOAD_CSS = "preloadCss"; 2604fd306cSNickeau /** 2704fd306cSNickeau * Use the Jquery of Dokuwiki and not of Bootstrap 2804fd306cSNickeau */ 2904fd306cSNickeau public const CONF_JQUERY_DOKU = 'jQueryDoku'; 3004fd306cSNickeau public const CONF_JQUERY_DOKU_DEFAULT = 0; 3104fd306cSNickeau 3204fd306cSNickeau /** 3304fd306cSNickeau * Disable the javascript of Dokuwiki 3404fd306cSNickeau * if public 3504fd306cSNickeau * https://combostrap.com/frontend/optimization 3604fd306cSNickeau */ 3704fd306cSNickeau public const CONF_DISABLE_BACKEND_JAVASCRIPT = "disableBackendJavascript"; 3804fd306cSNickeau 3904fd306cSNickeau /** 4004fd306cSNickeau * This is so a bad practice, default to no 4104fd306cSNickeau * but fun to watch 4204fd306cSNickeau */ 4304fd306cSNickeau const CONF_PRELOAD_CSS_DEFAULT = 0; 4404fd306cSNickeau const JQUERY_CANONICAL = "jquery"; 4504fd306cSNickeau const FRONT_END_OPTIMIZATION_CANONICAL = 'frontend:optimization'; 4604fd306cSNickeau 4704fd306cSNickeau 4804fd306cSNickeau /** 4904fd306cSNickeau * @param Doku_Event $event 5004fd306cSNickeau * @param $param 5104fd306cSNickeau * Function that handle the META HEADER event 5204fd306cSNickeau * * It will add the Bootstrap Js and CSS 5304fd306cSNickeau * * Make all script and resources defer 5404fd306cSNickeau * @throws Exception 5504fd306cSNickeau * @noinspection PhpUnused 5604fd306cSNickeau */ 5704fd306cSNickeau public static function handle_bootstrap(Doku_Event &$event, $param) 5804fd306cSNickeau { 5904fd306cSNickeau 6004fd306cSNickeau 6104fd306cSNickeau $newHeaderTypes = array(); 6204fd306cSNickeau 6304fd306cSNickeau $bootstrap = Bootstrap::getFromContext(); 6404fd306cSNickeau $bootStrapMajorVersion = $bootstrap->getMajorVersion(); 6504fd306cSNickeau $eventHeaderTypes = $event->data; 6604fd306cSNickeau $executionContextConfig = ExecutionContext::getActualOrCreateFromEnv()->getConfig(); 6704fd306cSNickeau foreach ($eventHeaderTypes as $headTagName => $headTagsAsArray) { 6804fd306cSNickeau switch ($headTagName) { 6904fd306cSNickeau 7004fd306cSNickeau case "link": 7104fd306cSNickeau /** 7204fd306cSNickeau * Link tag processing 7304fd306cSNickeau * ie index, rss, manifest, search, alternate, stylesheet 7404fd306cSNickeau */ 7504fd306cSNickeau $headTagsAsArray[] = $bootstrap->getCssSnippet()->toDokuWikiArray(); 7604fd306cSNickeau 7704fd306cSNickeau // preload all CSS is an heresy as it creates a FOUC (Flash of non-styled element) 7804fd306cSNickeau // but we know it only now and this is fun to experience for the user 7904fd306cSNickeau $cssPreloadConf = $executionContextConfig->getValue(self::CONF_PRELOAD_CSS, self::CONF_PRELOAD_CSS_DEFAULT); 8004fd306cSNickeau $newLinkData = array(); 8104fd306cSNickeau foreach ($headTagsAsArray as $linkData) { 82*70bbd7f1Sgerardnico $rel = $linkData['rel'] ?? null; 83*70bbd7f1Sgerardnico switch ($rel) { 8404fd306cSNickeau case 'edit': 8504fd306cSNickeau break; 8604fd306cSNickeau case 'preload': 8704fd306cSNickeau /** 8804fd306cSNickeau * Preload can be set at the array level with the critical attribute 8904fd306cSNickeau * If the preload attribute is present 9004fd306cSNickeau * We get that for instance for css animation style sheet 9104fd306cSNickeau * that are not needed for rendering 9204fd306cSNickeau */ 9304fd306cSNickeau if (isset($linkData["as"])) { 9404fd306cSNickeau if ($linkData["as"] === "style") { 9504fd306cSNickeau $newLinkData[] = self::captureStylePreloadingAndTransformToPreloadCssTag($linkData); 9604fd306cSNickeau continue 2; 9704fd306cSNickeau } 9804fd306cSNickeau } 9904fd306cSNickeau $newLinkData[] = $linkData; 10004fd306cSNickeau break; 10104fd306cSNickeau case 'stylesheet': 10204fd306cSNickeau if ($cssPreloadConf) { 10304fd306cSNickeau $newLinkData[] = self::captureStylePreloadingAndTransformToPreloadCssTag($linkData); 10404fd306cSNickeau continue 2; 10504fd306cSNickeau } 10604fd306cSNickeau $newLinkData[] = $linkData; 10704fd306cSNickeau break; 10804fd306cSNickeau default: 10904fd306cSNickeau $newLinkData[] = $linkData; 11004fd306cSNickeau break; 11104fd306cSNickeau } 11204fd306cSNickeau } 11304fd306cSNickeau 11404fd306cSNickeau $newHeaderTypes[$headTagName] = $newLinkData; 11504fd306cSNickeau break; 11604fd306cSNickeau 11704fd306cSNickeau case "script": 11804fd306cSNickeau 11904fd306cSNickeau /** 12004fd306cSNickeau * Script processing 12104fd306cSNickeau * 12204fd306cSNickeau * Do we delete the dokuwiki javascript ? 12304fd306cSNickeau */ 12404fd306cSNickeau $scriptToDeletes = []; 12504fd306cSNickeau $disableBackend = SiteConfig::getConfValue(self::CONF_DISABLE_BACKEND_JAVASCRIPT, 0); 12604fd306cSNickeau if (!Identity::isLoggedIn() && $disableBackend) { 12704fd306cSNickeau $scriptToDeletes = [ 12804fd306cSNickeau //'JSINFO', Don't delete Jsinfo !! It contains metadata information (that is used to get context) 12904fd306cSNickeau 'js.php' 13004fd306cSNickeau ]; 13104fd306cSNickeau if ($bootStrapMajorVersion == "5") { 13204fd306cSNickeau // bs 5 does not depends on jquery 13304fd306cSNickeau $scriptToDeletes[] = "jquery.php"; 13404fd306cSNickeau } 13504fd306cSNickeau } 13604fd306cSNickeau 13704fd306cSNickeau /** 13804fd306cSNickeau * The new script array 13904fd306cSNickeau * that will replace the actual 14004fd306cSNickeau */ 14104fd306cSNickeau $newScriptTagAsArray = array(); 14204fd306cSNickeau /** 14304fd306cSNickeau * Scan: 14404fd306cSNickeau * * Capture the Dokuwiki Jquery Tags 14504fd306cSNickeau * * Delete for optimization if needed 14604fd306cSNickeau * 14704fd306cSNickeau * @var array A variable to hold the Jquery scripts 14804fd306cSNickeau * jquery-migrate, jquery, jquery-ui ou jquery.php 14904fd306cSNickeau * see https://www.dokuwiki.org/config:jquerycdn 15004fd306cSNickeau */ 15104fd306cSNickeau $jqueryDokuScriptsTagsAsArray = array(); 15204fd306cSNickeau foreach ($headTagsAsArray as $scriptData) { 15304fd306cSNickeau 15404fd306cSNickeau foreach ($scriptToDeletes as $scriptToDelete) { 15504fd306cSNickeau if (isset($scriptData["_data"]) && !empty($scriptData["_data"])) { 15604fd306cSNickeau $haystack = $scriptData["_data"]; 15704fd306cSNickeau } else { 15804fd306cSNickeau $haystack = $scriptData["src"]; 15904fd306cSNickeau } 16004fd306cSNickeau if (preg_match("/$scriptToDelete/i", $haystack)) { 16104fd306cSNickeau continue 2; 16204fd306cSNickeau } 16304fd306cSNickeau } 16404fd306cSNickeau 16504fd306cSNickeau $critical = false; 16604fd306cSNickeau if (isset($scriptData["critical"])) { 16704fd306cSNickeau $critical = $scriptData["critical"]; 16804fd306cSNickeau unset($scriptData["critical"]); 16904fd306cSNickeau } 17004fd306cSNickeau 17104fd306cSNickeau // defer is only for external resource 17204fd306cSNickeau // if this is not, this is illegal 17304fd306cSNickeau if (isset($scriptData["src"])) { 17404fd306cSNickeau if (!$critical) { 17504fd306cSNickeau $scriptData['defer'] = null; 17604fd306cSNickeau } 17704fd306cSNickeau } 17804fd306cSNickeau 17904fd306cSNickeau if (isset($scriptData["type"])) { 18004fd306cSNickeau $type = strtolower($scriptData["type"]); 18104fd306cSNickeau if ($type == "text/javascript") { 18204fd306cSNickeau unset($scriptData["type"]); 18304fd306cSNickeau } 18404fd306cSNickeau } 18504fd306cSNickeau 18604fd306cSNickeau // The charset attribute on the script element is obsolete. 18704fd306cSNickeau if (isset($scriptData["charset"])) { 18804fd306cSNickeau unset($scriptData["charset"]); 18904fd306cSNickeau } 19004fd306cSNickeau 19104fd306cSNickeau // Jquery ? 19204fd306cSNickeau $jqueryFound = false; 19304fd306cSNickeau // script may also be just an online script without the src attribute 19404fd306cSNickeau if (array_key_exists('src', $scriptData)) { 19504fd306cSNickeau $jqueryFound = strpos($scriptData['src'], 'jquery'); 19604fd306cSNickeau } 19704fd306cSNickeau if ($jqueryFound === false) { 19804fd306cSNickeau $newScriptTagAsArray[] = $scriptData; 19904fd306cSNickeau } else { 20004fd306cSNickeau $jqueryDokuScriptsTagsAsArray[] = $scriptData; 20104fd306cSNickeau } 20204fd306cSNickeau 20304fd306cSNickeau } 20404fd306cSNickeau 20504fd306cSNickeau /** 20604fd306cSNickeau * Add Bootstrap scripts 20704fd306cSNickeau * At the top of the queue 20804fd306cSNickeau */ 20904fd306cSNickeau if ($bootStrapMajorVersion === 4) { 21004fd306cSNickeau $useJqueryDoku = ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getBooleanValue(self::CONF_JQUERY_DOKU, self::CONF_JQUERY_DOKU_DEFAULT); 21104fd306cSNickeau if ( 21204fd306cSNickeau !Identity::isLoggedIn() 21304fd306cSNickeau && !$useJqueryDoku 21404fd306cSNickeau ) { 21504fd306cSNickeau /** 21604fd306cSNickeau * We take the Javascript of Bootstrap 21704fd306cSNickeau * (Jquery and others) 21804fd306cSNickeau */ 21904fd306cSNickeau $boostrapSnippetsAsArray = []; 22004fd306cSNickeau foreach ($bootstrap->getJsSnippets() as $snippet) { 22104fd306cSNickeau $boostrapSnippetsAsArray[] = $snippet->toDokuWikiArray(); 22204fd306cSNickeau } 22304fd306cSNickeau /** 22404fd306cSNickeau * At the top of the queue 22504fd306cSNickeau */ 22604fd306cSNickeau $newScriptTagAsArray = array_merge($boostrapSnippetsAsArray, $newScriptTagAsArray); 22704fd306cSNickeau } else { 22804fd306cSNickeau // Logged in 22904fd306cSNickeau // We take the Jqueries of doku and we add Bootstrap 23004fd306cSNickeau $newScriptTagAsArray = array_merge($jqueryDokuScriptsTagsAsArray, $newScriptTagAsArray); // js 23104fd306cSNickeau // We had popper of Bootstrap 23204fd306cSNickeau $newScriptTagAsArray[] = $bootstrap->getPopperSnippet()->toDokuWikiArray(); 23304fd306cSNickeau // We had the js of Bootstrap 23404fd306cSNickeau $newScriptTagAsArray[] = $bootstrap->getBootstrapJsSnippet()->toDokuWikiArray(); 23504fd306cSNickeau } 23604fd306cSNickeau } else { 23704fd306cSNickeau 23804fd306cSNickeau // There is no JQuery in 5 23904fd306cSNickeau // We had the js of Bootstrap and popper 24004fd306cSNickeau // Add Jquery before the js.php 24104fd306cSNickeau $newScriptTagAsArray = array_merge($jqueryDokuScriptsTagsAsArray, $newScriptTagAsArray); // js 24204fd306cSNickeau // Then add at the top of the top (first of the first) bootstrap 24304fd306cSNickeau // Why ? Because Jquery should be last to be able to see the missing icon 24404fd306cSNickeau // https://stackoverflow.com/questions/17367736/jquery-ui-dialog-missing-close-icon 24504fd306cSNickeau $bootstrapTagArray[] = $bootstrap->getPopperSnippet()->toDokuWikiArray(); 24604fd306cSNickeau $bootstrapTagArray[] = $bootstrap->getBootstrapJsSnippet()->toDokuWikiArray(); 24704fd306cSNickeau $newScriptTagAsArray = array_merge($bootstrapTagArray, $newScriptTagAsArray); 24804fd306cSNickeau 24904fd306cSNickeau } 25004fd306cSNickeau 25104fd306cSNickeau $newHeaderTypes[$headTagName] = $newScriptTagAsArray; 25204fd306cSNickeau break; 25304fd306cSNickeau case "meta": 25404fd306cSNickeau $newHeaderData = array(); 25504fd306cSNickeau foreach ($headTagsAsArray as $metaData) { 25604fd306cSNickeau // Content should never be null 25704fd306cSNickeau // Name may change 25804fd306cSNickeau // https://www.w3.org/TR/html4/struct/global.html#edef-META 25904fd306cSNickeau if (!key_exists("content", $metaData)) { 26004fd306cSNickeau $message = "The head meta (" . print_r($metaData, true) . ") does not have a content property"; 26104fd306cSNickeau LogUtility::error($message, self::FRONT_END_OPTIMIZATION_CANONICAL); 26204fd306cSNickeau } else { 26304fd306cSNickeau $content = $metaData["content"]; 26404fd306cSNickeau if (empty($content)) { 26504fd306cSNickeau $messageEmpty = "the below head meta has an empty content property (" . ArrayUtility::formatAsString($metaData) . ")"; 26604fd306cSNickeau LogUtility::error($messageEmpty, self::FRONT_END_OPTIMIZATION_CANONICAL); 26704fd306cSNickeau } else { 26804fd306cSNickeau $newHeaderData[] = $metaData; 26904fd306cSNickeau } 27004fd306cSNickeau } 27104fd306cSNickeau } 27204fd306cSNickeau $newHeaderTypes[$headTagName] = $newHeaderData; 27304fd306cSNickeau break; 27404fd306cSNickeau case "noscript": // https://github.com/ComboStrap/dokuwiki-plugin-gtm/blob/master/action.php#L32 27504fd306cSNickeau case "style": 27604fd306cSNickeau $newHeaderTypes[$headTagName] = $headTagsAsArray; 27704fd306cSNickeau break; 27804fd306cSNickeau default: 27904fd306cSNickeau $message = "The header type ($headTagName) is unknown and was not controlled."; 28004fd306cSNickeau $newHeaderTypes[$headTagName] = $headTagsAsArray; 28104fd306cSNickeau LogUtility::error($message, self::FRONT_END_OPTIMIZATION_CANONICAL); 28204fd306cSNickeau 28304fd306cSNickeau } 28404fd306cSNickeau } 28504fd306cSNickeau 28604fd306cSNickeau $event->data = $newHeaderTypes; 28704fd306cSNickeau 28804fd306cSNickeau 28904fd306cSNickeau } 29004fd306cSNickeau 29104fd306cSNickeau /** 29204fd306cSNickeau * @param $linkData - an array of link style sheet data 29304fd306cSNickeau * @return array - the array with the preload attributes 29404fd306cSNickeau */ 29504fd306cSNickeau public static function captureStylePreloadingAndTransformToPreloadCssTag($linkData): array 29604fd306cSNickeau { 29704fd306cSNickeau /** 29804fd306cSNickeau * Save the stylesheet to load it at the end 29904fd306cSNickeau */ 30004fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 30104fd306cSNickeau try { 30204fd306cSNickeau $preloadedCss = &$executionContext->getRuntimeObject(TemplateForWebPage::PRELOAD_TAG); 30304fd306cSNickeau } catch (ExceptionNotFound $e) { 30404fd306cSNickeau $preloadedCss = []; 30504fd306cSNickeau $executionContext->setRuntimeObject(TemplateForWebPage::PRELOAD_TAG, $preloadedCss); 30604fd306cSNickeau } 30704fd306cSNickeau $preloadedCss[] = $linkData; 30804fd306cSNickeau 30904fd306cSNickeau /** 31004fd306cSNickeau * Modify the actual tag data 31104fd306cSNickeau * Change the loading mechanism to preload 31204fd306cSNickeau */ 31304fd306cSNickeau $linkData['rel'] = 'preload'; 31404fd306cSNickeau $linkData['as'] = 'style'; 31504fd306cSNickeau return $linkData; 31604fd306cSNickeau } 31704fd306cSNickeau 31804fd306cSNickeau 31904fd306cSNickeau /** 32004fd306cSNickeau * Registers our handler for the MANIFEST_SEND event 32104fd306cSNickeau * https://www.dokuwiki.org/devel:event:js_script_list 32204fd306cSNickeau * manipulate the list of JavaScripts that will be concatenated 32304fd306cSNickeau * @param Doku_Event_Handler $controller 32404fd306cSNickeau */ 32504fd306cSNickeau public function register(Doku_Event_Handler $controller) 32604fd306cSNickeau { 32704fd306cSNickeau 32804fd306cSNickeau $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_bootstrap'); 32904fd306cSNickeau 33004fd306cSNickeau } 33104fd306cSNickeau 33204fd306cSNickeau 33304fd306cSNickeau} 33404fd306cSNickeau 335