Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"renderer.min.js","sources":["../../../src/local/templates/renderer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\nimport * as Log from 'core/log';\nimport * as Truncate from 'core/truncate';\nimport * as UserDate from 'core/user_date';\nimport Pending from 'core/pending';\nimport {getStrings} from 'core/str';\nimport IconSystem from 'core/icon_system';\nimport config from 'core/config';\nimport mustache from 'core/mustache';\nimport Loader from './loader';\nimport {getNormalisedComponent} from 'core/utils';\n\n/** @var {string} The placeholder character used for standard strings (unclean) */\nconst placeholderString = 's';\n\n/** @var {string} The placeholder character used for cleaned strings */\nconst placeholderCleanedString = 'c';\n\n/**\n * Template Renderer Class.\n *\n * Note: This class is not intended to be instantiated directly. Instead, use the core/templates module.\n *\n * @module     core/local/templates/renderer\n * @copyright  2023 Andrew Lyons <andrew@nicols.co.uk>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since      4.3\n */\nexport default class Renderer {\n    /** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template */\n    requiredStrings = null;\n\n    /** @var {object[]} requiredDates - Collection of dates found during the rendering of one template */\n    requiredDates = [];\n\n    /** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template */\n    requiredJS = null;\n\n    /** @var {String} themeName for the current render */\n    currentThemeName = '';\n\n    /** @var {Number} uniqInstances Count of times this constructor has been called. */\n    static uniqInstances = 0;\n\n    /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n    static loadTemplateBuffer = [];\n\n    /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n    static isLoadingTemplates = false;\n\n    /** @var {Object} iconSystem - Object extending core/iconsystem */\n    iconSystem = null;\n\n    /** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers */\n    static disallowedNestedHelpers = [\n        'js',\n    ];\n\n    /** @var {String[]} templateCache - Cache of already loaded template strings */\n    static templateCache = {};\n\n    /**\n     * Cache of already loaded template promises.\n     *\n     * @type {Promise[]}\n     * @static\n     * @private\n     */\n    static templatePromises = {};\n\n    /**\n     * The loader used to fetch templates.\n     * @type {Loader}\n     * @static\n     * @private\n     */\n    static loader = Loader;\n\n    /**\n     * Constructor\n     *\n     * Each call to templates.render gets it's own instance of this class.\n     */\n    constructor() {\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        this.requiredDates = [];\n        this.currentThemeName = '';\n    }\n\n    /**\n     * Set the template loader to use for all Template renderers.\n     *\n     * @param {Loader} loader\n     */\n    static setLoader(loader) {\n        this.loader = loader;\n    }\n\n    /**\n     * Get the Loader used to fetch templates.\n     *\n     * @returns {Loader}\n     */\n    static getLoader() {\n        return this.loader;\n    }\n\n    /**\n     * Render a single image icon.\n     *\n     * @method renderIcon\n     * @private\n     * @param {string} key The icon key.\n     * @param {string} component The component name.\n     * @param {string} title The icon title\n     * @returns {Promise}\n     */\n    async renderIcon(key, component, title) {\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        component = getNormalisedComponent(component);\n\n        await this.setupIconSystem();\n        const template = await Renderer.getLoader().getTemplate(\n            this.iconSystem.getTemplateName(),\n            this.currentThemeName,\n        );\n\n        return this.iconSystem.renderIcon(\n            key,\n            component,\n            title,\n            template\n        );\n    }\n\n    /**\n     * Helper to set up the icon system.\n     */\n    async setupIconSystem() {\n        if (!this.iconSystem) {\n            this.iconSystem = await IconSystem.instance();\n        }\n\n        return this.iconSystem;\n    }\n\n    /**\n     * Render image icons.\n     *\n     * @method pixHelper\n     * @private\n     * @param {object} context The mustache context\n     * @param {string} sectionText The text to parse arguments from.\n     * @param {function} helper Used to render the alt attribute of the text.\n     * @returns {string}\n     */\n    pixHelper(context, sectionText, helper) {\n        const parts = sectionText.split(',');\n        let key = '';\n        let component = '';\n        let text = '';\n\n        if (parts.length > 0) {\n            key = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            component = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            text = helper(parts.join(',').trim(), context);\n        }\n\n        // Note: We cannot use Promises in Mustache helpers.\n        // We must fetch straight from the Loader cache.\n        // The Loader cache is statically defined on the Loader class and should be used by all children.\n        const Loader = Renderer.getLoader();\n        const templateName = this.iconSystem.getTemplateName();\n        const searchKey = Loader.getSearchKey(this.currentThemeName, templateName);\n        const template = Loader.getTemplateFromCache(searchKey);\n\n        component = getNormalisedComponent(component);\n\n        // The key might have been escaped by the JS Mustache engine which\n        // converts forward slashes to HTML entities. Let us undo that here.\n        key = key.replace(/&#x2F;/gi, '/');\n\n        return this.iconSystem.renderIcon(\n            key,\n            component,\n            text,\n            template\n        );\n    }\n\n    /**\n     * Render blocks of javascript and save them in an array.\n     *\n     * @method jsHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to save as a js block.\n     * @param {function} helper Used to render the block.\n     * @returns {string}\n     */\n    jsHelper(context, sectionText, helper) {\n        this.requiredJS.push(helper(sectionText, context));\n        return '';\n    }\n\n    /**\n     * String helper used to render {{#str}}abd component { a : 'fish'}{{/str}}\n     * into a get_string call.\n     *\n     * @method stringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @returns {string}\n     */\n    stringHelper(context, sectionText, helper) {\n        // A string instruction is in the format:\n        // key, component, params.\n\n        let parts = sectionText.split(',');\n\n        const key = parts.length > 0 ? parts.shift().trim() : '';\n        const component = parts.length > 0 ? getNormalisedComponent(parts.shift().trim()) : '';\n        let param = parts.length > 0 ? parts.join(',').trim() : '';\n\n        if (param !== '') {\n            // Allow variable expansion in the param part only.\n            param = helper(param, context);\n        }\n\n        if (param.match(/^{\\s*\"/gm)) {\n            // If it can't be parsed then the string is not a JSON format.\n            try {\n                const parsedParam = JSON.parse(param);\n                // Handle non-exception-throwing cases, e.g. null, integer, boolean.\n                if (parsedParam && typeof parsedParam === \"object\") {\n                    param = parsedParam;\n                }\n            } catch (err) {\n                // This was probably not JSON.\n                // Keep the error message visible but do not promote it because it may not be an error.\n                window.console.warn(err.message);\n            }\n        }\n\n        const index = this.requiredStrings.length;\n        this.requiredStrings.push({\n            key,\n            component,\n            param,\n        });\n\n        // The placeholder must not use {{}} as those can be misinterpreted by the engine.\n        return `[[_s${index}]]`;\n    }\n\n    /**\n     * String helper to render {{#cleanstr}}abd component { a : 'fish'}{{/cleanstr}}\n     * into a get_string following by an HTML escape.\n     *\n     * @method cleanStringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @returns {string}\n     */\n    cleanStringHelper(context, sectionText, helper) {\n        // We're going to use [[_cx]] format for clean strings, where x is a number.\n        // Hence, replacing 's' with 'c' in the placeholder that stringHelper returns.\n        return this\n            .stringHelper(context, sectionText, helper)\n            .replace(placeholderString, placeholderCleanedString);\n    }\n\n    /**\n     * Quote helper used to wrap content in quotes, and escape all special JSON characters present in the content.\n     *\n     * @method quoteHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @returns {string}\n     */\n    quoteHelper(context, sectionText, helper) {\n        let content = helper(sectionText.trim(), context);\n\n        // Escape the {{ and JSON encode.\n        // This involves wrapping {{, and }} in change delimeter tags.\n        content = JSON.stringify(content);\n        content = content.replace(/([{}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>');\n        return content;\n    }\n\n    /**\n     * Shorten text helper to truncate text and append a trailing ellipsis.\n     *\n     * @method shortenTextHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @returns {string}\n     */\n    shortenTextHelper(context, sectionText, helper) {\n        // Non-greedy split on comma to grab section text into the length and\n        // text parts.\n        const parts = sectionText.match(/(.*?),(.*)/);\n\n        // The length is the part matched in the first set of parethesis.\n        const length = parts[1].trim();\n        // The length is the part matched in the second set of parethesis.\n        const text = parts[2].trim();\n        const content = helper(text, context);\n        return Truncate.truncate(content, {\n            length,\n            words: true,\n            ellipsis: '...'\n        });\n    }\n\n    /**\n     * User date helper to render user dates from timestamps.\n     *\n     * @method userDateHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @returns {string}\n     */\n    userDateHelper(context, sectionText, helper) {\n        // Non-greedy split on comma to grab the timestamp and format.\n        const parts = sectionText.match(/(.*?),(.*)/);\n\n        const timestamp = helper(parts[1].trim(), context);\n        const format = helper(parts[2].trim(), context);\n        const index = this.requiredDates.length;\n\n        this.requiredDates.push({\n            timestamp: timestamp,\n            format: format\n        });\n\n        return `[[_t_${index}]]`;\n    }\n\n    /**\n     * Return a helper function to be added to the context for rendering the a\n     * template.\n     *\n     * This will parse the provided text before giving it to the helper function\n     * in order to remove any disallowed nested helpers to prevent one helper\n     * from calling another.\n     *\n     * In particular to prevent the JS helper from being called from within another\n     * helper because it can lead to security issues when the JS portion is user\n     * provided.\n     *\n     * @param  {function} helperFunction The helper function to add\n     * @param  {object} context The template context for the helper function\n     * @returns {Function} To be set in the context\n     */\n    addHelperFunction(helperFunction, context) {\n        return function() {\n            return function(sectionText, helper) {\n                // Override the disallowed helpers in the template context with\n                // a function that returns an empty string for use when executing\n                // other helpers. This is to prevent these helpers from being\n                // executed as part of the rendering of another helper in order to\n                // prevent any potential security issues.\n                const originalHelpers = Renderer.disallowedNestedHelpers.reduce((carry, name) => {\n                    if (context.hasOwnProperty(name)) {\n                        carry[name] = context[name];\n                    }\n\n                    return carry;\n                }, {});\n\n                Renderer.disallowedNestedHelpers.forEach((helperName) => {\n                    context[helperName] = () => '';\n                });\n\n                // Execute the helper with the modified context that doesn't include\n                // the disallowed nested helpers. This prevents the disallowed\n                // helpers from being called from within other helpers.\n                const result = helperFunction.apply(this, [context, sectionText, helper]);\n\n                // Restore the original helper implementation in the context so that\n                // any further rendering has access to them again.\n                for (const name in originalHelpers) {\n                    context[name] = originalHelpers[name];\n                }\n\n                return result;\n            }.bind(this);\n        }.bind(this);\n    }\n\n    /**\n     * Add some common helper functions to all context objects passed to templates.\n     * These helpers match exactly the helpers available in php.\n     *\n     * @method addHelpers\n     * @private\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName We set this multiple times, because there are async calls.\n     */\n    addHelpers(context, themeName) {\n        this.currentThemeName = themeName;\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        context.uniqid = (Renderer.uniqInstances++);\n\n        // Please note that these helpers _must_ not return a Promise.\n        context.str = this.addHelperFunction(this.stringHelper, context);\n        context.cleanstr = this.addHelperFunction(this.cleanStringHelper, context);\n        context.pix = this.addHelperFunction(this.pixHelper, context);\n        context.js = this.addHelperFunction(this.jsHelper, context);\n        context.quote = this.addHelperFunction(this.quoteHelper, context);\n        context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);\n        context.userdate = this.addHelperFunction(this.userDateHelper, context);\n        context.globals = {config: config};\n        context.currentTheme = themeName;\n    }\n\n    /**\n     * Get all the JS blocks from the last rendered template.\n     *\n     * @method getJS\n     * @private\n     * @returns {string}\n     */\n    getJS() {\n        return this.requiredJS.join(\";\\n\");\n    }\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the placeholders found in a string\n     * with the their respective translated strings.\n     *\n     * Previously we were relying on String.replace() but the complexity increased with\n     * the numbers of strings to replace. Now we manually walk the string and stop at each\n     * placeholder we find, only then we replace it. Most of the time we will\n     * replace all the placeholders in a single run, at times we will need a few\n     * more runs when placeholders are replaced with strings that contain placeholders\n     * themselves.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Map} stringMap The strings to replace with.\n     * @returns {String} The treated content.\n     */\n    treatStringsInContent(content, stringMap) {\n        // Placeholders are in the for [[_sX]] or [[_cX]] where X is the string index.\n        const stringPattern = /(?<placeholder>\\[\\[_(?<stringType>[cs])(?<stringIndex>\\d+)\\]\\])/g;\n\n        // A helpre to fetch the string for a given placeholder.\n        const getUpdatedString = ({placeholder, stringType, stringIndex}) => {\n            if (stringMap.has(placeholder)) {\n                return stringMap.get(placeholder);\n            }\n\n            if (stringType === placeholderCleanedString) {\n                // Attempt to find the unclean string and clean it. Store it for later use.\n                const uncleanString = stringMap.get(`[[_s${stringIndex}]]`);\n                if (uncleanString) {\n                    stringMap.set(placeholder, mustache.escape(uncleanString));\n                    return stringMap.get(placeholder);\n                }\n            }\n\n            Log.debug(`Could not find string for pattern ${placeholder}`);\n            return '';\n        };\n\n        // Find all placeholders in the content and replace them with their respective strings.\n        let match;\n        while ((match = stringPattern.exec(content)) !== null) {\n            let updatedContent = content.slice(0, match.index);\n            updatedContent += getUpdatedString(match.groups);\n            updatedContent += content.slice(match.index + match.groups.placeholder.length);\n\n            content = updatedContent;\n        }\n\n        return content;\n    }\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the date placeholders found in the\n     * content with the their respective translated dates.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Array} dates The dates to replace with.\n     * @returns {String} The treated content.\n     */\n    treatDatesInContent(content, dates) {\n        dates.forEach((date, index) => {\n            content = content.replace(\n                new RegExp(`\\\\[\\\\[_t_${index}\\\\]\\\\]`, 'g'),\n                date,\n            );\n        });\n\n        return content;\n    }\n\n    /**\n     * Render a template and then call the callback with the result.\n     *\n     * @method doRender\n     * @private\n     * @param {string|Promise} templateSourcePromise The mustache template to render.\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName Name of the current theme.\n     * @returns {Promise<object<string, string>>} The rendered HTML and JS.\n     */\n    async doRender(templateSourcePromise, context, themeName) {\n        this.currentThemeName = themeName;\n        const iconTemplate = this.iconSystem.getTemplateName();\n\n        const pendingPromise = new Pending('core/templates:doRender');\n        const [templateSource] = await Promise.all([\n            templateSourcePromise,\n            Renderer.getLoader().getTemplate(iconTemplate, themeName),\n        ]);\n\n        this.addHelpers(context, themeName);\n\n        // Render the template.\n        const renderedContent = await mustache.render(\n            templateSource,\n            context,\n            // Note: The third parameter is a function that will be called to process partials.\n            (partialName) => Renderer.getLoader().partialHelper(partialName, themeName),\n        );\n\n        const {html, js} = await this.processRenderedContent(renderedContent);\n\n        pendingPromise.resolve();\n        return {html, js};\n    }\n\n    /**\n     * Process the rendered content, treating any strings and applying and helper strings, dates, etc.\n     * @param {string} renderedContent\n     * @returns {Promise<object<string, string>>} The rendered HTML and JS.\n     */\n    async processRenderedContent(renderedContent) {\n        let html = renderedContent.trim();\n        let js = this.getJS();\n\n        if (this.requiredStrings.length > 0) {\n            // Fetch the strings into a new Map using the placeholder as an index.\n            // Note: We only fetch the unclean version. Cleaning of strings happens lazily in treatStringsInContent.\n            const stringMap = new Map(\n                (await getStrings(this.requiredStrings)).map((string, index) => (\n                    [`[[_s${index}]]`, string]\n                ))\n            );\n\n            // Make sure string substitutions are done for the userdate\n            // values as well.\n            this.requiredDates = this.requiredDates.map(function(date) {\n                return {\n                    timestamp: this.treatStringsInContent(date.timestamp, stringMap),\n                    format: this.treatStringsInContent(date.format, stringMap)\n                };\n            }.bind(this));\n\n            // Why do we not do another call the render here?\n            //\n            // Because that would expose DOS holes. E.g.\n            // I create an assignment called \"{{fish\" which\n            // would get inserted in the template in the first pass\n            // and cause the template to die on the second pass (unbalanced).\n            html = this.treatStringsInContent(html, stringMap);\n            js = this.treatStringsInContent(js, stringMap);\n        }\n\n        // This has to happen after the strings replacement because you can\n        // use the string helper in content for the user date helper.\n        if (this.requiredDates.length > 0) {\n            const dates = await UserDate.get(this.requiredDates);\n            html = this.treatDatesInContent(html, dates);\n            js = this.treatDatesInContent(js, dates);\n        }\n\n        return {html, js};\n    }\n\n    /**\n     * Load a template and call doRender on it.\n     *\n     * @method render\n     * @private\n     * @param {string} templateName - should consist of the component and the name of the template like this:\n     *                              core/menu (lib/templates/menu.mustache) or\n     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)\n     * @param {Object} [context={}] - Could be array, string or simple value for the context of the template.\n     * @param {string} [themeName] - Name of the current theme.\n     * @returns {Promise<object>} Native promise object resolved when the template has been rendered.}\n     */\n    async render(\n        templateName,\n        context = {},\n        themeName = config.theme,\n    ) {\n        this.currentThemeName = themeName;\n\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        await this.setupIconSystem();\n\n        const templateSource = Renderer.getLoader().cachePartials(templateName, themeName);\n        return this.doRender(templateSource, context, themeName);\n    }\n}\n"],"names":["Renderer","constructor","requiredStrings","requiredJS","requiredDates","currentThemeName","loader","this","key","component","title","setupIconSystem","template","getLoader","getTemplate","iconSystem","getTemplateName","renderIcon","IconSystem","instance","pixHelper","context","sectionText","helper","parts","split","text","length","shift","trim","join","Loader","templateName","searchKey","getSearchKey","getTemplateFromCache","replace","jsHelper","push","stringHelper","param","match","parsedParam","JSON","parse","err","window","console","warn","message","index","cleanStringHelper","quoteHelper","content","stringify","shortenTextHelper","Truncate","truncate","words","ellipsis","userDateHelper","timestamp","format","addHelperFunction","helperFunction","originalHelpers","disallowedNestedHelpers","reduce","carry","name","hasOwnProperty","forEach","helperName","result","apply","bind","addHelpers","themeName","uniqid","uniqInstances","str","cleanstr","pix","js","quote","shortentext","userdate","globals","config","currentTheme","getJS","treatStringsInContent","stringMap","stringPattern","getUpdatedString","_ref","placeholder","stringType","stringIndex","has","get","uncleanString","set","mustache","escape","Log","debug","exec","updatedContent","slice","groups","treatDatesInContent","dates","date","RegExp","templateSourcePromise","iconTemplate","pendingPromise","Pending","templateSource","Promise","all","renderedContent","render","partialName","partialHelper","html","processRenderedContent","resolve","Map","map","string","UserDate","theme","cachePartials","doRender"],"mappings":"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"}