xref: /plugin/combo/action/snippetsbootstrap.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
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