{"version":3,"file":"templates.min.js","sources":["https:\/\/formacion.cordoba-acoge.com\/lib\/amd\/src\/templates.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 .\n\n\/**\n * Template renderer for Moodle. Load and render Moodle templates with Mustache.\n *\n * @module core\/templates\n * @copyright 2015 Damyon Wiese \n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n * @since 2.9\n *\/\ndefine([\n 'core\/mustache',\n 'jquery',\n 'core\/ajax',\n 'core\/str',\n 'core\/notification',\n 'core\/url',\n 'core\/config',\n 'core\/localstorage',\n 'core\/icon_system',\n 'core_filters\/events',\n 'core\/yui',\n 'core\/log',\n 'core\/truncate',\n 'core\/user_date',\n 'core\/pending',\n],\nfunction(\n mustache,\n $,\n ajax,\n str,\n notification,\n coreurl,\n config,\n storage,\n IconSystem,\n filterEvents,\n Y,\n Log,\n Truncate,\n UserDate,\n Pending\n) {\n\n \/\/ Module variables.\n \/** @var {Number} uniqInstances Count of times this constructor has been called. *\/\n var uniqInstances = 0;\n\n \/** @var {String[]} templateCache - Cache of already loaded template strings *\/\n var templateCache = {};\n\n \/** @var {Promise[]} templatePromises - Cache of already loaded template promises *\/\n var templatePromises = {};\n\n \/** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises *\/\n var cachePartialPromises = {};\n\n \/** @var {Object} iconSystem - Object extending core\/iconsystem *\/\n var iconSystem = {};\n\n \/** @var {Object[]} loadTemplateBuffer - List of templates to be loaded *\/\n var loadTemplateBuffer = [];\n\n \/** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded *\/\n var isLoadingTemplates = false;\n\n \/** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers *\/\n var disallowedNestedHelpers = ['js'];\n\n \/**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n *\/\n var getNormalisedComponent = function(component) {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n };\n\n \/**\n * Search the various caches for a template promise for the given search key.\n * The search key should be in the format \/\/ e.g. boost\/core\/modal.\n *\n * If the template is found in any of the caches it will populate the other caches with\n * the same data as well.\n *\n * @param {String} searchKey The template search key in the format \/\/ e.g. boost\/core\/modal\n * @return {Object} jQuery promise resolved with the template source\n *\/\n var getTemplatePromiseFromCache = function(searchKey) {\n \/\/ First try the cache of promises.\n if (searchKey in templatePromises) {\n return templatePromises[searchKey];\n }\n\n \/\/ Check the module cache.\n if (searchKey in templateCache) {\n \/\/ Add this to the promises cache for future.\n templatePromises[searchKey] = $.Deferred().resolve(templateCache[searchKey]).promise();\n return templatePromises[searchKey];\n }\n\n if (M.cfg.templaterev <= 0) {\n \/\/ Template caching is disabled. Do not store in persistent storage.\n return null;\n }\n\n \/\/ Now try local storage.\n var cached = storage.get('core_template\/' + M.cfg.templaterev + ':' + searchKey);\n if (cached) {\n \/\/ Add this to the module cache for future.\n templateCache[searchKey] = cached;\n \/\/ Add this to the promises cache for future.\n templatePromises[searchKey] = $.Deferred().resolve(cached).promise();\n return templatePromises[searchKey];\n }\n\n return null;\n };\n\n \/**\n * Take all of the templates waiting in the buffer and load them from the server\n * or from the cache.\n *\n * All of the templates that need to be loaded from the server will be batched up\n * and sent in a single network request.\n *\/\n var processLoadTemplateBuffer = function() {\n if (!loadTemplateBuffer.length) {\n return;\n }\n\n if (isLoadingTemplates) {\n return;\n }\n\n isLoadingTemplates = true;\n \/\/ Grab any templates waiting in the buffer.\n var templatesToLoad = loadTemplateBuffer.slice();\n \/\/ This will be resolved with the list of promises for the server request.\n var serverRequestsDeferred = $.Deferred();\n var requests = [];\n \/\/ Get a list of promises for each of the templates we need to load.\n var templatePromises = templatesToLoad.map(function(templateData) {\n var component = getNormalisedComponent(templateData.component);\n var name = templateData.name;\n var searchKey = templateData.searchKey;\n var theme = templateData.theme;\n var templateDeferred = templateData.deferred;\n var promise = null;\n\n \/\/ Double check to see if this template happened to have landed in the\n \/\/ cache as a dependency of an earlier template.\n var cachedPromise = getTemplatePromiseFromCache(searchKey);\n if (cachedPromise) {\n \/\/ We've seen this template so immediately resolve the existing promise.\n promise = cachedPromise;\n } else {\n \/\/ We haven't seen this template yet so we need to request it from\n \/\/ the server.\n requests.push({\n methodname: 'core_output_load_template_with_dependencies',\n args: {\n component: component,\n template: name,\n themename: theme,\n lang: $('html').attr('lang').replace(\/-\/g, '_')\n }\n });\n \/\/ Remember the index in the requests list for this template so that\n \/\/ we can get the appropriate promise back.\n var index = requests.length - 1;\n\n \/\/ The server deferred will be resolved with a list of all of the promises\n \/\/ that were sent in the order that they were added to the requests array.\n promise = serverRequestsDeferred.promise()\n .then(function(promises) {\n \/\/ The promise for this template will be the one that matches the index\n \/\/ for it's entry in the requests array.\n \/\/\n \/\/ Make sure the promise is added to the promises cache for this template\n \/\/ search key so that we don't request it again.\n templatePromises[searchKey] = promises[index].then(function(response) {\n var templateSource = null;\n\n \/\/ Process all of the template dependencies for this template and add\n \/\/ them to the caches so that we don't request them again later.\n response.templates.forEach(function(data) {\n data.component = getNormalisedComponent(data.component);\n \/\/ Generate the search key for this template in the response so that we\n \/\/ can add it to the caches.\n var tempSearchKey = [theme, data.component, data.name].join('\/');\n \/\/ Cache all of the dependent templates because we'll need them to render\n \/\/ the requested template.\n templateCache[tempSearchKey] = data.value;\n\n if (M.cfg.templaterev > 0) {\n \/\/ The template cache is enabled - set the value there.\n storage.set('core_template\/' + M.cfg.templaterev + ':' + tempSearchKey, data.value);\n }\n\n if (data.component == component && data.name == name) {\n \/\/ This is the original template that was requested so remember it to return.\n templateSource = data.value;\n }\n });\n\n if (response.strings.length) {\n \/\/ If we have strings that the template needs then warm the string cache\n \/\/ with them now so that we don't need to re-fetch them.\n str.cache_strings(response.strings.map(function(data) {\n return {\n component: getNormalisedComponent(data.component),\n key: data.name,\n value: data.value\n };\n }));\n }\n\n \/\/ Return the original template source that the user requested.\n return templateSource;\n });\n\n return templatePromises[searchKey];\n });\n }\n\n return promise\n .then(function(source) {\n \/\/ When we've successfully loaded the template then resolve the deferred\n \/\/ in the buffer so that all of the calling code can proceed.\n return templateDeferred.resolve(source);\n })\n .catch(function(error) {\n \/\/ If there was an error loading the template then reject the deferred\n \/\/ in the buffer so that all of the calling code can proceed.\n templateDeferred.reject(error);\n \/\/ Rethrow for anyone else listening.\n throw error;\n });\n });\n\n if (requests.length) {\n \/\/ We have requests to send so resolve the deferred with the promises.\n serverRequestsDeferred.resolve(ajax.call(requests, true, false, false, 0, M.cfg.templaterev));\n } else {\n \/\/ Nothing to load so we can resolve our deferred.\n serverRequestsDeferred.resolve();\n }\n\n \/\/ Once we've finished loading all of the templates then recurse to process\n \/\/ any templates that may have been added to the buffer in the time that we\n \/\/ were fetching.\n $.when.apply(null, templatePromises)\n .then(function() {\n \/\/ Remove the templates we've loaded from the buffer.\n loadTemplateBuffer.splice(0, templatesToLoad.length);\n isLoadingTemplates = false;\n processLoadTemplateBuffer();\n return;\n })\n .catch(function() {\n \/\/ Remove the templates we've loaded from the buffer.\n loadTemplateBuffer.splice(0, templatesToLoad.length);\n isLoadingTemplates = false;\n processLoadTemplateBuffer();\n });\n };\n\n \/**\n * Constructor\n *\n * Each call to templates.render gets it's own instance of this class.\n *\/\n var Renderer = function() {\n this.requiredStrings = [];\n this.requiredJS = [];\n this.requiredDates = [];\n this.currentThemeName = '';\n };\n \/\/ Class variables and functions.\n\n \/** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template *\/\n Renderer.prototype.requiredStrings = null;\n\n \/** @var {object[]} requiredDates - Collection of dates found during the rendering of one template *\/\n Renderer.prototype.requiredDates = [];\n\n \/** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template *\/\n Renderer.prototype.requiredJS = null;\n\n \/** @var {String} themeName for the current render *\/\n Renderer.prototype.currentThemeName = '';\n\n \/**\n * Load a template.\n *\n * @method getTemplate\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 * @return {Promise} JQuery promise object resolved when the template has been fetched.\n *\/\n Renderer.prototype.getTemplate = function(templateName) {\n var currentTheme = this.currentThemeName;\n var searchKey = currentTheme + '\/' + templateName;\n\n \/\/ If we haven't already seen this template then buffer it.\n var cachedPromise = getTemplatePromiseFromCache(searchKey);\n if (cachedPromise) {\n return cachedPromise;\n }\n\n \/\/ Check the buffer to see if this template has already been added.\n var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n return record.searchKey == searchKey;\n });\n if (existingBufferRecords.length) {\n \/\/ This template is already in the buffer so just return the existing\n \/\/ promise. No need to add it to the buffer again.\n return existingBufferRecords[0].deferred.promise();\n }\n\n \/\/ This is the first time this has been requested so let's add it to the buffer\n \/\/ to be loaded.\n var parts = templateName.split('\/');\n var component = getNormalisedComponent(parts.shift());\n var name = parts.join('\/');\n var deferred = $.Deferred();\n\n \/\/ Add this template to the buffer to be loaded.\n loadTemplateBuffer.push({\n component: component,\n name: name,\n theme: currentTheme,\n searchKey: searchKey,\n deferred: deferred\n });\n\n \/\/ We know there is at least one thing in the buffer so kick off a processing run.\n processLoadTemplateBuffer();\n return deferred.promise();\n };\n\n \/**\n * Prefetch a set of templates without rendering them.\n *\n * @param {Array} templateNames The list of templates to fetch\n * @param {String} currentTheme\n *\/\n Renderer.prototype.prefetchTemplates = function(templateNames, currentTheme) {\n templateNames.forEach(function(templateName) {\n var searchKey = currentTheme + '\/' + templateName;\n\n \/\/ If we haven't already seen this template then buffer it.\n if (getTemplatePromiseFromCache(searchKey)) {\n return;\n }\n\n \/\/ Check the buffer to see if this template has already been added.\n var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n return record.searchKey == searchKey;\n });\n\n if (existingBufferRecords.length) {\n \/\/ This template is already in the buffer so just return the existing promise.\n \/\/ No need to add it to the buffer again.\n return;\n }\n\n \/\/ This is the first time this has been requested so let's add it to the buffer to be loaded.\n var parts = templateName.split('\/');\n var component = getNormalisedComponent(parts.shift());\n var name = parts.join('\/');\n\n \/\/ Add this template to the buffer to be loaded.\n loadTemplateBuffer.push({\n component: component,\n name: name,\n theme: currentTheme,\n searchKey: searchKey,\n deferred: $.Deferred(),\n });\n });\n\n processLoadTemplateBuffer();\n };\n\n \/**\n * Load a partial from the cache or ajax.\n *\n * @method partialHelper\n * @private\n * @param {string} name The partial name to load.\n * @return {string}\n *\/\n Renderer.prototype.partialHelper = function(name) {\n\n var searchKey = this.currentThemeName + '\/' + name;\n\n if (!(searchKey in templateCache)) {\n notification.exception(new Error('Failed to pre-fetch the template: ' + name));\n }\n\n return templateCache[searchKey];\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 * @return {Promise}\n *\/\n Renderer.prototype.renderIcon = function(key, component, title) {\n \/\/ Preload the module to do the icon rendering based on the theme iconsystem.\n var modulename = config.iconsystemmodule;\n component = getNormalisedComponent(component);\n\n \/\/ RequireJS does not return a promise.\n var ready = $.Deferred();\n require([modulename], function(System) {\n var system = new System();\n if (!(system instanceof IconSystem)) {\n ready.reject('Invalid icon system specified' + config.iconsystemmodule);\n } else {\n iconSystem = system;\n system.init().then(ready.resolve).catch(notification.exception);\n }\n });\n\n return ready.then(function(iconSystem) {\n return this.getTemplate(iconSystem.getTemplateName());\n }.bind(this)).then(function(template) {\n return iconSystem.renderIcon(\n key,\n component,\n title,\n template\n );\n });\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 * @return {string}\n *\/\n Renderer.prototype.pixHelper = function(context, sectionText, helper) {\n var parts = sectionText.split(',');\n var key = '';\n var component = '';\n var 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 var templateName = iconSystem.getTemplateName();\n var searchKey = this.currentThemeName + '\/' + templateName;\n var template = templateCache[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(\//\/gi, '\/');\n\n return 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 * @return {string}\n *\/\n Renderer.prototype.jsHelper = function(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 * @return {string}\n *\/\n Renderer.prototype.stringHelper = function(context, sectionText, helper) {\n var parts = sectionText.split(',');\n var key = '';\n var component = '';\n var param = '';\n if (parts.length > 0) {\n key = parts.shift().trim();\n }\n if (parts.length > 0) {\n component = parts.shift().trim();\n }\n if (parts.length > 0) {\n param = parts.join(',').trim();\n }\n\n component = getNormalisedComponent(component);\n\n if (param !== '') {\n \/\/ Allow variable expansion in the param part only.\n param = helper(param, context);\n }\n\n \/\/ Allow json formatted $a arguments.\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.\n window.console.warn(err.message);\n }\n }\n\n var index = this.requiredStrings.length;\n this.requiredStrings.push({\n key: key,\n component: component,\n param: 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 * @return {string}\n *\/\n Renderer.prototype.cleanStringHelper = function(context, sectionText, helper) {\n var str = this.stringHelper(context, sectionText, helper);\n\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 str.replace('s', 'c');\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 * @return {string}\n *\/\n Renderer.prototype.quoteHelper = function(context, sectionText, helper) {\n var 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 * @return {string}\n *\/\n Renderer.prototype.shortenTextHelper = function(context, sectionText, helper) {\n \/\/ Non-greedy split on comma to grab section text into the length and\n \/\/ text parts.\n var regex = \/(.*?),(.*)\/;\n var parts = sectionText.match(regex);\n \/\/ The length is the part matched in the first set of parethesis.\n var length = parts[1].trim();\n \/\/ The length is the part matched in the second set of parethesis.\n var text = parts[2].trim();\n var content = helper(text, context);\n return Truncate.truncate(content, {\n length: 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 * @return {string}\n *\/\n Renderer.prototype.userDateHelper = function(context, sectionText, helper) {\n \/\/ Non-greedy split on comma to grab the timestamp and format.\n var regex = \/(.*?),(.*)\/;\n var parts = sectionText.match(regex);\n var timestamp = helper(parts[1].trim(), context);\n var format = helper(parts[2].trim(), context);\n var 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 * @return {Function} To be set in the context\n *\/\n Renderer.prototype.addHelperFunction = function(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 var originalHelpers = disallowedNestedHelpers.reduce(function(carry, name) {\n if (context.hasOwnProperty(name)) {\n carry[name] = context[name];\n }\n\n return carry;\n }, {});\n\n disallowedNestedHelpers.forEach(function(helperName) {\n context[helperName] = function() {\n return '';\n };\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 var 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 (var 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 Renderer.prototype.addHelpers = function(context, themeName) {\n this.currentThemeName = themeName;\n this.requiredStrings = [];\n this.requiredJS = [];\n context.uniqid = (uniqInstances++);\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 * @return {string}\n *\/\n Renderer.prototype.getJS = function() {\n var js = '';\n if (this.requiredJS.length > 0) {\n js = this.requiredJS.join(\";\\n\");\n }\n\n return js;\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 {Array} strings The strings to replace with.\n * @return {String} The treated content.\n *\/\n Renderer.prototype.treatStringsInContent = function(content, strings) {\n var pattern = \/\\[\\[_(s|c)\\d+\\]\\]\/,\n treated,\n index,\n strIndex,\n walker,\n char,\n strFinal,\n isClean;\n\n do {\n treated = '';\n index = content.search(pattern);\n while (index > -1) {\n\n \/\/ Copy the part prior to the placeholder to the treated string.\n treated += content.substring(0, index);\n content = content.substr(index);\n isClean = content[3] == 'c';\n strIndex = '';\n walker = 4; \/\/ 4 is the length of either '[[_s' or '[[_c'.\n\n \/\/ Walk the characters to manually extract the index of the string from the placeholder.\n char = content.substr(walker, 1);\n do {\n strIndex += char;\n walker++;\n char = content.substr(walker, 1);\n } while (char != ']');\n\n \/\/ Get the string, add it to the treated result, and remove the placeholder from the content to treat.\n strFinal = strings[parseInt(strIndex, 10)];\n if (typeof strFinal === 'undefined') {\n Log.debug('Could not find string for pattern [[_' + (isClean ? 'c' : 's') + strIndex + ']].');\n strFinal = '';\n }\n if (isClean) {\n strFinal = mustache.escape(strFinal);\n }\n treated += strFinal;\n content = content.substr(6 + strIndex.length); \/\/ 6 is the length of the placeholder without the index.\n \/\/ That's either '[[_s]]' or '[[_c]]'.\n\n \/\/ Find the next placeholder.\n index = content.search(pattern);\n }\n\n \/\/ The content becomes the treated part with the rest of the content.\n content = treated + content;\n\n \/\/ Check if we need to walk the content again, in case strings contained placeholders.\n index = content.search(pattern);\n\n } while (index > -1);\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 * @return {String} The treated content.\n *\/\n Renderer.prototype.treatDatesInContent = function(content, dates) {\n dates.forEach(function(date, index) {\n var key = '\\\\[\\\\[_t_' + index + '\\\\]\\\\]';\n var re = new RegExp(key, 'g');\n content = content.replace(re, date);\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} templateSource 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 * @return {Promise} object\n *\/\n Renderer.prototype.doRender = function(templateSource, context, themeName) {\n this.currentThemeName = themeName;\n var iconTemplate = iconSystem.getTemplateName();\n\n var pendingPromise = new Pending('core\/templates:doRender');\n return this.getTemplate(iconTemplate).then(function() {\n this.addHelpers(context, themeName);\n var result = mustache.render(templateSource, context, this.partialHelper.bind(this));\n return $.Deferred().resolve(result.trim(), this.getJS()).promise();\n }.bind(this))\n .then(function(html, js) {\n if (this.requiredStrings.length > 0) {\n return str.get_strings(this.requiredStrings).then(function(strings) {\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, strings),\n format: this.treatStringsInContent(date.format, strings)\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, strings);\n js = this.treatStringsInContent(js, strings);\n return $.Deferred().resolve(html, js).promise();\n }.bind(this));\n }\n\n return $.Deferred().resolve(html, js).promise();\n }.bind(this))\n .then(function(html, js) {\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 return UserDate.get(this.requiredDates).then(function(dates) {\n html = this.treatDatesInContent(html, dates);\n js = this.treatDatesInContent(js, dates);\n return $.Deferred().resolve(html, js).promise();\n }.bind(this));\n }\n\n return $.Deferred().resolve(html, js).promise();\n }.bind(this))\n .then(function(html, js) {\n pendingPromise.resolve();\n return $.Deferred().resolve(html, js).promise();\n });\n };\n\n \/**\n * Execute a block of JS returned from a template.\n * Call this AFTER adding the template HTML into the DOM so the nodes can be found.\n *\n * @method runTemplateJS\n * @param {string} source - A block of javascript.\n *\/\n var runTemplateJS = function(source) {\n if (source.trim() !== '') {\n var newscript = $('