Merge branch 'MDL-49152-master' of git://github.com/damyon/moodle

This commit is contained in:
Dan Poltawski 2015-03-16 14:26:15 +00:00
commit f4e1913530
66 changed files with 6532 additions and 33 deletions

View File

@ -1 +1 @@
define(["jquery","core/config"],function(a,b){var c=function(a){var b,c,d=this,e=null,f=0;for(f=0;f<d.length;f++){if(b=d[f],c=a[f],"undefined"==typeof c){e=new Error("missing response");break}if(c.error!==!1){e=c.exception;break}b.deferred.resolve(c.data)}if(null!==e)for(;f<d.length;f++)b=d[f],b.deferred.reject(e)},d=function(a,b){var c=this,d=0;for(d=0;d<c.length;d++){var e=c[d];"undefined"!=typeof e.fail&&e.deferred.reject(b)}};return{call:function(e){var f,g=[],h=[];for(f=0;f<e.length;f++){var i=e[f];g.push({index:f,methodname:i.methodname,args:i.args}),i.deferred=a.Deferred(),h.push(i.deferred.promise()),"undefined"!=typeof i.done&&i.deferred.done(i.done),"undefined"!=typeof i.fail&&i.deferred.fail(i.fail),i.index=f}g=JSON.stringify(g);var j={type:"POST",data:g,context:e,dataType:"json",processData:!1};return a.ajax(b.wwwroot+"/lib/ajax/service.php",j).done(c).fail(d),h}}});
define(["jquery","core/config"],function(a,b){var c=function(a){var b,c,d=this,e=null,f=0;for(f=0;f<d.length;f++){if(b=d[f],c=a[f],"undefined"==typeof c){e=new Error("missing response");break}if(c.error!==!1){e=c.exception;break}b.deferred.resolve(c.data)}if(null!==e)for(;f<d.length;f++)b=d[f],b.deferred.reject(e)},d=function(a,b){var c=this,d=0;for(d=0;d<c.length;d++){var e=c[d];"undefined"!=typeof e.fail&&e.deferred.reject(b)}};return{call:function(e,f){var g,h=[],i=[];for("undefined"==typeof f&&(f=!0),g=0;g<e.length;g++){var j=e[g];h.push({index:g,methodname:j.methodname,args:j.args}),j.deferred=a.Deferred(),i.push(j.deferred.promise()),"undefined"!=typeof j.done&&j.deferred.done(j.done),"undefined"!=typeof j.fail&&j.deferred.fail(j.fail),j.index=g}h=JSON.stringify(h);var k={type:"POST",data:h,context:e,dataType:"json",processData:!1,async:f};return f?a.ajax(b.wwwroot+"/lib/ajax/service.php",k).done(c).fail(d):(k.success=c,k.error=d,a.ajax(b.wwwroot+"/lib/ajax/service.php",k)),i}}});

1
lib/amd/build/mustache.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(a,b){"object"==typeof exports&&exports?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):b(a.Mustache={})}(this,function(a){function b(a){return"function"==typeof a}function c(a){return a.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function d(a,b){return o.call(a,b)}function e(a){return!d(p,a)}function f(a){return String(a).replace(/[&<>"'\/]/g,function(a){return q[a]})}function g(b,d){function f(){if(w&&!x)for(;q.length;)delete p[q.pop()];else q=[];w=!1,x=!1}function g(a){if("string"==typeof a&&(a=a.split(s,2)),!n(a)||2!==a.length)throw new Error("Invalid tags: "+a);k=new RegExp(c(a[0])+"\\s*"),l=new RegExp("\\s*"+c(a[1])),m=new RegExp("\\s*"+c("}"+a[1]))}if(!b)return[];var k,l,m,o=[],p=[],q=[],w=!1,x=!1;g(d||a.tags);for(var y,z,A,B,C,D,E=new j(b);!E.eos();){if(y=E.pos,A=E.scanUntil(k))for(var F=0,G=A.length;G>F;++F)B=A.charAt(F),e(B)?q.push(p.length):x=!0,p.push(["text",B,y,y+1]),y+=1,"\n"===B&&f();if(!E.scan(k))break;if(w=!0,z=E.scan(v)||"name",E.scan(r),"="===z?(A=E.scanUntil(t),E.scan(t),E.scanUntil(l)):"{"===z?(A=E.scanUntil(m),E.scan(u),E.scanUntil(l),z="&"):A=E.scanUntil(l),!E.scan(l))throw new Error("Unclosed tag at "+E.pos);if(C=[z,A,y,E.pos],p.push(C),"#"===z||"^"===z)o.push(C);else if("/"===z){if(D=o.pop(),!D)throw new Error('Unopened section "'+A+'" at '+y);if(D[1]!==A)throw new Error('Unclosed section "'+D[1]+'" at '+y)}else"name"===z||"{"===z||"&"===z?x=!0:"="===z&&g(A)}if(D=o.pop())throw new Error('Unclosed section "'+D[1]+'" at '+E.pos);return i(h(p))}function h(a){for(var b,c,d=[],e=0,f=a.length;f>e;++e)b=a[e],b&&("text"===b[0]&&c&&"text"===c[0]?(c[1]+=b[1],c[3]=b[3]):(d.push(b),c=b));return d}function i(a){for(var b,c,d=[],e=d,f=[],g=0,h=a.length;h>g;++g)switch(b=a[g],b[0]){case"#":case"^":e.push(b),f.push(b),e=b[4]=[];break;case"/":c=f.pop(),c[5]=b[2],e=f.length>0?f[f.length-1][4]:d;break;default:e.push(b)}return d}function j(a){this.string=a,this.tail=a,this.pos=0}function k(a,b){this.view=null==a?{}:a,this.cache={".":this.view},this.parent=b}function l(){this.cache={}}var m=Object.prototype.toString,n=Array.isArray||function(a){return"[object Array]"===m.call(a)},o=RegExp.prototype.test,p=/\S/,q={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;"},r=/\s*/,s=/\s+/,t=/\s*=/,u=/\s*\}/,v=/#|\^|\/|>|\{|&|=|!/;j.prototype.eos=function(){return""===this.tail},j.prototype.scan=function(a){var b=this.tail.match(a);if(!b||0!==b.index)return"";var c=b[0];return this.tail=this.tail.substring(c.length),this.pos+=c.length,c},j.prototype.scanUntil=function(a){var b,c=this.tail.search(a);switch(c){case-1:b=this.tail,this.tail="";break;case 0:b="";break;default:b=this.tail.substring(0,c),this.tail=this.tail.substring(c)}return this.pos+=b.length,b},k.prototype.push=function(a){return new k(a,this)},k.prototype.lookup=function(a){var c,d=this.cache;if(a in d)c=d[a];else{for(var e,f,g=this;g;){if(a.indexOf(".")>0)for(c=g.view,e=a.split("."),f=0;null!=c&&f<e.length;)c=c[e[f++]];else"object"==typeof g.view&&(c=g.view[a]);if(null!=c)break;g=g.parent}d[a]=c}return b(c)&&(c=c.call(this.view)),c},l.prototype.clearCache=function(){this.cache={}},l.prototype.parse=function(a,b){var c=this.cache,d=c[a];return null==d&&(d=c[a]=g(a,b)),d},l.prototype.render=function(a,b,c){var d=this.parse(a),e=b instanceof k?b:new k(b);return this.renderTokens(d,e,c,a)},l.prototype.renderTokens=function(c,d,e,f){function g(a){return k.render(a,d,e)}for(var h,i,j="",k=this,l=0,m=c.length;m>l;++l)switch(h=c[l],h[0]){case"#":if(i=d.lookup(h[1]),!i)continue;if(n(i))for(var o=0,p=i.length;p>o;++o)j+=this.renderTokens(h[4],d.push(i[o]),e,f);else if("object"==typeof i||"string"==typeof i)j+=this.renderTokens(h[4],d.push(i),e,f);else if(b(i)){if("string"!=typeof f)throw new Error("Cannot use higher-order sections without the original template");i=i.call(d.view,f.slice(h[3],h[5]),g),null!=i&&(j+=i)}else j+=this.renderTokens(h[4],d,e,f);break;case"^":i=d.lookup(h[1]),(!i||n(i)&&0===i.length)&&(j+=this.renderTokens(h[4],d,e,f));break;case">":if(!e)continue;i=b(e)?e(h[1]):e[h[1]],null!=i&&(j+=this.renderTokens(this.parse(i),d,e,i));break;case"&":i=d.lookup(h[1]),null!=i&&(j+=i);break;case"name":i=d.lookup(h[1]),null!=i&&(j+=a.escape(i));break;case"text":j+=h[1]}return j},a.name="mustache.js",a.version="1.0.0",a.tags=["{{","}}"];var w=new l;a.clearCache=function(){return w.clearCache()},a.parse=function(a,b){return w.parse(a,b)},a.render=function(a,b,c){return w.render(a,b,c)},a.to_html=function(c,d,e,f){var g=a.render(c,d,e);return b(f)?void f(g):g},a.escape=f,a.Scanner=j,a.Context=k,a.Writer=l});

1
lib/amd/build/notification.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["core/yui"],function(a){return{alert:function(b,c,d){a.use("moodle-core-notification-alert",function(){var a=new M.core.alert({title:b,message:c,yesLabel:d});a.show()})},confirm:function(b,c,d,e,f){a.use("moodle-core-notification-confirm",function(){var a=new M.core.confirm({title:b,question:c,yesLabel:d,noLabel:e});a.on("complete-yes",function(){f()}),a.show()})},exception:function(b){b.backtrace&&(b.lineNumber=b.backtrace[0].line,b.fileName=b.backtrace[0].file,b.fileName="..."+b.fileName.substr(b.fileName.length-20),b.stack=b.debuginfo,b.name=b.errorcode),a.use("moodle-core-notification-exception",function(){var a=new M.core.exception(b);a.show()})}}});

1
lib/amd/build/str.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["jquery","core/ajax"],function(a,b){return{get_string:function(b,c,d,e){var f=a.Deferred(),g=this.get_strings([{key:b,component:c,param:d,lang:e}]);return g.done(function(a){f.resolve(a[0])}).fail(function(a){f.reject(a)}),f.promise()},get_strings:function(c){var d,e=a.Deferred(),f=[],g=0,h=!1;for(g=0;g<c.length;g++)d=c[g],("undefined"==typeof M.str[d.component]||"undefined"==typeof M.str[d.component][d.key])&&(h=!0);if(h){var i=[];for(g=0;g<c.length;g++)d=c[g],"undefined"==typeof d.lang&&(d.lang=a("html").attr("lang")),i.push({methodname:"core_get_string",args:{stringid:d.key,component:d.component,lang:d.lang,stringparams:[]}});var j=b.call(i);a.when.apply(null,j).done(function(){var a=0;for(a=0;a<arguments.length;a++)d=c[a],"undefined"==typeof M.str[d.component]&&(M.str[d.component]=[]),M.str[d.component][d.key]=arguments[a],f[a]=M.util.get_string(d.key,d.component,d.param).trim();e.resolve(f)}).fail(function(a){e.reject(a)})}else{for(g=0;g<c.length;g++)d=c[g],f[g]=M.util.get_string(d.key,d.component,d.param);e.resolve(f)}return e.promise()}}});

1
lib/amd/build/templates.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config"],function(a,b,c,d,e,f,g){var h={},i=[],j=[],k=1,l="",m=function(b){var c,d=b.split(","),e="",g="",i="";d.length>0&&(e=d.shift().trim()),d.length>0&&(g=d.shift().trim()),d.length>0&&(i=d.join(",").trim());var j=f.imageUrl(e,g),k={attributes:[{name:"src",value:j},{name:"alt",value:i},{name:"class",value:"smallicon"}]},m=h[l+"/core/pix_icon"];return c=a.render(m,k,n),c.trim()},n=function(a){var b="";return t(a,!1).done(function(a){b=a}).fail(e.exception),b},o=function(a,b){return j.push(b(a,this)),""},p=function(a,b){var c=a.split(","),d="",e="",f="";c.length>0&&(d=c.shift().trim()),c.length>0&&(e=c.shift().trim()),c.length>0&&(f=c.join(",").trim()),""!==f&&(f=b(f,this)),0===f.indexOf("{")&&0!==f.indexOf("{{")&&(f=JSON.parse(f));var g=i.length;return i.push({key:d,component:e,param:f}),"{{_s"+g+"}}"},q=function(a,b){l=b,i=[],j=[],a.uniqid=k++,a.str=function(){return p},a.pix=function(){return m},a.js=function(){return o},a.globals={config:g},a.currentTheme=b},r=function(a){var b="";j.length>0&&(b=j.join(";\n"));var c=0;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);return b},s=function(c,e,f){var g=b.Deferred();l=f;var h=t("core/pix_icon",!0);return h.done(function(){q(e,f);var b="";try{b=a.render(c,e,n)}catch(h){g.reject(h)}i.length>0?d.get_strings(i).done(function(a){var c;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);g.resolve(b.trim(),r(a))}).fail(function(a){g.reject(a)}):g.resolve(b.trim(),r([]))}).fail(function(a){g.reject(a)}),g.promise()},t=function(a,d){var e=b.Deferred(),f=a.split("/"),g=f.shift(),i=f.shift(),j=l+"/"+a;if(j in h)e.resolve(h[j]);else{var k=c.call([{methodname:"core_output_load_template",args:{component:g,template:i,themename:l}}],d);k[0].done(function(a){h[j]=a,e.resolve(a)}).fail(function(a){e.reject(a)})}return e.promise()};return{render:function(a,c,d){var e=b.Deferred();"undefined"==typeof d&&(d=g.theme),l=d;var f=t(a,!0);return f.done(function(a){var b=s(a,c,d);b.done(function(a,b){e.resolve(a,b)}).fail(function(a){e.reject(a)})}).fail(function(a){e.reject(a)}),e.promise()},runTemplateJS:function(a){var c=b("<script>").attr("type","text/javascript").html(a);b("head").append(c)}}});

1
lib/amd/build/url.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["core/config"],function(a){return{fileUrl:function(b,c){var d=a.wwwroot+b;return"/"!=c.charAt(0)&&(c="/"+c),d+=a.slasharguments?c:"?file="+encodeURIComponent(c)},relativeUrl:function(b){if(0===b.indexOf("http:")||0===b.indexOf("https:")||b.indexOf("://"))throw new Error("relativeUrl function does not accept absolute urls");return"/"!=b.charAt(0)&&(b="/"+b),"admin"!==a.admin&&(b=b.replace(/^\/admin\//,"/"+a.admin+"/")),a.wwwroot+b},imageUrl:function(a,b){return M.util.image_url(a,b)}}});

1
lib/amd/build/yui.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(function(){return Y});

View File

@ -19,9 +19,11 @@
* In addition, it can batch multiple requests and return multiple responses.
*
* @module core/ajax
* @class ajax
* @package core
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(['jquery', 'core/config'], function($, config) {
@ -29,6 +31,8 @@ define(['jquery', 'core/config'], function($, config) {
* Success handler. Called when the ajax call succeeds. Checks each response and
* resolves or rejects the deferred from that request.
*
* @method requestSuccess
* @private
* @param {Object[]} responses Array of responses containing error, exception and data attributes.
*/
var requestSuccess = function(responses) {
@ -70,6 +74,8 @@ define(['jquery', 'core/config'], function($, config) {
/**
* Fail handler. Called when the ajax call fails. Rejects all deferreds.
*
* @method requestFail
* @private
* @param {jqXHR} jqXHR The ajax object.
* @param {string} textStatus The status string.
*/
@ -91,15 +97,23 @@ define(['jquery', 'core/config'], function($, config) {
// Public variables and functions.
/**
* Make a series of ajax requests and return all the responses.
*
* @method call
* @param {Object[]} Array of requests with each containing methodname and args properties.
* done and fail callbacks can be set for each element in the array, or the
* can be attached to the promises returned by this function.
* @return {Promise{}} Array of promises that will be resolved when the ajax call returns.
* @param {Boolean} async Optional, defaults to true.
* If false - this function will not return until the promises are resolved.
* @return {Promise[]} Array of promises that will be resolved when the ajax call returns.
*/
call: function(requests) {
call: function(requests, async) {
var ajaxRequestData = [],
i,
promises = [];
if (typeof async === "undefined") {
async = true;
}
for (i = 0; i < requests.length; i++) {
var request = requests[i];
ajaxRequestData.push({
@ -126,12 +140,20 @@ define(['jquery', 'core/config'], function($, config) {
data: ajaxRequestData,
context: requests,
dataType: 'json',
processData: false
processData: false,
async: async
};
$.ajax(config.wwwroot + '/lib/ajax/service.php', settings)
.done(requestSuccess)
.fail(requestFail);
// Jquery deprecated done and fail with async=false so we need to do this 2 ways.
if (async) {
$.ajax(config.wwwroot + '/lib/ajax/service.php', settings)
.done(requestSuccess)
.fail(requestFail);
} else {
settings.success = requestSuccess;
settings.error = requestFail;
$.ajax(config.wwwroot + '/lib/ajax/service.php', settings);
}
return promises;
}

View File

@ -17,9 +17,11 @@
* Expose the M.cfg global variable.
*
* @module core/config
* @class config
* @package core
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(function() {

View File

@ -22,5 +22,6 @@
* @package core
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(function() { });

608
lib/amd/src/mustache.js Normal file
View File

@ -0,0 +1,608 @@
// The MIT License
//
// Copyright (c) 2009 Chris Wanstrath (Ruby)
// Copyright (c) 2010-2014 Jan Lehnardt (JavaScript)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Description of import into Moodle:
// Download from https://github.com/janl/mustache.js/releases
// Copy mustache.js into lib/amd/src/ in Moodle folder.
// Add the license as a comment to the file and these instructions.
/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
/* jshint ignore:start */
(function (global, factory) {
if (typeof exports === "object" && exports) {
factory(exports); // CommonJS
} else if (typeof define === "function" && define.amd) {
define(['exports'], factory); // AMD
} else {
factory(global.Mustache = {}); // <script>
}
}(this, function (mustache) {
var Object_toString = Object.prototype.toString;
var isArray = Array.isArray || function (object) {
return Object_toString.call(object) === '[object Array]';
};
function isFunction(object) {
return typeof object === 'function';
}
function escapeRegExp(string) {
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
var RegExp_test = RegExp.prototype.test;
function testRegExp(re, string) {
return RegExp_test.call(re, string);
}
var nonSpaceRe = /\S/;
function isWhitespace(string) {
return !testRegExp(nonSpaceRe, string);
}
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
var whiteRe = /\s*/;
var spaceRe = /\s+/;
var equalsRe = /\s*=/;
var curlyRe = /\s*\}/;
var tagRe = /#|\^|\/|>|\{|&|=|!/;
/**
* Breaks up the given `template` string into a tree of tokens. If the `tags`
* argument is given here it must be an array with two string values: the
* opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
* course, the default is to use mustaches (i.e. mustache.tags).
*
* A token is an array with at least 4 elements. The first element is the
* mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
* did not contain a symbol (i.e. {{myValue}}) this element is "name". For
* all text that appears outside a symbol this element is "text".
*
* The second element of a token is its "value". For mustache tags this is
* whatever else was inside the tag besides the opening symbol. For text tokens
* this is the text itself.
*
* The third and fourth elements of the token are the start and end indices,
* respectively, of the token in the original template.
*
* Tokens that are the root node of a subtree contain two more elements: 1) an
* array of tokens in the subtree and 2) the index in the original template at
* which the closing tag for that section begins.
*/
function parseTemplate(template, tags) {
if (!template)
return [];
var sections = []; // Stack to hold section tokens
var tokens = []; // Buffer to hold the tokens
var spaces = []; // Indices of whitespace tokens on the current line
var hasTag = false; // Is there a {{tag}} on the current line?
var nonSpace = false; // Is there a non-space char on the current line?
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
function stripSpace() {
if (hasTag && !nonSpace) {
while (spaces.length)
delete tokens[spaces.pop()];
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
}
var openingTagRe, closingTagRe, closingCurlyRe;
function compileTags(tags) {
if (typeof tags === 'string')
tags = tags.split(spaceRe, 2);
if (!isArray(tags) || tags.length !== 2)
throw new Error('Invalid tags: ' + tags);
openingTagRe = new RegExp(escapeRegExp(tags[0]) + '\\s*');
closingTagRe = new RegExp('\\s*' + escapeRegExp(tags[1]));
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tags[1]));
}
compileTags(tags || mustache.tags);
var scanner = new Scanner(template);
var start, type, value, chr, token, openSection;
while (!scanner.eos()) {
start = scanner.pos;
// Match any text between tags.
value = scanner.scanUntil(openingTagRe);
if (value) {
for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push([ 'text', chr, start, start + 1 ]);
start += 1;
// Check for whitespace on the current line.
if (chr === '\n')
stripSpace();
}
}
// Match the opening tag.
if (!scanner.scan(openingTagRe))
break;
hasTag = true;
// Get the tag type.
type = scanner.scan(tagRe) || 'name';
scanner.scan(whiteRe);
// Get the tag value.
if (type === '=') {
value = scanner.scanUntil(equalsRe);
scanner.scan(equalsRe);
scanner.scanUntil(closingTagRe);
} else if (type === '{') {
value = scanner.scanUntil(closingCurlyRe);
scanner.scan(curlyRe);
scanner.scanUntil(closingTagRe);
type = '&';
} else {
value = scanner.scanUntil(closingTagRe);
}
// Match the closing tag.
if (!scanner.scan(closingTagRe))
throw new Error('Unclosed tag at ' + scanner.pos);
token = [ type, value, start, scanner.pos ];
tokens.push(token);
if (type === '#' || type === '^') {
sections.push(token);
} else if (type === '/') {
// Check section nesting.
openSection = sections.pop();
if (!openSection)
throw new Error('Unopened section "' + value + '" at ' + start);
if (openSection[1] !== value)
throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
} else if (type === 'name' || type === '{' || type === '&') {
nonSpace = true;
} else if (type === '=') {
// Set the tags for the next time around.
compileTags(value);
}
}
// Make sure there are no open sections when we're done.
openSection = sections.pop();
if (openSection)
throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
return nestTokens(squashTokens(tokens));
}
/**
* Combines the values of consecutive text tokens in the given `tokens` array
* to a single token.
*/
function squashTokens(tokens) {
var squashedTokens = [];
var token, lastToken;
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
token = tokens[i];
if (token) {
if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
lastToken[1] += token[1];
lastToken[3] = token[3];
} else {
squashedTokens.push(token);
lastToken = token;
}
}
}
return squashedTokens;
}
/**
* Forms the given array of `tokens` into a nested tree structure where
* tokens that represent a section have two additional items: 1) an array of
* all tokens that appear in that section and 2) the index in the original
* template that represents the end of that section.
*/
function nestTokens(tokens) {
var nestedTokens = [];
var collector = nestedTokens;
var sections = [];
var token, section;
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
token = tokens[i];
switch (token[0]) {
case '#':
case '^':
collector.push(token);
sections.push(token);
collector = token[4] = [];
break;
case '/':
section = sections.pop();
section[5] = token[2];
collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
break;
default:
collector.push(token);
}
}
return nestedTokens;
}
/**
* A simple string scanner that is used by the template parser to find
* tokens in template strings.
*/
function Scanner(string) {
this.string = string;
this.tail = string;
this.pos = 0;
}
/**
* Returns `true` if the tail is empty (end of string).
*/
Scanner.prototype.eos = function () {
return this.tail === "";
};
/**
* Tries to match the given regular expression at the current position.
* Returns the matched text if it can match, the empty string otherwise.
*/
Scanner.prototype.scan = function (re) {
var match = this.tail.match(re);
if (!match || match.index !== 0)
return '';
var string = match[0];
this.tail = this.tail.substring(string.length);
this.pos += string.length;
return string;
};
/**
* Skips all text until the given regular expression can be matched. Returns
* the skipped string, which is the entire tail if no match can be made.
*/
Scanner.prototype.scanUntil = function (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = "";
break;
case 0:
match = "";
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
/**
* Represents a rendering context by wrapping a view object and
* maintaining a reference to the parent context.
*/
function Context(view, parentContext) {
this.view = view == null ? {} : view;
this.cache = { '.': this.view };
this.parent = parentContext;
}
/**
* Creates a new context using the given view with this context
* as the parent.
*/
Context.prototype.push = function (view) {
return new Context(view, this);
};
/**
* Returns the value of the given name in this context, traversing
* up the context hierarchy if the value is absent in this context's view.
*/
Context.prototype.lookup = function (name) {
var cache = this.cache;
var value;
if (name in cache) {
value = cache[name];
} else {
var context = this, names, index;
while (context) {
if (name.indexOf('.') > 0) {
value = context.view;
names = name.split('.');
index = 0;
while (value != null && index < names.length)
value = value[names[index++]];
} else if (typeof context.view == 'object') {
value = context.view[name];
}
if (value != null)
break;
context = context.parent;
}
cache[name] = value;
}
if (isFunction(value))
value = value.call(this.view);
return value;
};
/**
* A Writer knows how to take a stream of tokens and render them to a
* string, given a context. It also maintains a cache of templates to
* avoid the need to parse the same template twice.
*/
function Writer() {
this.cache = {};
}
/**
* Clears all cached templates in this writer.
*/
Writer.prototype.clearCache = function () {
this.cache = {};
};
/**
* Parses and caches the given `template` and returns the array of tokens
* that is generated from the parse.
*/
Writer.prototype.parse = function (template, tags) {
var cache = this.cache;
var tokens = cache[template];
if (tokens == null)
tokens = cache[template] = parseTemplate(template, tags);
return tokens;
};
/**
* High-level method that is used to render the given `template` with
* the given `view`.
*
* The optional `partials` argument may be an object that contains the
* names and templates of partials that are used in the template. It may
* also be a function that is used to load partial templates on the fly
* that takes a single argument: the name of the partial.
*/
Writer.prototype.render = function (template, view, partials) {
var tokens = this.parse(template);
var context = (view instanceof Context) ? view : new Context(view);
return this.renderTokens(tokens, context, partials, template);
};
/**
* Low-level method that renders the given array of `tokens` using
* the given `context` and `partials`.
*
* Note: The `originalTemplate` is only ever used to extract the portion
* of the original template that was contained in a higher-order section.
* If the template doesn't use higher-order sections, this argument may
* be omitted.
*/
Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {
var buffer = '';
// This function is used to render an arbitrary template
// in the current context by higher-order sections.
var self = this;
function subRender(template) {
return self.render(template, context, partials);
}
var token, value;
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
token = tokens[i];
switch (token[0]) {
case '#':
value = context.lookup(token[1]);
if (!value)
continue;
if (isArray(value)) {
for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
}
} else if (typeof value === 'object' || typeof value === 'string') {
buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
} else if (isFunction(value)) {
if (typeof originalTemplate !== 'string')
throw new Error('Cannot use higher-order sections without the original template');
// Extract the portion of the original template that the section contains.
value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
if (value != null)
buffer += value;
} else {
buffer += this.renderTokens(token[4], context, partials, originalTemplate);
}
break;
case '^':
value = context.lookup(token[1]);
// Use JavaScript's definition of falsy. Include empty arrays.
// See https://github.com/janl/mustache.js/issues/186
if (!value || (isArray(value) && value.length === 0))
buffer += this.renderTokens(token[4], context, partials, originalTemplate);
break;
case '>':
if (!partials)
continue;
value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
if (value != null)
buffer += this.renderTokens(this.parse(value), context, partials, value);
break;
case '&':
value = context.lookup(token[1]);
if (value != null)
buffer += value;
break;
case 'name':
value = context.lookup(token[1]);
if (value != null)
buffer += mustache.escape(value);
break;
case 'text':
buffer += token[1];
break;
}
}
return buffer;
};
mustache.name = "mustache.js";
mustache.version = "1.0.0";
mustache.tags = [ "{{", "}}" ];
// All high-level mustache.* functions use this writer.
var defaultWriter = new Writer();
/**
* Clears all cached templates in the default writer.
*/
mustache.clearCache = function () {
return defaultWriter.clearCache();
};
/**
* Parses and caches the given template in the default writer and returns the
* array of tokens it contains. Doing this ahead of time avoids the need to
* parse templates on the fly as they are rendered.
*/
mustache.parse = function (template, tags) {
return defaultWriter.parse(template, tags);
};
/**
* Renders the `template` with the given `view` and `partials` using the
* default writer.
*/
mustache.render = function (template, view, partials) {
return defaultWriter.render(template, view, partials);
};
// This is here for backwards compatibility with 0.4.x.
mustache.to_html = function (template, view, partials, send) {
var result = mustache.render(template, view, partials);
if (isFunction(send)) {
send(result);
} else {
return result;
}
};
// Export the escaping function so that the user may override it.
// See https://github.com/janl/mustache.js/issues/244
mustache.escape = escapeHtml;
// Export these mainly for testing, but also for advanced usage.
mustache.Scanner = Scanner;
mustache.Context = Context;
mustache.Writer = Writer;
}));
/* jshint ignore:end */

105
lib/amd/src/notification.js Normal file
View File

@ -0,0 +1,105 @@
// 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/>.
/**
* Wrapper for the YUI M.core.notification class. Allows us to
* use the YUI version in AMD code until it is replaced.
*
* @module core/notification
* @class notification
* @package core
* @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/yui'], function(Y) {
// Private variables and functions.
return /** @alias module:core/notification */ {
// Public variables and functions.
/**
* Wrap M.core.alert.
*
* @method alert
* @param {string} title
* @param {string} message
* @param {string} yesLabel
*/
alert: function(title, message, yesLabel) {
// Here we are wrapping YUI. This allows us to start transitioning, but
// wait for a good alternative without having inconsistent dialogues.
Y.use('moodle-core-notification-alert', function () {
var alert = new M.core.alert({
title : title,
message : message,
yesLabel: yesLabel
});
alert.show();
});
},
/**
* Wrap M.core.confirm.
*
* @method confirm
* @param {string} title
* @param {string} question
* @param {string} yesLabel
* @param {string} noLabel
* @param {function} callback
*/
confirm: function(title, question, yesLabel, noLabel, callback) {
// Here we are wrapping YUI. This allows us to start transitioning, but
// wait for a good alternative without having inconsistent dialogues.
Y.use('moodle-core-notification-confirm', function () {
var modal = new M.core.confirm({
title : title,
question : question,
yesLabel: yesLabel,
noLabel: noLabel
});
modal.on('complete-yes', function() {
callback();
});
modal.show();
});
},
/**
* Wrap M.core.exception.
*
* @method exception
* @param {Error} ex
*/
exception: function(ex) {
// Fudge some parameters.
if (ex.backtrace) {
ex.lineNumber = ex.backtrace[0].line;
ex.fileName = ex.backtrace[0].file;
ex.fileName = '...' + ex.fileName.substr(ex.fileName.length - 20);
ex.stack = ex.debuginfo;
ex.name = ex.errorcode;
}
Y.use('moodle-core-notification-exception', function () {
var modal = new M.core.exception(ex);
modal.show();
});
}
};
});

141
lib/amd/src/str.js Normal file
View File

@ -0,0 +1,141 @@
// 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/>.
/**
* Fetch and render language strings.
* Hooks into the old M.str global - but can also fetch missing strings on the fly.
*
* @module core/str
* @class str
* @package core
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(['jquery', 'core/ajax'], function($, ajax) {
return /** @alias module:core/str */ {
// Public variables and functions.
/**
* Return a promise object that will be resolved into a string eventually (maybe immediately).
*
* @method get_string
* @param {string} key The language string key
* @param {string} component The language string component
* @param {string} param The param for variable expansion in the string.
* @param {string} lang The users language - if not passed it is deduced.
* @return {Promise}
*/
get_string: function(key, component, param, lang) {
var deferred = $.Deferred();
var request = this.get_strings([{
key: key,
component: component,
param: param,
lang: lang
}]);
request.done(function(results) {
deferred.resolve(results[0]);
}).fail(function(ex) {
deferred.reject(ex);
});
return deferred.promise();
},
/**
* Make a batch request to load a set of strings
*
* @method get_strings
* @param {Object[]} requests Array of { key: key, component: component, param: param, lang: lang };
* See get_string for more info on these args.
* @return {Promise}
*/
get_strings: function(requests) {
var deferred = $.Deferred();
var results = [];
var i = 0;
var missing = false;
var request;
for (i = 0; i < requests.length; i++) {
request = requests[i];
if (typeof M.str[request.component] === "undefined" ||
typeof M.str[request.component][request.key] === "undefined") {
missing = true;
}
}
if (!missing) {
// We have all the strings already.
for (i = 0; i < requests.length; i++) {
request = requests[i];
results[i] = M.util.get_string(request.key, request.component, request.param);
}
deferred.resolve(results);
} else {
// Something is missing, we might as well load them all.
var ajaxrequests = [];
for (i = 0; i < requests.length; i++) {
request = requests[i];
if (typeof request.lang === "undefined") {
request.lang = $('html').attr('lang');
}
ajaxrequests.push({
methodname: 'core_get_string',
args: {
stringid: request.key,
component: request.component,
lang: request.lang,
stringparams: []
}
});
}
var deferreds = ajax.call(ajaxrequests);
$.when.apply(null, deferreds).done(
function() {
// Turn the list of arguments (unknown length) into a real array.
var i = 0;
for (i = 0; i < arguments.length; i++) {
request = requests[i];
// Cache all the string templates.
if (typeof M.str[request.component] === "undefined") {
M.str[request.component] = [];
}
M.str[request.component][request.key] = arguments[i];
// And set the results.
results[i] = M.util.get_string(request.key, request.component, request.param).trim();
}
deferred.resolve(results);
}
).fail(
function(ex) {
deferred.reject(ex);
}
);
}
return deferred.promise();
}
};
});

374
lib/amd/src/templates.js Normal file
View File

@ -0,0 +1,374 @@
// 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
* @package core
* @class 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'
],
function(mustache, $, ajax, str, notification, coreurl, config) {
// Private variables and functions.
/** @var {string[]} templateCache - Cache of already loaded templates */
var templateCache = {};
/** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template */
var requiredStrings = [];
/** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template */
var requiredJS = [];
/** @var {Number} uniqid Incrementing value that is changed for every call to render */
var uniqid = 1;
/** @var {String} themeName for the current render */
var currentThemeName = '';
/**
* Render image icons.
*
* @method pixHelper
* @private
* @param {string} sectionText The text to parse arguments from.
* @return {string}
*/
var pixHelper = function(sectionText) {
var parts = sectionText.split(',');
var key = '';
var component = '';
var text = '';
var result;
if (parts.length > 0) {
key = parts.shift().trim();
}
if (parts.length > 0) {
component = parts.shift().trim();
}
if (parts.length > 0) {
text = parts.join(',').trim();
}
var url = coreurl.imageUrl(key, component);
var templatecontext = {
attributes: [
{ name: 'src', value: url},
{ name: 'alt', value: text},
{ name: 'class', value: 'smallicon'}
]
};
// We forced loading of this early, so it will be in the cache.
var template = templateCache[currentThemeName + '/core/pix_icon'];
result = mustache.render(template, templatecontext, partialHelper);
return result.trim();
};
/**
* Load a partial from the cache or ajax.
*
* @method partialHelper
* @private
* @param {string} name The partial name to load.
* @return {string}
*/
var partialHelper = function(name) {
var template = '';
getTemplate(name, false).done(
function(source) {
template = source;
}
).fail(notification.exception);
return template;
};
/**
* Render blocks of javascript and save them in an array.
*
* @method jsHelper
* @private
* @param {string} sectionText The text to save as a js block.
* @param {function} helper Used to render the block.
* @return {string}
*/
var jsHelper = function(sectionText, helper) {
requiredJS.push(helper(sectionText, this));
return '';
};
/**
* String helper used to render {{#str}}abd component { a : 'fish'}{{/str}}
* into a get_string call.
*
* @method stringHelper
* @private
* @param {string} sectionText The text to parse the arguments from.
* @param {function} helper Used to render subsections of the text.
* @return {string}
*/
var stringHelper = function(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();
}
if (param !== '') {
// Allow variable expansion in the param part only.
param = helper(param, this);
}
// Allow json formatted $a arguments.
if ((param.indexOf('{') === 0) && (param.indexOf('{{') !== 0)) {
param = JSON.parse(param);
}
var index = requiredStrings.length;
requiredStrings.push({key: key, component: component, param: param});
return '{{_s' + index + '}}';
};
/**
* 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.
*/
var addHelpers = function(context, themeName) {
currentThemeName = themeName;
requiredStrings = [];
requiredJS = [];
context.uniqid = uniqid++;
context.str = function() { return stringHelper; };
context.pix = function() { return pixHelper; };
context.js = function() { return jsHelper; };
context.globals = { config : config };
context.currentTheme = themeName;
};
/**
* Get all the JS blocks from the last rendered template.
*
* @method getJS
* @private
* @param {string[]} strings Replacement strings.
* @return {string}
*/
var getJS = function(strings) {
var js = '';
if (requiredJS.length > 0) {
js = requiredJS.join(";\n");
}
var i = 0;
for (i = 0; i < strings.length; i++) {
js = js.replace('{{_s' + i + '}}', strings[i]);
}
// Re-render to get the final strings.
return js;
};
/**
* 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
*/
var doRender = function(templateSource, context, themeName) {
var deferred = $.Deferred();
currentThemeName = themeName;
// Make sure we fetch this first.
var loadPixTemplate = getTemplate('core/pix_icon', true);
loadPixTemplate.done(
function() {
addHelpers(context, themeName);
var result = '';
try {
result = mustache.render(templateSource, context, partialHelper);
} catch (ex) {
deferred.reject(ex);
}
if (requiredStrings.length > 0) {
str.get_strings(requiredStrings).done(
function(strings) {
var i;
// 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).
for (i = 0; i < strings.length; i++) {
result = result.replace('{{_s' + i + '}}', strings[i]);
}
deferred.resolve(result.trim(), getJS(strings));
}
).fail(
function(ex) {
deferred.reject(ex);
}
);
} else {
deferred.resolve(result.trim(), getJS([]));
}
}
).fail(
function(ex) {
deferred.reject(ex);
}
);
return deferred.promise();
};
/**
* Load a template from the cache or ajax request.
*
* @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.
*/
var getTemplate = function(templateName, async) {
var deferred = $.Deferred();
var parts = templateName.split('/');
var component = parts.shift();
var name = parts.shift();
var searchKey = currentThemeName + '/' + templateName;
if (searchKey in templateCache) {
deferred.resolve(templateCache[searchKey]);
} else {
var promises = ajax.call([{
methodname: 'core_output_load_template',
args:{
component: component,
template: name,
themename: currentThemeName
}
}], async);
promises[0].done(
function (templateSource) {
templateCache[searchKey] = templateSource;
deferred.resolve(templateSource);
}
).fail(
function (ex) {
deferred.reject(ex);
}
);
}
return deferred.promise();
};
return /** @alias module:core/templates */ {
// Public variables and functions.
/**
* 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.
*/
render: function(templateName, context, themeName) {
var deferred = $.Deferred();
if (typeof (themeName) === "undefined") {
// System context by default.
themeName = config.theme;
}
currentThemeName = themeName;
var loadTemplate = getTemplate(templateName, true);
loadTemplate.done(
function(templateSource) {
var renderPromise = doRender(templateSource, context, themeName);
renderPromise.done(
function(result, js) {
deferred.resolve(result, js);
}
).fail(
function(ex) {
deferred.reject(ex);
}
);
}
).fail(
function(ex) {
deferred.reject(ex);
}
);
return deferred.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
* @private
* @param {string} source - A block of javascript.
*/
runTemplateJS: function(source) {
var newscript = $('<script>').attr('type','text/javascript').html(source);
$('head').append(newscript);
}
};
});

91
lib/amd/src/url.js Normal file
View File

@ -0,0 +1,91 @@
// 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/>.
/**
* URL utility functions.
*
* @module core/url
* @package core
* @class url
* @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/config'], function(config) {
return /** @alias module:core/url */ {
// Public variables and functions.
/**
* Generate a style tag referencing this sheet and add it to the head of the page.
*
* @method fileUrl
* @param {string} sheet The style sheet name. Must exist in the theme, or one of it's parents.
* @return {string}
*/
fileUrl: function(relativeScript, slashArg) {
var url = config.wwwroot + relativeScript;
// Force a /
if (slashArg.charAt(0) != '/') {
slashArg = '/' + slashArg;
}
if (config.slasharguments) {
url += slashArg;
} else {
url += '?file=' + encodeURIComponent(slashArg);
}
return url;
},
/**
* Take a path relative to the moodle basedir and do some fixing (see class moodle_url in php).
*
* @method relativeUrl
* @param {string} relativePath The path relative to the moodle basedir.
* @return {string}
*/
relativeUrl: function(relativePath) {
if (relativePath.indexOf('http:') === 0 || relativePath.indexOf('https:') === 0 || relativePath.indexOf('://')) {
throw new Error('relativeUrl function does not accept absolute urls');
}
// Fix non-relative paths;
if (relativePath.charAt(0) != '/') {
relativePath = '/' + relativePath;
}
// Fix admin urls.
if (config.admin !== 'admin') {
relativePath = relativePath.replace(/^\/admin\//, '/' + config.admin + '/');
}
return config.wwwroot + relativePath;
},
/**
* Wrapper for image_url function.
*
* @method imageUrl
* @param {string} imagename The image name (e.g. t/edit).
* @param {string} component The component (e.g. mod_feedback).
* @return {string}
*/
imageUrl: function(imagename, component) {
return M.util.image_url(imagename, component);
}
};
});

30
lib/amd/src/yui.js vendored Normal file
View File

@ -0,0 +1,30 @@
// 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/>.
/**
* Expose the global YUI variable. Note: This is only for scripts that are writing AMD
* wrappers for YUI functionality. This is not for plugins.
*
* @module core/yui
* @package core
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
define(function() {
// This module exposes only the global yui instance.
return /** @alias module:core/yui */ Y;
});

View File

@ -0,0 +1,133 @@
<?php
// 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/>.
/**
* Mustache helper to load strings from string_manager.
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
use external_api;
use external_function_parameters;
use external_value;
use core_component;
use moodle_exception;
use context_system;
use theme_config;
/**
* This class contains a list of webservice functions related to output.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class external extends external_api {
/**
* Returns description of load_template() parameters.
*
* @return external_function_parameters
*/
public static function load_template_parameters() {
return new external_function_parameters(
array('component' => new external_value(PARAM_COMPONENT, 'component containing the template'),
'template' => new external_value(PARAM_ALPHANUMEXT, 'name of the template'),
'themename' => new external_value(PARAM_ALPHANUMEXT, 'The current theme.'),
)
);
}
/**
* Can this function be called directly from ajax?
*
* @return boolean
* @since Moodle 2.9
*/
public static function load_template_is_allowed_from_ajax() {
return true;
}
/**
* Return a mustache template, and all the strings it requires.
*
* @param string $component The component that holds the template.
* @param string $templatename The name of the template.
* @param string $themename The name of the current theme.
* @return string the template
*/
public static function load_template($component, $template, $themename) {
global $DB, $CFG, $PAGE;
$params = self::validate_parameters(self::load_template_parameters(),
array('component' => $component,
'template' => $template,
'themename' => $themename));
$component = $params['component'];
$template = $params['template'];
$themename = $params['themename'];
// Check if this is a valid component.
$componentdir = core_component::get_component_directory($component);
if (empty($componentdir)) {
throw new moodle_exception('filenotfound', 'error');
}
// Places to look.
$candidates = array();
// Theme dir.
$root = $CFG->dirroot;
$themeconfig = theme_config::load($themename);
$candidate = "${root}/theme/${themename}/templates/${component}/${template}.mustache";
$candidates[] = $candidate;
// Theme parents dir.
foreach ($themeconfig->parents as $theme) {
$candidate = "${root}/theme/${theme}/templates/${component}/${template}.mustache";
$candidates[] = $candidate;
}
// Component dir.
$candidate = "${componentdir}/templates/${template}.mustache";
$candidates[] = $candidate;
$templatestr = false;
foreach ($candidates as $candidate) {
if (file_exists($candidate)) {
$templatestr = file_get_contents($candidate);
break;
}
}
if ($templatestr === false) {
throw new moodle_exception('filenotfound', 'error');
}
return $templatestr;
}
/**
* Returns description of load_template() result value.
*
* @return external_description
*/
public static function load_template_returns() {
return new external_value(PARAM_RAW, 'template');
}
}

View File

@ -0,0 +1,54 @@
<?php
// 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/>.
/**
* Perform some custom name mapping for template file names (strip leading component/).
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
use coding_exception;
/**
* Perform some custom name mapping for template file names (strip leading component/).
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class mustache_filesystem_loader extends \Mustache_Loader_FilesystemLoader {
/**
* Helper function for getting a Mustache template file name.
* Strips the leading component as we are already limited to the correct directories.
*
* @param string $name
*
* @return string Template file name
*/
protected function getFileName($name) {
if (strpos($name, '/') === false) {
throw new coding_exception('Templates names must be specified as "componentname/templatename" (' . $name . ' requested) ');
}
list($component, $templatename) = explode('/', $name, 2);
return parent::getFileName($templatename);
}
}

View File

@ -0,0 +1,59 @@
<?php
// 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/>.
/**
* Mustache helper that will add JS to the end of the page.
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
/**
* Store a list of JS calls to insert at the end of the page.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class mustache_javascript_helper {
/** @var page_requirements_manager $requires - Page requirements manager for collecting JS calls. */
private $requires = null;
/**
* Create new instance of mustache javascript helper.
*
* @param page_requirements_manager $requires Page requirements manager.
*/
public function __construct($requires) {
$this->requires = $requires;
}
/**
* Add the block of text to the page requires so it is appended in the footer. The
* content of the block can contain further mustache tags which will be resolved.
*
* @param string $text The script content of the section.
* @param \Mustache_LambdaHelper $helper Used to render the content of this block.
*/
public function help($text, \Mustache_LambdaHelper $helper) {
$this->requires->js_amd_inline($helper->render($text));
}
}

View File

@ -0,0 +1,78 @@
<?php
// 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/>.
/**
* Mustache helper render pix icons.
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
use Mustache_LambdaHelper;
use renderer_base;
/**
* This class will call pix_icon with the section content.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class mustache_pix_helper {
/** @var renderer_base $renderer A reference to the renderer in use */
private $renderer;
/**
* Save a reference to the renderer.
* @param renderer_base $renderer
*/
public function __construct(renderer_base $renderer) {
$this->renderer = $renderer;
}
/**
* Read a pix icon name from a template and get it from pix_icon.
*
* {{#pix}}t/edit,component,Anything else is alt text{{/pix}}
*
* The args are comma separated and only the first is required.
*
* @param string $text The text to parse for arguments.
* @param Mustache_LambdaHelper $helper Used to render nested mustache variables.
* @return string
*/
public function pix($text, Mustache_LambdaHelper $helper) {
// Split the text into an array of variables.
$key = strtok($text, ",");
$key = trim($key);
$component = strtok(",");
$component = trim($component);
if (!$component) {
$component = '';
}
$text = strtok("");
// Allow mustache tags in the last argument.
$text = $helper->render($text);
return trim($this->renderer->pix_icon($key, $text, $component));
}
}

View File

@ -0,0 +1,79 @@
<?php
// 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/>.
/**
* Mustache helper to load strings from string_manager.
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
use Mustache_LambdaHelper;
use stdClass;
/**
* This class will load language strings in a template.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class mustache_string_helper {
/**
* Read a lang string from a template and get it from get_string.
*
* Some examples for calling this from a template are:
*
* {{#str}}activity{{/str}}
* {{#str}}actionchoice, core, {{#str}}delete{{/str}}{{/str}} (Nested)
* {{#str}}addinganewto, core, {"what":"This", "to":"That"}{{/str}} (Complex $a)
*
* The args are comma separated and only the first is required.
* The last is a $a argument for get string. For complex data here, use JSON.
*
* @param string $text The text to parse for arguments.
* @param Mustache_LambdaHelper $helper Used to render nested mustache variables.
* @return string
*/
public function str($text, Mustache_LambdaHelper $helper) {
// Split the text into an array of variables.
$key = strtok($text, ",");
$key = trim($key);
$component = strtok(",");
$component = trim($component);
if (!$component) {
$component = '';
}
$a = new stdClass();
$next = strtok('');
$next = trim($next);
if ((strpos($next, '{') === 0) && (strpos($next, '{{') !== 0)) {
$rawjson = $helper->render($next);
$a = json_decode($rawjson);
} else {
$a = $helper->render($next);
}
return get_string($key, $component, $a);
}
}

View File

@ -0,0 +1,52 @@
<?php
// 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/>.
/**
* Mustache helper that will add JS to the end of the page.
*
* @package core
* @category output
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\output;
/**
* Lazy create a uniqid per instance of the class. The id is only generated
* when this class it converted to a string.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*/
class mustache_uniqid_helper {
/** @var string $uniqid The unique id */
private $uniqid = null;
/**
* Init the random variable and return it as a string.
*
* @return string random id.
*/
public function __toString() {
if ($this->uniqid === null) {
$this->uniqid = \html_writer::random_id(uniqid());
}
return $this->uniqid;
}
}

View File

@ -950,6 +950,13 @@ $functions = array(
'type' => 'write',
'capabilities'=> 'moodle/calendar:manageentries', 'moodle/calendar:manageownentries', 'moodle/calendar:managegroupentries'
),
'core_output_load_template' => array(
'classname' => 'core\output\external',
'methodname' => 'load_template',
'description' => 'Load a template for a renderable',
'type' => 'read'
),
);
$services = array(

View File

@ -0,0 +1,35 @@
# Contributions welcome!
### Here's a quick guide:
1. [Fork the repo on GitHub](https://github.com/bobthecow/mustache.php).
2. Update submodules: `git submodule update --init`
3. Run the test suite. We only take pull requests with passing tests, and it's great to know that you have a clean slate. Make sure you have PHPUnit 3.5+, then run `phpunit` from the project directory.
4. Add tests for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, add a test!
5. Make the tests pass.
6. Push your fork to GitHub and submit a pull request against the `dev` branch.
### You can do some things to increase the chance that your pull request is accepted the first time:
* Submit one pull request per fix or feature.
* To help with that, do all your work in a feature branch (e.g. `feature/my-alsome-feature`).
* Follow the conventions you see used in the project.
* Use `phpcs --standard=PSR2` to check your changes against the coding standard.
* Write tests that fail without your code, and pass with it.
* Don't bump version numbers. Those will be updated — per [semver](http://semver.org) — once your change is merged into `master`.
* Update any documentation: docblocks, README, examples, etc.
* ... Don't update the wiki until your change is merged and released, but make a note in your pull request so we don't forget.
### Mustache.php follows the PSR-* coding standards:
* [PSR-0: Class and file naming conventions](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
* [PSR-1: Basic coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
* [PSR-2: Coding style guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)

21
lib/mustache/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010-2014 Justin Hileman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

71
lib/mustache/README.md Normal file
View File

@ -0,0 +1,71 @@
Mustache.php
============
A [Mustache](http://mustache.github.com/) implementation in PHP.
[![Package version](http://img.shields.io/packagist/v/mustache/mustache.svg)](https://packagist.org/packages/mustache/mustache)
[![Build status](http://img.shields.io/travis/bobthecow/mustache.php/dev.svg)](http://travis-ci.org/bobthecow/mustache.php)
[![Monthly downloads](http://img.shields.io/packagist/dm/mustache/mustache.svg)](https://packagist.org/packages/mustache/mustache)
Usage
-----
A quick example:
```php
<?php
$m = new Mustache_Engine;
echo $m->render('Hello {{planet}}', array('planet' => 'World!')); // "Hello World!"
```
And a more in-depth example -- this is the canonical Mustache template:
```html+jinja
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{taxed_value}}, after taxes.
{{/in_ca}}
```
Create a view "context" object -- which could also be an associative array, but those don't do functions quite as well:
```php
<?php
class Chris {
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
}
```
And render it:
```php
<?php
$m = new Mustache_Engine;
$chris = new Chris;
echo $m->render($template, $chris);
```
And That's Not All!
-------------------
Read [the Mustache.php documentation](https://github.com/bobthecow/mustache.php/wiki/Home) for more information.
See Also
--------
* [Readme for the Ruby Mustache implementation](http://github.com/defunkt/mustache/blob/master/README.md).
* [mustache(5)](http://mustache.github.com/mustache.5.html) man page.

View File

@ -0,0 +1,24 @@
{
"name": "mustache/mustache",
"description": "A Mustache implementation in PHP.",
"keywords": ["templating", "mustache"],
"homepage": "https://github.com/bobthecow/mustache.php",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
}
],
"require": {
"php": ">=5.2.4"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"autoload": {
"psr-0": { "Mustache": "src/" }
}
}

View File

@ -0,0 +1,11 @@
Description of Mustache library import into moodle.
Download from https://github.com/bobthecow/mustache.php
Delete folder "test"
Delete phpunit.xml.dist
Delete hidden files ".*"
Delete folder "bin"

View File

@ -0,0 +1,74 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache class autoloader.
*/
class Mustache_Autoloader
{
private $baseDir;
/**
* Autoloader constructor.
*
* @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
*/
public function __construct($baseDir = null)
{
if ($baseDir === null) {
$baseDir = dirname(__FILE__).'/..';
}
// realpath doesn't always work, for example, with stream URIs
$realDir = realpath($baseDir);
if (is_dir($realDir)) {
$this->baseDir = $realDir;
} else {
$this->baseDir = $baseDir;
}
}
/**
* Register a new instance as an SPL autoloader.
*
* @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
*
* @return Mustache_Autoloader Registered Autoloader instance
*/
public static function register($baseDir = null)
{
$loader = new self($baseDir);
spl_autoload_register(array($loader, 'autoload'));
return $loader;
}
/**
* Autoload Mustache classes.
*
* @param string $class
*/
public function autoload($class)
{
if ($class[0] === '\\') {
$class = substr($class, 1);
}
if (strpos($class, 'Mustache') !== 0) {
return;
}
$file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
if (is_file($file)) {
require $file;
}
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Cache interface.
*
* Interface for caching and loading Mustache_Template classes
* generated by the Mustache_Compiler.
*/
interface Mustache_Cache
{
/**
* Load a compiled Mustache_Template class from cache.
*
* @param string $key
*
* @return boolean indicates successfully class load
*/
public function load($key);
/**
* Cache and load a compiled Mustache_Template class.
*
* @param string $key
* @param string $value
*
* @return void
*/
public function cache($key, $value);
}

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract Mustache Cache class.
*
* Provides logging support to child implementations.
*
* @abstract
*/
abstract class Mustache_Cache_AbstractCache implements Mustache_Cache
{
private $logger = null;
/**
* Get the current logger instance.
*
* @return Mustache_Logger|Psr\Log\LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* Set a logger instance.
*
* @param Mustache_Logger|Psr\Log\LoggerInterface $logger
*/
public function setLogger($logger = null)
{
if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
}
$this->logger = $logger;
}
/**
* Add a log record if logging is enabled.
*
* @param integer $level The logging level
* @param string $message The log message
* @param array $context The log context
*/
protected function log($level, $message, array $context = array())
{
if (isset($this->logger)) {
$this->logger->log($level, $message, $context);
}
}
}

View File

@ -0,0 +1,159 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Cache filesystem implementation.
*
* A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
*
* $cache = new Mustache_Cache_FilesystemCache(dirname(__FILE__).'/cache');
* $cache->cache($className, $compiledSource);
*
* The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
*/
class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache
{
private $baseDir;
private $fileMode;
/**
* Filesystem cache constructor.
*
* @param string $baseDir Directory for compiled templates.
* @param int $fileMode Override default permissions for cache files. Defaults to using the system-defined umask.
*/
public function __construct($baseDir, $fileMode = null)
{
$this->baseDir = $baseDir;
$this->fileMode = $fileMode;
}
/**
* Load the class from cache using `require_once`.
*
* @param string $key
*
* @return boolean
*/
public function load($key)
{
$fileName = $this->getCacheFilename($key);
if (!is_file($fileName)) {
return false;
}
require_once $fileName;
return true;
}
/**
* Cache and load the compiled class
*
* @param string $key
* @param string $value
*
* @return void
*/
public function cache($key, $value)
{
$fileName = $this->getCacheFilename($key);
$this->log(
Mustache_Logger::DEBUG,
'Writing to template cache: "{fileName}"',
array('fileName' => $fileName)
);
$this->writeFile($fileName, $value);
$this->load($key);
}
/**
* Build the cache filename.
* Subclasses should override for custom cache directory structures.
*
* @param string $name
*
* @return string
*/
protected function getCacheFilename($name)
{
return sprintf('%s/%s.php', $this->baseDir, $name);
}
/**
* Create cache directory
*
* @throws Mustache_Exception_RuntimeException If unable to create directory
*
* @param string $fileName
*
* @return string
*/
private function buildDirectoryForFilename($fileName)
{
$dirName = dirname($fileName);
if (!is_dir($dirName)) {
$this->log(
Mustache_Logger::INFO,
'Creating Mustache template cache directory: "{dirName}"',
array('dirName' => $dirName)
);
@mkdir($dirName, 0777, true);
if (!is_dir($dirName)) {
throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
}
}
return $dirName;
}
/**
* Write cache file
*
* @throws Mustache_Exception_RuntimeException If unable to write file
*
* @param string $fileName
* @param string $value
*
* @return void
*/
private function writeFile($fileName, $value)
{
$dirName = $this->buildDirectoryForFilename($fileName);
$this->log(
Mustache_Logger::DEBUG,
'Caching compiled template to "{fileName}"',
array('fileName' => $fileName)
);
$tempFile = tempnam($dirName, basename($fileName));
if (false !== @file_put_contents($tempFile, $value)) {
if (@rename($tempFile, $fileName)) {
$mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
@chmod($fileName, $mode);
return;
}
$this->log(
Mustache_Logger::ERROR,
'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
array('tempName' => $tempFile, 'fileName' => $fileName)
);
}
throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
}
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Cache in-memory implementation.
*
* The in-memory cache is used for uncached lambda section templates. It's also useful during development, but is not
* recommended for production use.
*/
class Mustache_Cache_NoopCache extends Mustache_Cache_AbstractCache
{
/**
* Loads nothing. Move along.
*
* @param string $key
*
* @return boolean
*/
public function load($key)
{
return false;
}
/**
* Loads the compiled Mustache Template class without caching.
*
* @param string $key
* @param string $value
*
* @return void
*/
public function cache($key, $value)
{
$this->log(
Mustache_Logger::WARNING,
'Template cache disabled, evaluating "{className}" class at runtime',
array('className' => $key)
);
eval('?>' . $value);
}
}

View File

@ -0,0 +1,646 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Compiler class.
*
* This class is responsible for turning a Mustache token parse tree into normal PHP source code.
*/
class Mustache_Compiler
{
private $pragmas;
private $defaultPragmas = array();
private $sections;
private $source;
private $indentNextLine;
private $customEscape;
private $entityFlags;
private $charset;
private $strictCallables;
/**
* Compile a Mustache token parse tree into PHP source code.
*
* @param string $source Mustache Template source code
* @param string $tree Parse tree of Mustache tokens
* @param string $name Mustache Template class name
* @param bool $customEscape (default: false)
* @param string $charset (default: 'UTF-8')
* @param bool $strictCallables (default: false)
* @param int $entityFlags (default: ENT_COMPAT)
*
* @return string Generated PHP source code
*/
public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
{
$this->pragmas = $this->defaultPragmas;
$this->sections = array();
$this->source = $source;
$this->indentNextLine = true;
$this->customEscape = $customEscape;
$this->entityFlags = $entityFlags;
$this->charset = $charset;
$this->strictCallables = $strictCallables;
return $this->writeCode($tree, $name);
}
/**
* Enable pragmas across all templates, regardless of the presence of pragma
* tags in the individual templates.
*
* @internal Users should set global pragmas in Mustache_Engine, not here :)
*
* @param string[] $pragmas
*/
public function setPragmas(array $pragmas)
{
$this->pragmas = array();
foreach ($pragmas as $pragma) {
$this->pragmas[$pragma] = true;
}
$this->defaultPragmas = $this->pragmas;
}
/**
* Helper function for walking the Mustache token parse tree.
*
* @throws Mustache_Exception_SyntaxException upon encountering unknown token types.
*
* @param array $tree Parse tree of Mustache tokens
* @param int $level (default: 0)
*
* @return string Generated PHP source code
*/
private function walk(array $tree, $level = 0)
{
$code = '';
$level++;
foreach ($tree as $node) {
switch ($node[Mustache_Tokenizer::TYPE]) {
case Mustache_Tokenizer::T_PRAGMA:
$this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
break;
case Mustache_Tokenizer::T_SECTION:
$code .= $this->section(
$node[Mustache_Tokenizer::NODES],
$node[Mustache_Tokenizer::NAME],
isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
$node[Mustache_Tokenizer::INDEX],
$node[Mustache_Tokenizer::END],
$node[Mustache_Tokenizer::OTAG],
$node[Mustache_Tokenizer::CTAG],
$level
);
break;
case Mustache_Tokenizer::T_INVERTED:
$code .= $this->invertedSection(
$node[Mustache_Tokenizer::NODES],
$node[Mustache_Tokenizer::NAME],
isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
$level
);
break;
case Mustache_Tokenizer::T_PARTIAL:
$code .= $this->partial(
$node[Mustache_Tokenizer::NAME],
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
$level
);
break;
case Mustache_Tokenizer::T_PARENT:
$code .= $this->parent(
$node[Mustache_Tokenizer::NAME],
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
$node[Mustache_Tokenizer::NODES],
$level
);
break;
case Mustache_Tokenizer::T_BLOCK_ARG:
$code .= $this->blockArg(
$node[Mustache_Tokenizer::NODES],
$node[Mustache_Tokenizer::NAME],
$node[Mustache_Tokenizer::INDEX],
$node[Mustache_Tokenizer::END],
$node[Mustache_Tokenizer::OTAG],
$node[Mustache_Tokenizer::CTAG],
$level
);
break;
case Mustache_Tokenizer::T_BLOCK_VAR:
$code .= $this->blockVar(
$node[Mustache_Tokenizer::NODES],
$node[Mustache_Tokenizer::NAME],
$node[Mustache_Tokenizer::INDEX],
$node[Mustache_Tokenizer::END],
$node[Mustache_Tokenizer::OTAG],
$node[Mustache_Tokenizer::CTAG],
$level
);
break;
case Mustache_Tokenizer::T_COMMENT:
break;
case Mustache_Tokenizer::T_ESCAPED:
case Mustache_Tokenizer::T_UNESCAPED:
case Mustache_Tokenizer::T_UNESCAPED_2:
$code .= $this->variable(
$node[Mustache_Tokenizer::NAME],
isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
$node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
$level
);
break;
case Mustache_Tokenizer::T_TEXT:
$code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
break;
default:
throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
}
}
return $code;
}
const KLASS = '<?php
class %s extends Mustache_Template
{
private $lambdaHelper;%s
public function renderInternal(Mustache_Context $context, $indent = \'\')
{
$this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
$buffer = \'\';
$newContext = array();
%s
return $buffer;
}
%s
}';
const KLASS_NO_LAMBDAS = '<?php
class %s extends Mustache_Template
{%s
public function renderInternal(Mustache_Context $context, $indent = \'\')
{
$buffer = \'\';
$newContext = array();
%s
return $buffer;
}
}';
const STRICT_CALLABLE = 'protected $strictCallables = true;';
/**
* Generate Mustache Template class PHP source.
*
* @param array $tree Parse tree of Mustache tokens
* @param string $name Mustache Template class name
*
* @return string Generated PHP source code
*/
private function writeCode($tree, $name)
{
$code = $this->walk($tree);
$sections = implode("\n", $this->sections);
$klass = empty($this->sections) ? self::KLASS_NO_LAMBDAS : self::KLASS;
$callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections);
}
const BLOCK_VAR = '
$value = $this->resolveValue($context->findInBlock(%s), $context, $indent);
if ($value && !is_array($value) && !is_object($value)) {
$buffer .= $value;
} else {
%s
}
';
/**
* Generate Mustache Template inheritance block variable PHP source.
*
* @param array $nodes Array of child tokens
* @param string $id Section name
* @param int $start Section start offset
* @param int $end Section end offset
* @param string $otag Current Mustache opening tag
* @param string $ctag Current Mustache closing tag
* @param int $level
*
* @return string Generated PHP source code
*/
private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
{
$id = var_export($id, true);
return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes, 2));
}
const BLOCK_ARG = '
// %s block_arg
$value = $this->section%s($context, $indent, true);
$newContext[%s] = %s$value;
';
/**
* Generate Mustache Template inheritance block argument PHP source.
*
* @param array $nodes Array of child tokens
* @param string $id Section name
* @param int $start Section start offset
* @param int $end Section end offset
* @param string $otag Current Mustache opening tag
* @param string $ctag Current Mustache closing tag
* @param int $level
*
* @return string Generated PHP source code
*/
private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
{
$key = $this->section($nodes, $id, array(), $start, $end, $otag, $ctag, $level, true);
$id = var_export($id, true);
return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key, $id, $this->flushIndent());
}
const SECTION_CALL = '
// %s section
$value = $context->%s(%s);%s
$buffer .= $this->section%s($context, $indent, $value);
';
const SECTION = '
private function section%s(Mustache_Context $context, $indent, $value)
{
$buffer = \'\';
if (%s) {
$source = %s;
$result = call_user_func($value, $source, $this->lambdaHelper);
if (strpos($result, \'{{\') === false) {
$buffer .= $result;
} else {
$buffer .= $this->mustache
->loadLambda((string) $result%s)
->renderInternal($context);
}
} elseif (!empty($value)) {
$values = $this->isIterable($value) ? $value : array($value);
foreach ($values as $value) {
$context->push($value);
%s
$context->pop();
}
}
return $buffer;
}';
/**
* Generate Mustache Template section PHP source.
*
* @param array $nodes Array of child tokens
* @param string $id Section name
* @param string[] $filters Array of filters
* @param int $start Section start offset
* @param int $end Section end offset
* @param string $otag Current Mustache opening tag
* @param string $ctag Current Mustache closing tag
* @param int $level
* @param bool $arg (default: false)
*
* @return string Generated section PHP source code
*/
private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level, $arg = false)
{
$source = var_export(substr($this->source, $start, $end - $start), true);
$callable = $this->getCallable();
if ($otag !== '{{' || $ctag !== '}}') {
$delims = ', '.var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
} else {
$delims = '';
}
$key = ucfirst(md5($delims."\n".$source));
if (!isset($this->sections[$key])) {
$this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $delims, $this->walk($nodes, 2));
}
if ($arg === true) {
return $key;
} else {
$method = $this->getFindMethod($id);
$id = var_export($id, true);
$filters = $this->getFilters($filters, $level);
return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
}
}
const INVERTED_SECTION = '
// %s inverted section
$value = $context->%s(%s);%s
if (empty($value)) {
%s
}';
/**
* Generate Mustache Template inverted section PHP source.
*
* @param array $nodes Array of child tokens
* @param string $id Section name
* @param string[] $filters Array of filters
* @param int $level
*
* @return string Generated inverted section PHP source code
*/
private function invertedSection($nodes, $id, $filters, $level)
{
$method = $this->getFindMethod($id);
$id = var_export($id, true);
$filters = $this->getFilters($filters, $level);
return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
}
const PARTIAL_INDENT = ', $indent . %s';
const PARTIAL = '
if ($partial = $this->mustache->loadPartial(%s)) {
$buffer .= $partial->renderInternal($context%s);
}
';
/**
* Generate Mustache Template partial call PHP source.
*
* @param string $id Partial name
* @param string $indent Whitespace indent to apply to partial
* @param int $level
*
* @return string Generated partial call PHP source code
*/
private function partial($id, $indent, $level)
{
if ($indent !== '') {
$indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
} else {
$indentParam = '';
}
return sprintf(
$this->prepare(self::PARTIAL, $level),
var_export($id, true),
$indentParam
);
}
const PARENT = '
%s
if ($parent = $this->mustache->LoadPartial(%s)) {
$context->pushBlockContext($newContext);
$buffer .= $parent->renderInternal($context, $indent);
$context->popBlockContext();
}
';
/**
* Generate Mustache Template inheritance parent call PHP source.
*
* @param string $id Parent tag name
* @param string $indent Whitespace indent to apply to parent
* @param array $children Child nodes
* @param int $level
*
* @return string Generated PHP source code
*/
private function parent($id, $indent, array $children, $level)
{
$realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
return sprintf(
$this->prepare(self::PARENT, $level),
$this->walk($realChildren, $level),
var_export($id, true),
var_export($indent, true)
);
}
/**
* Helper method for filtering out non-block-arg tokens.
*
* @param array $node
*
* @return boolean True if $node is a block arg token.
*/
private static function onlyBlockArgs(array $node)
{
return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
}
const VARIABLE = '
$value = $this->resolveValue($context->%s(%s), $context, $indent);%s
$buffer .= %s%s;
';
/**
* Generate Mustache Template variable interpolation PHP source.
*
* @param string $id Variable name
* @param string[] $filters Array of filters
* @param boolean $escape Escape the variable value for output?
* @param int $level
*
* @return string Generated variable interpolation PHP source
*/
private function variable($id, $filters, $escape, $level)
{
$method = $this->getFindMethod($id);
$id = ($method !== 'last') ? var_export($id, true) : '';
$filters = $this->getFilters($filters, $level);
$value = $escape ? $this->getEscape() : '$value';
return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
}
const FILTER = '
$filter = $context->%s(%s);
if (!(%s)) {
throw new Mustache_Exception_UnknownFilterException(%s);
}
$value = call_user_func($filter, $value);%s
';
/**
* Generate Mustache Template variable filtering PHP source.
*
* @param string[] $filters Array of filters
* @param int $level
*
* @return string Generated filter PHP source
*/
private function getFilters(array $filters, $level)
{
if (empty($filters)) {
return '';
}
$name = array_shift($filters);
$method = $this->getFindMethod($name);
$filter = ($method !== 'last') ? var_export($name, true) : '';
$callable = $this->getCallable('$filter');
$msg = var_export($name, true);
return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
}
const LINE = '$buffer .= "\n";';
const TEXT = '$buffer .= %s%s;';
/**
* Generate Mustache Template output Buffer call PHP source.
*
* @param string $text
* @param int $level
*
* @return string Generated output Buffer call PHP source
*/
private function text($text, $level)
{
$indentNextLine = (substr($text, -1) === "\n");
$code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
$this->indentNextLine = $indentNextLine;
return $code;
}
/**
* Prepare PHP source code snippet for output.
*
* @param string $text
* @param int $bonus Additional indent level (default: 0)
* @param boolean $prependNewline Prepend a newline to the snippet? (default: true)
* @param boolean $appendNewline Append a newline to the snippet? (default: false)
*
* @return string PHP source code snippet
*/
private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
{
$text = ($prependNewline ? "\n" : '').trim($text);
if ($prependNewline) {
$bonus++;
}
if ($appendNewline) {
$text .= "\n";
}
return preg_replace("/\n( {8})?/", "\n".str_repeat(" ", $bonus * 4), $text);
}
const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)';
/**
* Get the current escaper.
*
* @param string $value (default: '$value')
*
* @return string Either a custom callback, or an inline call to `htmlspecialchars`
*/
private function getEscape($value = '$value')
{
if ($this->customEscape) {
return sprintf(self::CUSTOM_ESCAPE, $value);
}
return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
}
/**
* Select the appropriate Context `find` method for a given $id.
*
* The return value will be one of `find`, `findDot` or `last`.
*
* @see Mustache_Context::find
* @see Mustache_Context::findDot
* @see Mustache_Context::last
*
* @param string $id Variable name
*
* @return string `find` method name
*/
private function getFindMethod($id)
{
if ($id === '.') {
return 'last';
}
if (strpos($id, '.') === false) {
return 'find';
}
return 'findDot';
}
const IS_CALLABLE = '!is_string(%s) && is_callable(%s)';
const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
/**
* Helper function to compile strict vs lax "is callable" logic.
*
* @param string $variable (default: '$value')
*
* @return string "is callable" logic
*/
private function getCallable($variable = '$value')
{
$tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
return sprintf($tpl, $variable, $variable);
}
const LINE_INDENT = '$indent . ';
/**
* Get the current $indent prefix to write to the buffer.
*
* @return string "$indent . " or ""
*/
private function flushIndent()
{
if (!$this->indentNextLine) {
return '';
}
$this->indentNextLine = false;
return self::LINE_INDENT;
}
}

View File

@ -0,0 +1,206 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template rendering Context.
*/
class Mustache_Context
{
private $stack = array();
private $blockStack = array();
/**
* Mustache rendering Context constructor.
*
* @param mixed $context Default rendering context (default: null)
*/
public function __construct($context = null)
{
if ($context !== null) {
$this->stack = array($context);
}
}
/**
* Push a new Context frame onto the stack.
*
* @param mixed $value Object or array to use for context
*/
public function push($value)
{
array_push($this->stack, $value);
}
/**
* Push a new Context frame onto the block context stack.
*
* @param mixed $value Object or array to use for block context
*/
public function pushBlockContext($value)
{
array_push($this->blockStack, $value);
}
/**
* Pop the last Context frame from the stack.
*
* @return mixed Last Context frame (object or array)
*/
public function pop()
{
return array_pop($this->stack);
}
/**
* Pop the last block Context frame from the stack.
*
* @return mixed Last block Context frame (object or array)
*/
public function popBlockContext()
{
return array_pop($this->blockStack);
}
/**
* Get the last Context frame.
*
* @return mixed Last Context frame (object or array)
*/
public function last()
{
return end($this->stack);
}
/**
* Find a variable in the Context stack.
*
* Starting with the last Context frame (the context of the innermost section), and working back to the top-level
* rendering context, look for a variable with the given name:
*
* * If the Context frame is an associative array which contains the key $id, returns the value of that element.
* * If the Context frame is an object, this will check first for a public method, then a public property named
* $id. Failing both of these, it will try `__isset` and `__get` magic methods.
* * If a value named $id is not found in any Context frame, returns an empty string.
*
* @param string $id Variable name
*
* @return mixed Variable value, or '' if not found
*/
public function find($id)
{
return $this->findVariableInStack($id, $this->stack);
}
/**
* Find a 'dot notation' variable in the Context stack.
*
* Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
* the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
* result. For example, given the following context stack:
*
* $data = array(
* 'name' => 'Fred',
* 'child' => array(
* 'name' => 'Bob'
* ),
* );
*
* ... and the Mustache following template:
*
* {{ child.name }}
*
* ... the `name` value is only searched for within the `child` value of the global Context, not within parent
* Context frames.
*
* @param string $id Dotted variable selector
*
* @return mixed Variable value, or '' if not found
*/
public function findDot($id)
{
$chunks = explode('.', $id);
$first = array_shift($chunks);
$value = $this->findVariableInStack($first, $this->stack);
foreach ($chunks as $chunk) {
if ($value === '') {
return $value;
}
$value = $this->findVariableInStack($chunk, array($value));
}
return $value;
}
/**
* Find an argument in the block context stack.
*
* @param string $id
*
* @return mixed Variable value, or '' if not found.
*/
public function findInBlock($id)
{
foreach ($this->blockStack as $context) {
if (array_key_exists($id, $context)) {
return $context[$id];
}
}
return '';
}
/**
* Helper function to find a variable in the Context stack.
*
* @see Mustache_Context::find
*
* @param string $id Variable name
* @param array $stack Context stack
*
* @return mixed Variable value, or '' if not found
*/
private function findVariableInStack($id, array $stack)
{
for ($i = count($stack) - 1; $i >= 0; $i--) {
$frame = &$stack[$i];
switch (gettype($frame)) {
case 'object':
if (!($frame instanceof Closure)) {
// Note that is_callable() *will not work here*
// See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
if (method_exists($frame, $id)) {
return $frame->$id();
}
if (isset($frame->$id)) {
return $frame->$id;
}
if ($frame instanceof ArrayAccess && isset($frame[$id])) {
return $frame[$id];
}
}
break;
case 'array':
if (array_key_exists($id, $frame)) {
return $frame[$id];
}
break;
}
}
return '';
}
}

View File

@ -0,0 +1,785 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache implementation in PHP.
*
* {@link http://defunkt.github.com/mustache}
*
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
* logic from template files. In fact, it is not even possible to embed logic in the template.
*
* This is very, very rad.
*
* @author Justin Hileman {@link http://justinhileman.com}
*/
class Mustache_Engine
{
const VERSION = '2.7.0';
const SPEC_VERSION = '1.1.2';
const PRAGMA_FILTERS = 'FILTERS';
const PRAGMA_BLOCKS = 'BLOCKS';
// Known pragmas
private static $knownPragmas = array(
self::PRAGMA_FILTERS => true,
self::PRAGMA_BLOCKS => true,
);
// Template cache
private $templates = array();
// Environment
private $templateClassPrefix = '__Mustache_';
private $cache;
private $lambdaCache;
private $cacheLambdaTemplates = false;
private $loader;
private $partialsLoader;
private $helpers;
private $escape;
private $entityFlags = ENT_COMPAT;
private $charset = 'UTF-8';
private $logger;
private $strictCallables = false;
private $pragmas = array();
// Services
private $tokenizer;
private $parser;
private $compiler;
/**
* Mustache class constructor.
*
* Passing an $options array allows overriding certain Mustache options during instantiation:
*
* $options = array(
* // The class prefix for compiled templates. Defaults to '__Mustache_'.
* 'template_class_prefix' => '__MyTemplates_',
*
* // A Mustache cache instance or a cache directory string for compiled templates.
* // Mustache will not cache templates unless this is set.
* 'cache' => dirname(__FILE__).'/tmp/cache/mustache',
*
* // Override default permissions for cache files. Defaults to using the system-defined umask. It is
* // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
* 'cache_file_mode' => 0666,
*
* // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda
* // sections are often too dynamic to benefit from caching.
* 'cache_lambda_templates' => true,
*
* // A Mustache template loader instance. Uses a StringLoader if not specified.
* 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
*
* // A Mustache loader instance for partials.
* 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
*
* // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
* // efficient or lazy as a Filesystem (or database) loader.
* 'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')),
*
* // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
* // sections), or any other valid Mustache context value. They will be prepended to the context stack,
* // so they will be available in any template loaded by this Mustache instance.
* 'helpers' => array('i18n' => function ($text) {
* // do something translatey here...
* }),
*
* // An 'escape' callback, responsible for escaping double-mustache variables.
* 'escape' => function ($value) {
* return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
* },
*
* // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES.
* 'entity_flags' => ENT_QUOTES,
*
* // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
* 'charset' => 'ISO-8859-1',
*
* // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
* // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
* // available as well:
* 'logger' => new Mustache_Logger_StreamLogger('php://stderr'),
*
* // Only treat Closure instances and invokable classes as callable. If true, values like
* // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally
* // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
* // helps protect against arbitrary code execution when user input is passed directly into the template.
* // This currently defaults to false, but will default to true in v3.0.
* 'strict_callables' => true,
*
* // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
* // templates.
* 'pragmas' => [Mustache_Engine::PRAGMA_FILTERS],
* );
*
* @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable.
*
* @param array $options (default: array())
*/
public function __construct(array $options = array())
{
if (isset($options['template_class_prefix'])) {
$this->templateClassPrefix = $options['template_class_prefix'];
}
if (isset($options['cache'])) {
$cache = $options['cache'];
if (is_string($cache)) {
$mode = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
$cache = new Mustache_Cache_FilesystemCache($cache, $mode);
}
$this->setCache($cache);
}
if (isset($options['cache_lambda_templates'])) {
$this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
}
if (isset($options['loader'])) {
$this->setLoader($options['loader']);
}
if (isset($options['partials_loader'])) {
$this->setPartialsLoader($options['partials_loader']);
}
if (isset($options['partials'])) {
$this->setPartials($options['partials']);
}
if (isset($options['helpers'])) {
$this->setHelpers($options['helpers']);
}
if (isset($options['escape'])) {
if (!is_callable($options['escape'])) {
throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable');
}
$this->escape = $options['escape'];
}
if (isset($options['entity_flags'])) {
$this->entityFlags = $options['entity_flags'];
}
if (isset($options['charset'])) {
$this->charset = $options['charset'];
}
if (isset($options['logger'])) {
$this->setLogger($options['logger']);
}
if (isset($options['strict_callables'])) {
$this->strictCallables = $options['strict_callables'];
}
if (isset($options['pragmas'])) {
foreach ($options['pragmas'] as $pragma) {
if (!isset(self::$knownPragmas[$pragma])) {
throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma));
}
$this->pragmas[$pragma] = true;
}
}
}
/**
* Shortcut 'render' invocation.
*
* Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
*
* @see Mustache_Engine::loadTemplate
* @see Mustache_Template::render
*
* @param string $template
* @param mixed $context (default: array())
*
* @return string Rendered template
*/
public function render($template, $context = array())
{
return $this->loadTemplate($template)->render($context);
}
/**
* Get the current Mustache escape callback.
*
* @return callable|null
*/
public function getEscape()
{
return $this->escape;
}
/**
* Get the current Mustache entitity type to escape.
*
* @return int
*/
public function getEntityFlags()
{
return $this->entityFlags;
}
/**
* Get the current Mustache character set.
*
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get the current globally enabled pragmas.
*
* @return array
*/
public function getPragmas()
{
return array_keys($this->pragmas);
}
/**
* Set the Mustache template Loader instance.
*
* @param Mustache_Loader $loader
*/
public function setLoader(Mustache_Loader $loader)
{
$this->loader = $loader;
}
/**
* Get the current Mustache template Loader instance.
*
* If no Loader instance has been explicitly specified, this method will instantiate and return
* a StringLoader instance.
*
* @return Mustache_Loader
*/
public function getLoader()
{
if (!isset($this->loader)) {
$this->loader = new Mustache_Loader_StringLoader();
}
return $this->loader;
}
/**
* Set the Mustache partials Loader instance.
*
* @param Mustache_Loader $partialsLoader
*/
public function setPartialsLoader(Mustache_Loader $partialsLoader)
{
$this->partialsLoader = $partialsLoader;
}
/**
* Get the current Mustache partials Loader instance.
*
* If no Loader instance has been explicitly specified, this method will instantiate and return
* an ArrayLoader instance.
*
* @return Mustache_Loader
*/
public function getPartialsLoader()
{
if (!isset($this->partialsLoader)) {
$this->partialsLoader = new Mustache_Loader_ArrayLoader();
}
return $this->partialsLoader;
}
/**
* Set partials for the current partials Loader instance.
*
* @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable
*
* @param array $partials (default: array())
*/
public function setPartials(array $partials = array())
{
if (!isset($this->partialsLoader)) {
$this->partialsLoader = new Mustache_Loader_ArrayLoader();
}
if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
}
$this->partialsLoader->setTemplates($partials);
}
/**
* Set an array of Mustache helpers.
*
* An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
* any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
* any template loaded by this Mustache instance.
*
* @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable
*
* @param array|Traversable $helpers
*/
public function setHelpers($helpers)
{
if (!is_array($helpers) && !$helpers instanceof Traversable) {
throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers');
}
$this->getHelpers()->clear();
foreach ($helpers as $name => $helper) {
$this->addHelper($name, $helper);
}
}
/**
* Get the current set of Mustache helpers.
*
* @see Mustache_Engine::setHelpers
*
* @return Mustache_HelperCollection
*/
public function getHelpers()
{
if (!isset($this->helpers)) {
$this->helpers = new Mustache_HelperCollection();
}
return $this->helpers;
}
/**
* Add a new Mustache helper.
*
* @see Mustache_Engine::setHelpers
*
* @param string $name
* @param mixed $helper
*/
public function addHelper($name, $helper)
{
$this->getHelpers()->add($name, $helper);
}
/**
* Get a Mustache helper by name.
*
* @see Mustache_Engine::setHelpers
*
* @param string $name
*
* @return mixed Helper
*/
public function getHelper($name)
{
return $this->getHelpers()->get($name);
}
/**
* Check whether this Mustache instance has a helper.
*
* @see Mustache_Engine::setHelpers
*
* @param string $name
*
* @return boolean True if the helper is present
*/
public function hasHelper($name)
{
return $this->getHelpers()->has($name);
}
/**
* Remove a helper by name.
*
* @see Mustache_Engine::setHelpers
*
* @param string $name
*/
public function removeHelper($name)
{
$this->getHelpers()->remove($name);
}
/**
* Set the Mustache Logger instance.
*
* @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface.
*
* @param Mustache_Logger|Psr\Log\LoggerInterface $logger
*/
public function setLogger($logger = null)
{
if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
}
if ($this->getCache()->getLogger() === null) {
$this->getCache()->setLogger($logger);
}
$this->logger = $logger;
}
/**
* Get the current Mustache Logger instance.
*
* @return Mustache_Logger|Psr\Log\LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* Set the Mustache Tokenizer instance.
*
* @param Mustache_Tokenizer $tokenizer
*/
public function setTokenizer(Mustache_Tokenizer $tokenizer)
{
$this->tokenizer = $tokenizer;
}
/**
* Get the current Mustache Tokenizer instance.
*
* If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Mustache_Tokenizer
*/
public function getTokenizer()
{
if (!isset($this->tokenizer)) {
$this->tokenizer = new Mustache_Tokenizer();
}
return $this->tokenizer;
}
/**
* Set the Mustache Parser instance.
*
* @param Mustache_Parser $parser
*/
public function setParser(Mustache_Parser $parser)
{
$this->parser = $parser;
}
/**
* Get the current Mustache Parser instance.
*
* If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Mustache_Parser
*/
public function getParser()
{
if (!isset($this->parser)) {
$this->parser = new Mustache_Parser();
}
return $this->parser;
}
/**
* Set the Mustache Compiler instance.
*
* @param Mustache_Compiler $compiler
*/
public function setCompiler(Mustache_Compiler $compiler)
{
$this->compiler = $compiler;
}
/**
* Get the current Mustache Compiler instance.
*
* If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Mustache_Compiler
*/
public function getCompiler()
{
if (!isset($this->compiler)) {
$this->compiler = new Mustache_Compiler();
}
return $this->compiler;
}
/**
* Set the Mustache Cache instance.
*
* @param Mustache_Cache $cache
*/
public function setCache(Mustache_Cache $cache)
{
if (isset($this->logger) && $cache->getLogger() === null) {
$cache->setLogger($this->getLogger());
}
$this->cache = $cache;
}
/**
* Get the current Mustache Cache instance.
*
* If no Cache instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Mustache_Cache
*/
public function getCache()
{
if (!isset($this->cache)) {
$this->setCache(new Mustache_Cache_NoopCache());
}
return $this->cache;
}
/**
* Get the current Lambda Cache instance.
*
* If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache.
*
* @see Mustache_Engine::getCache
*
* @return Mustache_Cache
*/
protected function getLambdaCache()
{
if ($this->cacheLambdaTemplates) {
return $this->getCache();
}
if (!isset($this->lambdaCache)) {
$this->lambdaCache = new Mustache_Cache_NoopCache();
}
return $this->lambdaCache;
}
/**
* Helper method to generate a Mustache template class.
*
* @param string $source
*
* @return string Mustache Template class name
*/
public function getTemplateClassName($source)
{
return $this->templateClassPrefix . md5(sprintf(
'version:%s,escape:%s,entity_flags:%i,charset:%s,strict_callables:%s,pragmas:%s,source:%s',
self::VERSION,
isset($this->escape) ? 'custom' : 'default',
$this->entityFlags,
$this->charset,
$this->strictCallables ? 'true' : 'false',
implode(' ', $this->getPragmas()),
$source
));
}
/**
* Load a Mustache Template by name.
*
* @param string $name
*
* @return Mustache_Template
*/
public function loadTemplate($name)
{
return $this->loadSource($this->getLoader()->load($name));
}
/**
* Load a Mustache partial Template by name.
*
* This is a helper method used internally by Template instances for loading partial templates. You can most likely
* ignore it completely.
*
* @param string $name
*
* @return Mustache_Template
*/
public function loadPartial($name)
{
try {
if (isset($this->partialsLoader)) {
$loader = $this->partialsLoader;
} elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) {
$loader = $this->loader;
} else {
throw new Mustache_Exception_UnknownTemplateException($name);
}
return $this->loadSource($loader->load($name));
} catch (Mustache_Exception_UnknownTemplateException $e) {
// If the named partial cannot be found, log then return null.
$this->log(
Mustache_Logger::WARNING,
'Partial not found: "{name}"',
array('name' => $e->getTemplateName())
);
}
}
/**
* Load a Mustache lambda Template by source.
*
* This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
* likely ignore it completely.
*
* @param string $source
* @param string $delims (default: null)
*
* @return Mustache_Template
*/
public function loadLambda($source, $delims = null)
{
if ($delims !== null) {
$source = $delims . "\n" . $source;
}
return $this->loadSource($source, $this->getLambdaCache());
}
/**
* Instantiate and return a Mustache Template instance by source.
*
* Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect
* the 'cache_lambda_templates' configuration option.
*
* @see Mustache_Engine::loadTemplate
* @see Mustache_Engine::loadPartial
* @see Mustache_Engine::loadLambda
*
* @param string $source
* @param Mustache_Cache $cache (default: null)
*
* @return Mustache_Template
*/
private function loadSource($source, Mustache_Cache $cache = null)
{
$className = $this->getTemplateClassName($source);
if (!isset($this->templates[$className])) {
if ($cache === null) {
$cache = $this->getCache();
}
if (!class_exists($className, false)) {
if (!$cache->load($className)) {
$compiled = $this->compile($source);
$cache->cache($className, $compiled);
}
}
$this->log(
Mustache_Logger::DEBUG,
'Instantiating template: "{className}"',
array('className' => $className)
);
$this->templates[$className] = new $className($this);
}
return $this->templates[$className];
}
/**
* Helper method to tokenize a Mustache template.
*
* @see Mustache_Tokenizer::scan
*
* @param string $source
*
* @return array Tokens
*/
private function tokenize($source)
{
return $this->getTokenizer()->scan($source);
}
/**
* Helper method to parse a Mustache template.
*
* @see Mustache_Parser::parse
*
* @param string $source
*
* @return array Token tree
*/
private function parse($source)
{
$parser = $this->getParser();
$parser->setPragmas($this->getPragmas());
return $parser->parse($this->tokenize($source));
}
/**
* Helper method to compile a Mustache template.
*
* @see Mustache_Compiler::compile
*
* @param string $source
*
* @return string generated Mustache template class code
*/
private function compile($source)
{
$tree = $this->parse($source);
$name = $this->getTemplateClassName($source);
$this->log(
Mustache_Logger::INFO,
'Compiling template to "{className}" class',
array('className' => $name)
);
$compiler = $this->getCompiler();
$compiler->setPragmas($this->getPragmas());
return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
}
/**
* Add a log record if logging is enabled.
*
* @param integer $level The logging level
* @param string $message The log message
* @param array $context The log context
*/
private function log($level, $message, array $context = array())
{
if (isset($this->logger)) {
$this->logger->log($level, $message, $context);
}
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache Exception interface.
*/
interface Mustache_Exception
{
// This space intentionally left blank.
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Invalid argument exception.
*/
class Mustache_Exception_InvalidArgumentException extends InvalidArgumentException implements Mustache_Exception
{
// This space intentionally left blank.
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Logic exception.
*/
class Mustache_Exception_LogicException extends LogicException implements Mustache_Exception
{
// This space intentionally left blank.
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Runtime exception.
*/
class Mustache_Exception_RuntimeException extends RuntimeException implements Mustache_Exception
{
// This space intentionally left blank.
}

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache syntax exception.
*/
class Mustache_Exception_SyntaxException extends LogicException implements Mustache_Exception
{
protected $token;
/**
* @param string $msg
* @param array $token
*/
public function __construct($msg, array $token)
{
$this->token = $token;
parent::__construct($msg);
}
/**
* @return array
*/
public function getToken()
{
return $this->token;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Unknown filter exception.
*/
class Mustache_Exception_UnknownFilterException extends UnexpectedValueException implements Mustache_Exception
{
protected $filterName;
/**
* @param string $filterName
*/
public function __construct($filterName)
{
$this->filterName = $filterName;
parent::__construct(sprintf('Unknown filter: %s', $filterName));
}
public function getFilterName()
{
return $this->filterName;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Unknown helper exception.
*/
class Mustache_Exception_UnknownHelperException extends InvalidArgumentException implements Mustache_Exception
{
protected $helperName;
/**
* @param string $helperName
*/
public function __construct($helperName)
{
$this->helperName = $helperName;
parent::__construct(sprintf('Unknown helper: %s', $helperName));
}
public function getHelperName()
{
return $this->helperName;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Unknown template exception.
*/
class Mustache_Exception_UnknownTemplateException extends InvalidArgumentException implements Mustache_Exception
{
protected $templateName;
/**
* @param string $templateName
*/
public function __construct($templateName)
{
$this->templateName = $templateName;
parent::__construct(sprintf('Unknown template: %s', $templateName));
}
public function getTemplateName()
{
return $this->templateName;
}
}

View File

@ -0,0 +1,172 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A collection of helpers for a Mustache instance.
*/
class Mustache_HelperCollection
{
private $helpers = array();
/**
* Helper Collection constructor.
*
* Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
*
* @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
*
* @param array|Traversable $helpers (default: null)
*/
public function __construct($helpers = null)
{
if ($helpers === null) {
return;
}
if (!is_array($helpers) && !$helpers instanceof Traversable) {
throw new Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers');
}
foreach ($helpers as $name => $helper) {
$this->add($name, $helper);
}
}
/**
* Magic mutator.
*
* @see Mustache_HelperCollection::add
*
* @param string $name
* @param mixed $helper
*/
public function __set($name, $helper)
{
$this->add($name, $helper);
}
/**
* Add a helper to this collection.
*
* @param string $name
* @param mixed $helper
*/
public function add($name, $helper)
{
$this->helpers[$name] = $helper;
}
/**
* Magic accessor.
*
* @see Mustache_HelperCollection::get
*
* @param string $name
*
* @return mixed Helper
*/
public function __get($name)
{
return $this->get($name);
}
/**
* Get a helper by name.
*
* @throws Mustache_Exception_UnknownHelperException If helper does not exist.
*
* @param string $name
*
* @return mixed Helper
*/
public function get($name)
{
if (!$this->has($name)) {
throw new Mustache_Exception_UnknownHelperException($name);
}
return $this->helpers[$name];
}
/**
* Magic isset().
*
* @see Mustache_HelperCollection::has
*
* @param string $name
*
* @return boolean True if helper is present
*/
public function __isset($name)
{
return $this->has($name);
}
/**
* Check whether a given helper is present in the collection.
*
* @param string $name
*
* @return boolean True if helper is present
*/
public function has($name)
{
return array_key_exists($name, $this->helpers);
}
/**
* Magic unset().
*
* @see Mustache_HelperCollection::remove
*
* @param string $name
*/
public function __unset($name)
{
$this->remove($name);
}
/**
* Check whether a given helper is present in the collection.
*
* @throws Mustache_Exception_UnknownHelperException if the requested helper is not present.
*
* @param string $name
*/
public function remove($name)
{
if (!$this->has($name)) {
throw new Mustache_Exception_UnknownHelperException($name);
}
unset($this->helpers[$name]);
}
/**
* Clear the helper collection.
*
* Removes all helpers from this collection
*/
public function clear()
{
$this->helpers = array();
}
/**
* Check whether the helper collection is empty.
*
* @return boolean True if the collection is empty
*/
public function isEmpty()
{
return empty($this->helpers);
}
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Lambda Helper.
*
* Passed as the second argument to section lambdas (higher order sections),
* giving them access to a `render` method for rendering a string with the
* current context.
*/
class Mustache_LambdaHelper
{
private $mustache;
private $context;
/**
* Mustache Lambda Helper constructor.
*
* @param Mustache_Engine $mustache Mustache engine instance.
* @param Mustache_Context $context Rendering context.
*/
public function __construct(Mustache_Engine $mustache, Mustache_Context $context)
{
$this->mustache = $mustache;
$this->context = $context;
}
/**
* Render a string as a Mustache template with the current rendering context.
*
* @param string $string
*
* @return string Rendered template.
*/
public function render($string)
{
return $this->mustache
->loadLambda((string) $string)
->renderInternal($this->context);
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template Loader interface.
*/
interface Mustache_Loader
{
/**
* Load a Template by name.
*
* @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name);
}

View File

@ -0,0 +1,79 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template array Loader implementation.
*
* An ArrayLoader instance loads Mustache Template source by name from an initial array:
*
* $loader = new ArrayLoader(
* 'foo' => '{{ bar }}',
* 'baz' => 'Hey {{ qux }}!'
* );
*
* $tpl = $loader->load('foo'); // '{{ bar }}'
*
* The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials
* is set. It can also be used as a quick-and-dirty Template loader.
*/
class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader
{
private $templates;
/**
* ArrayLoader constructor.
*
* @param array $templates Associative array of Template source (default: array())
*/
public function __construct(array $templates = array())
{
$this->templates = $templates;
}
/**
* Load a Template.
*
* @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name)
{
if (!isset($this->templates[$name])) {
throw new Mustache_Exception_UnknownTemplateException($name);
}
return $this->templates[$name];
}
/**
* Set an associative array of Template sources for this loader.
*
* @param array $templates
*/
public function setTemplates(array $templates)
{
$this->templates = $templates;
}
/**
* Set a Template source by name.
*
* @param string $name
* @param string $template Mustache Template source
*/
public function setTemplate($name, $template)
{
$this->templates[$name] = $template;
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache Template cascading loader implementation, which delegates to other
* Loader instances.
*/
class Mustache_Loader_CascadingLoader implements Mustache_Loader
{
private $loaders;
/**
* Construct a CascadingLoader with an array of loaders:
*
* $loader = new Mustache_Loader_CascadingLoader(array(
* new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__),
* new Mustache_Loader_FilesystemLoader(__DIR__.'/templates')
* ));
*
* @param Mustache_Loader[] $loaders
*/
public function __construct(array $loaders = array())
{
$this->loaders = array();
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
/**
* Add a Loader instance.
*
* @param Mustache_Loader $loader
*/
public function addLoader(Mustache_Loader $loader)
{
$this->loaders[] = $loader;
}
/**
* Load a Template by name.
*
* @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name)
{
foreach ($this->loaders as $loader) {
try {
return $loader->load($name);
} catch (Mustache_Exception_UnknownTemplateException $e) {
// do nothing, check the next loader.
}
}
throw new Mustache_Exception_UnknownTemplateException($name);
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template filesystem Loader implementation.
*
* A FilesystemLoader instance loads Mustache Template source from the filesystem by name:
*
* $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
* $tpl = $loader->load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache');
*
* This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
*
* $m = new Mustache(array(
* 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
* 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
* ));
*/
class Mustache_Loader_FilesystemLoader implements Mustache_Loader
{
private $baseDir;
private $extension = '.mustache';
private $templates = array();
/**
* Mustache filesystem Loader constructor.
*
* Passing an $options array allows overriding certain Loader options during instantiation:
*
* $options = array(
* // The filename extension used for Mustache templates. Defaults to '.mustache'
* 'extension' => '.ms',
* );
*
* @throws Mustache_Exception_RuntimeException if $baseDir does not exist.
*
* @param string $baseDir Base directory containing Mustache template files.
* @param array $options Array of Loader options (default: array())
*/
public function __construct($baseDir, array $options = array())
{
$this->baseDir = $baseDir;
if (strpos($this->baseDir, '://') === false) {
$this->baseDir = realpath($this->baseDir);
}
if (!is_dir($this->baseDir)) {
throw new Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir));
}
if (array_key_exists('extension', $options)) {
if (empty($options['extension'])) {
$this->extension = '';
} else {
$this->extension = '.' . ltrim($options['extension'], '.');
}
}
}
/**
* Load a Template by name.
*
* $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
* $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache";
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name)
{
if (!isset($this->templates[$name])) {
$this->templates[$name] = $this->loadFile($name);
}
return $this->templates[$name];
}
/**
* Helper function for loading a Mustache file by name.
*
* @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
*
* @param string $name
*
* @return string Mustache Template source
*/
protected function loadFile($name)
{
$fileName = $this->getFileName($name);
if (!file_exists($fileName)) {
throw new Mustache_Exception_UnknownTemplateException($name);
}
return file_get_contents($fileName);
}
/**
* Helper function for getting a Mustache template file name.
*
* @param string $name
*
* @return string Template file name
*/
protected function getFileName($name)
{
$fileName = $this->baseDir . '/' . $name;
if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
$fileName .= $this->extension;
}
return $fileName;
}
}

View File

@ -0,0 +1,123 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache Template loader for inline templates.
*
* With the InlineLoader, templates can be defined at the end of any PHP source
* file:
*
* $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
* $hello = $loader->load('hello');
* $goodbye = $loader->load('goodbye');
*
* __halt_compiler();
*
* @@ hello
* Hello, {{ planet }}!
*
* @@ goodbye
* Goodbye, cruel {{ planet }}
*
* Templates are deliniated by lines containing only `@@ name`.
*
* The InlineLoader is well-suited to micro-frameworks such as Silex:
*
* $app->register(new MustacheServiceProvider, array(
* 'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
* ));
*
* $app->get('/{name}', function ($name) use ($app) {
* return $app['mustache']->render('hello', compact('name'));
* })
* ->value('name', 'world');
*
* // ...
*
* __halt_compiler();
*
* @@ hello
* Hello, {{ name }}!
*
*/
class Mustache_Loader_InlineLoader implements Mustache_Loader
{
protected $fileName;
protected $offset;
protected $templates;
/**
* The InlineLoader requires a filename and offset to process templates.
* The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually
* perfectly suited to the job:
*
* $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
*
* Note that this only works if the loader is instantiated inside the same
* file as the inline templates. If the templates are located in another
* file, it would be necessary to manually specify the filename and offset.
*
* @param string $fileName The file to parse for inline templates
* @param int $offset A string offset for the start of the templates.
* This usually coincides with the `__halt_compiler`
* call, and the `__COMPILER_HALT_OFFSET__`.
*/
public function __construct($fileName, $offset)
{
if (!is_file($fileName)) {
throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.');
}
if (!is_int($offset) || $offset < 0) {
throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.');
}
$this->fileName = $fileName;
$this->offset = $offset;
}
/**
* Load a Template by name.
*
* @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name)
{
$this->loadTemplates();
if (!array_key_exists($name, $this->templates)) {
throw new Mustache_Exception_UnknownTemplateException($name);
}
return $this->templates[$name];
}
/**
* Parse and load templates from the end of a source file.
*/
protected function loadTemplates()
{
if ($this->templates === null) {
$this->templates = array();
$data = file_get_contents($this->fileName, false, null, $this->offset);
foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
if (trim($chunk)) {
list($name, $content) = explode("\n", $chunk, 2);
$this->templates[trim($name)] = trim($content);
}
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template mutable Loader interface.
*/
interface Mustache_Loader_MutableLoader
{
/**
* Set an associative array of Template sources for this loader.
*
* @param array $templates
*
* @return void
*/
public function setTemplates(array $templates);
/**
* Set a Template source by name.
*
* @param string $name
* @param string $template Mustache Template source
*
* @return void
*/
public function setTemplate($name, $template);
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template string Loader implementation.
*
* A StringLoader instance is essentially a noop. It simply passes the 'name' argument straight through:
*
* $loader = new StringLoader;
* $tpl = $loader->load('{{ foo }}'); // '{{ foo }}'
*
* This is the default Template Loader instance used by Mustache:
*
* $m = new Mustache;
* $tpl = $m->loadTemplate('{{ foo }}');
* echo $tpl->render(array('foo' => 'bar')); // "bar"
*/
class Mustache_Loader_StringLoader implements Mustache_Loader
{
/**
* Load a Template by source.
*
* @param string $name Mustache Template source
*
* @return string Mustache Template source
*/
public function load($name)
{
return $name;
}
}

View File

@ -0,0 +1,144 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Describes a Mustache logger instance
*
* This is identical to the Psr\Log\LoggerInterface.
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface Mustache_Logger
{
/**
* Psr\Log compatible log levels
*/
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return null
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return null
*/
public function log($level, $message, array $context = array());
}

View File

@ -0,0 +1,121 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* This is a simple Logger implementation that other Loggers can inherit from.
*
* This is identical to the Psr\Log\AbstractLogger.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*/
public function emergency($message, array $context = array())
{
$this->log(Mustache_Logger::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*/
public function alert($message, array $context = array())
{
$this->log(Mustache_Logger::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*/
public function critical($message, array $context = array())
{
$this->log(Mustache_Logger::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*/
public function error($message, array $context = array())
{
$this->log(Mustache_Logger::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*/
public function warning($message, array $context = array())
{
$this->log(Mustache_Logger::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*/
public function notice($message, array $context = array())
{
$this->log(Mustache_Logger::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*/
public function info($message, array $context = array())
{
$this->log(Mustache_Logger::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*/
public function debug($message, array $context = array())
{
$this->log(Mustache_Logger::DEBUG, $message, $context);
}
}

View File

@ -0,0 +1,194 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache Stream Logger.
*
* The Stream Logger wraps a file resource instance (such as a stream) or a
* stream URL. All log messages over the threshold level will be appended to
* this stream.
*
* Hint: Try `php://stderr` for your stream URL.
*/
class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
{
protected static $levels = array(
self::DEBUG => 100,
self::INFO => 200,
self::NOTICE => 250,
self::WARNING => 300,
self::ERROR => 400,
self::CRITICAL => 500,
self::ALERT => 550,
self::EMERGENCY => 600,
);
protected $level;
protected $stream = null;
protected $url = null;
/**
* @throws InvalidArgumentException if the logging level is unknown.
*
* @param resource|string $stream Resource instance or URL
* @param integer $level The minimum logging level at which this handler will be triggered
*/
public function __construct($stream, $level = Mustache_Logger::ERROR)
{
$this->setLevel($level);
if (is_resource($stream)) {
$this->stream = $stream;
} else {
$this->url = $stream;
}
}
/**
* Close stream resources.
*/
public function __destruct()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
}
/**
* Set the minimum logging level.
*
* @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
*
* @param integer $level The minimum logging level which will be written
*/
public function setLevel($level)
{
if (!array_key_exists($level, self::$levels)) {
throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
}
$this->level = $level;
}
/**
* Get the current minimum logging level.
*
* @return integer
*/
public function getLevel()
{
return $this->level;
}
/**
* Logs with an arbitrary level.
*
* @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
*
* @param mixed $level
* @param string $message
* @param array $context
*/
public function log($level, $message, array $context = array())
{
if (!array_key_exists($level, self::$levels)) {
throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
}
if (self::$levels[$level] >= self::$levels[$this->level]) {
$this->writeLog($level, $message, $context);
}
}
/**
* Write a record to the log.
*
* @throws Mustache_Exception_LogicException If neither a stream resource nor url is present.
* @throws Mustache_Exception_RuntimeException If the stream url cannot be opened.
*
* @param integer $level The logging level
* @param string $message The log message
* @param array $context The log context
*/
protected function writeLog($level, $message, array $context = array())
{
if (!is_resource($this->stream)) {
if (!isset($this->url)) {
throw new Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
}
$this->stream = fopen($this->url, 'a');
if (!is_resource($this->stream)) {
// @codeCoverageIgnoreStart
throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url));
// @codeCoverageIgnoreEnd
}
}
fwrite($this->stream, self::formatLine($level, $message, $context));
}
/**
* Gets the name of the logging level.
*
* @throws InvalidArgumentException if the logging level is unknown.
*
* @param integer $level
*
* @return string
*/
protected static function getLevelName($level)
{
return strtoupper($level);
}
/**
* Format a log line for output.
*
* @param integer $level The logging level
* @param string $message The log message
* @param array $context The log context
*
* @return string
*/
protected static function formatLine($level, $message, array $context = array())
{
return sprintf(
"%s: %s\n",
self::getLevelName($level),
self::interpolateMessage($message, $context)
);
}
/**
* Interpolate context values into the message placeholders.
*
* @param string $message
* @param array $context
*
* @return string
*/
protected static function interpolateMessage($message, array $context = array())
{
if (strpos($message, '{') === false) {
return $message;
}
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
// interpolate replacement values into the the message and return
return strtr($message, $replace);
}
}

View File

@ -0,0 +1,317 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Parser class.
*
* This class is responsible for turning a set of Mustache tokens into a parse tree.
*/
class Mustache_Parser
{
private $lineNum;
private $lineTokens;
private $pragmas;
private $defaultPragmas = array();
private $pragmaFilters;
private $pragmaBlocks;
/**
* Process an array of Mustache tokens and convert them into a parse tree.
*
* @param array $tokens Set of Mustache tokens
*
* @return array Mustache token parse tree
*/
public function parse(array $tokens = array())
{
$this->lineNum = -1;
$this->lineTokens = 0;
$this->pragmas = $this->defaultPragmas;
$this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
$this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);
return $this->buildTree($tokens);
}
/**
* Enable pragmas across all templates, regardless of the presence of pragma
* tags in the individual templates.
*
* @internal Users should set global pragmas in Mustache_Engine, not here :)
*
* @param string[] $pragmas
*/
public function setPragmas(array $pragmas)
{
$this->pragmas = array();
foreach ($pragmas as $pragma) {
$this->enablePragma($pragma);
}
$this->defaultPragmas = $this->pragmas;
}
/**
* Helper method for recursively building a parse tree.
*
* @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered.
*
* @param array &$tokens Set of Mustache tokens
* @param array $parent Parent token (default: null)
*
* @return array Mustache Token parse tree
*/
private function buildTree(array &$tokens, array $parent = null)
{
$nodes = array();
while (!empty($tokens)) {
$token = array_shift($tokens);
if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) {
$this->lineTokens++;
} else {
$this->lineNum = $token[Mustache_Tokenizer::LINE];
$this->lineTokens = 0;
}
if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
if (!empty($filters)) {
$token[Mustache_Tokenizer::NAME] = $name;
$token[Mustache_Tokenizer::FILTERS] = $filters;
}
}
switch ($token[Mustache_Tokenizer::TYPE]) {
case Mustache_Tokenizer::T_DELIM_CHANGE:
$this->checkIfTokenIsAllowedInParent($parent, $token);
$this->clearStandaloneLines($nodes, $tokens);
break;
case Mustache_Tokenizer::T_SECTION:
case Mustache_Tokenizer::T_INVERTED:
$this->checkIfTokenIsAllowedInParent($parent, $token);
$this->clearStandaloneLines($nodes, $tokens);
$nodes[] = $this->buildTree($tokens, $token);
break;
case Mustache_Tokenizer::T_END_SECTION:
if (!isset($parent)) {
$msg = sprintf(
'Unexpected closing tag: /%s on line %d',
$token[Mustache_Tokenizer::NAME],
$token[Mustache_Tokenizer::LINE]
);
throw new Mustache_Exception_SyntaxException($msg, $token);
}
if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
$msg = sprintf(
'Nesting error: %s (on line %d) vs. %s (on line %d)',
$parent[Mustache_Tokenizer::NAME],
$parent[Mustache_Tokenizer::LINE],
$token[Mustache_Tokenizer::NAME],
$token[Mustache_Tokenizer::LINE]
);
throw new Mustache_Exception_SyntaxException($msg, $token);
}
$this->clearStandaloneLines($nodes, $tokens);
$parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX];
$parent[Mustache_Tokenizer::NODES] = $nodes;
return $parent;
case Mustache_Tokenizer::T_PARTIAL:
$this->checkIfTokenIsAllowedInParent($parent, $token);
//store the whitespace prefix for laters!
if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
$token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
}
$nodes[] = $token;
break;
case Mustache_Tokenizer::T_PARENT:
$this->checkIfTokenIsAllowedInParent($parent, $token);
$nodes[] = $this->buildTree($tokens, $token);
break;
case Mustache_Tokenizer::T_BLOCK_VAR:
if ($this->pragmaBlocks) {
// BLOCKS pragma is enabled, let's do this!
if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
$token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
}
$this->clearStandaloneLines($nodes, $tokens);
$nodes[] = $this->buildTree($tokens, $token);
} else {
// pretend this was just a normal "escaped" token...
$token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
// TODO: figure out how to figure out if there was a space after this dollar:
$token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME];
$nodes[] = $token;
}
break;
case Mustache_Tokenizer::T_PRAGMA:
$this->enablePragma($token[Mustache_Tokenizer::NAME]);
// no break
case Mustache_Tokenizer::T_COMMENT:
$this->clearStandaloneLines($nodes, $tokens);
$nodes[] = $token;
break;
default:
$nodes[] = $token;
break;
}
}
if (isset($parent)) {
$msg = sprintf(
'Missing closing tag: %s opened on line %d',
$parent[Mustache_Tokenizer::NAME],
$parent[Mustache_Tokenizer::LINE]
);
throw new Mustache_Exception_SyntaxException($msg, $parent);
}
return $nodes;
}
/**
* Clear standalone line tokens.
*
* Returns a whitespace token for indenting partials, if applicable.
*
* @param array $nodes Parsed nodes.
* @param array $tokens Tokens to be parsed.
*
* @return array|null Resulting indent token, if any.
*/
private function clearStandaloneLines(array &$nodes, array &$tokens)
{
if ($this->lineTokens > 1) {
// this is the third or later node on this line, so it can't be standalone
return;
}
$prev = null;
if ($this->lineTokens === 1) {
// this is the second node on this line, so it can't be standalone
// unless the previous node is whitespace.
if ($prev = end($nodes)) {
if (!$this->tokenIsWhitespace($prev)) {
return;
}
}
}
if ($next = reset($tokens)) {
// If we're on a new line, bail.
if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
return;
}
// If the next token isn't whitespace, bail.
if (!$this->tokenIsWhitespace($next)) {
return;
}
if (count($tokens) !== 1) {
// Unless it's the last token in the template, the next token
// must end in newline for this to be standalone.
if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
return;
}
}
// Discard the whitespace suffix
array_shift($tokens);
}
if ($prev) {
// Return the whitespace prefix, if any
return array_pop($nodes);
}
}
/**
* Check whether token is a whitespace token.
*
* True if token type is T_TEXT and value is all whitespace characters.
*
* @param array $token
*
* @return boolean True if token is a whitespace token
*/
private function tokenIsWhitespace(array $token)
{
if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
}
return false;
}
/**
* Check whether a token is allowed inside a parent tag.
*
* @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag.
*
* @param array|null $parent
* @param array $token
*/
private function checkIfTokenIsAllowedInParent($parent, array $token)
{
if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token);
}
}
/**
* Split a tag name into name and filters.
*
* @param string $name
*
* @return array [Tag name, Array of filters]
*/
private function getNameAndFilters($name)
{
$filters = array_map('trim', explode('|', $name));
$name = array_shift($filters);
return array($name, $filters);
}
/**
* Enable a pragma.
*
* @param string $name
*/
private function enablePragma($name)
{
$this->pragmas[$name] = true;
switch ($name) {
case Mustache_Engine::PRAGMA_BLOCKS:
$this->pragmaBlocks = true;
break;
case Mustache_Engine::PRAGMA_FILTERS:
$this->pragmaFilters = true;
break;
}
}
}

View File

@ -0,0 +1,181 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract Mustache Template class.
*
* @abstract
*/
abstract class Mustache_Template
{
/**
* @var Mustache_Engine
*/
protected $mustache;
/**
* @var boolean
*/
protected $strictCallables = false;
/**
* Mustache Template constructor.
*
* @param Mustache_Engine $mustache
*/
public function __construct(Mustache_Engine $mustache)
{
$this->mustache = $mustache;
}
/**
* Mustache Template instances can be treated as a function and rendered by simply calling them:
*
* $m = new Mustache_Engine;
* $tpl = $m->loadTemplate('Hello, {{ name }}!');
* echo $tpl(array('name' => 'World')); // "Hello, World!"
*
* @see Mustache_Template::render
*
* @param mixed $context Array or object rendering context (default: array())
*
* @return string Rendered template
*/
public function __invoke($context = array())
{
return $this->render($context);
}
/**
* Render this template given the rendering context.
*
* @param mixed $context Array or object rendering context (default: array())
*
* @return string Rendered template
*/
public function render($context = array())
{
return $this->renderInternal(
$this->prepareContextStack($context)
);
}
/**
* Internal rendering method implemented by Mustache Template concrete subclasses.
*
* This is where the magic happens :)
*
* NOTE: This method is not part of the Mustache.php public API.
*
* @param Mustache_Context $context
* @param string $indent (default: '')
*
* @return string Rendered template
*/
abstract public function renderInternal(Mustache_Context $context, $indent = '');
/**
* Tests whether a value should be iterated over (e.g. in a section context).
*
* In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
* should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
* Java, Python, etc.
*
* PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
* between between a list of things (numeric, normalized array) and a set of variables to be used as section context
* (associative array). In other words, this will be iterated over:
*
* $items = array(
* array('name' => 'foo'),
* array('name' => 'bar'),
* array('name' => 'baz'),
* );
*
* ... but this will be used as a section context block:
*
* $items = array(
* 1 => array('name' => 'foo'),
* 'banana' => array('name' => 'bar'),
* 42 => array('name' => 'baz'),
* );
*
* @param mixed $value
*
* @return boolean True if the value is 'iterable'
*/
protected function isIterable($value)
{
switch (gettype($value)) {
case 'object':
return $value instanceof Traversable;
case 'array':
$i = 0;
foreach ($value as $k => $v) {
if ($k !== $i++) {
return false;
}
}
return true;
default:
return false;
}
}
/**
* Helper method to prepare the Context stack.
*
* Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
*
* @param mixed $context Optional first context frame (default: null)
*
* @return Mustache_Context
*/
protected function prepareContextStack($context = null)
{
$stack = new Mustache_Context();
$helpers = $this->mustache->getHelpers();
if (!$helpers->isEmpty()) {
$stack->push($helpers);
}
if (!empty($context)) {
$stack->push($context);
}
return $stack;
}
/**
* Resolve a context value.
*
* Invoke the value if it is callable, otherwise return the value.
*
* @param mixed $value
* @param Mustache_Context $context
* @param string $indent
*
* @return string
*/
protected function resolveValue($value, Mustache_Context $context, $indent = '')
{
if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) {
return $this->mustache
->loadLambda((string) call_user_func($value))
->renderInternal($context, $indent);
}
return $value;
}
}

View File

@ -0,0 +1,331 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2010-2014 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Tokenizer class.
*
* This class is responsible for turning raw template source into a set of Mustache tokens.
*/
class Mustache_Tokenizer
{
// Finite state machine states
const IN_TEXT = 0;
const IN_TAG_TYPE = 1;
const IN_TAG = 2;
// Token types
const T_SECTION = '#';
const T_INVERTED = '^';
const T_END_SECTION = '/';
const T_COMMENT = '!';
const T_PARTIAL = '>';
const T_PARENT = '<';
const T_DELIM_CHANGE = '=';
const T_ESCAPED = '_v';
const T_UNESCAPED = '{';
const T_UNESCAPED_2 = '&';
const T_TEXT = '_t';
const T_PRAGMA = '%';
const T_BLOCK_VAR = '$';
const T_BLOCK_ARG = '$arg';
// Valid token types
private static $tagTypes = array(
self::T_SECTION => true,
self::T_INVERTED => true,
self::T_END_SECTION => true,
self::T_COMMENT => true,
self::T_PARTIAL => true,
self::T_PARENT => true,
self::T_DELIM_CHANGE => true,
self::T_ESCAPED => true,
self::T_UNESCAPED => true,
self::T_UNESCAPED_2 => true,
self::T_PRAGMA => true,
self::T_BLOCK_VAR => true,
);
// Interpolated tags
private static $interpolatedTags = array(
self::T_ESCAPED => true,
self::T_UNESCAPED => true,
self::T_UNESCAPED_2 => true,
);
// Token properties
const TYPE = 'type';
const NAME = 'name';
const OTAG = 'otag';
const CTAG = 'ctag';
const LINE = 'line';
const INDEX = 'index';
const END = 'end';
const INDENT = 'indent';
const NODES = 'nodes';
const VALUE = 'value';
const FILTERS = 'filters';
private $state;
private $tagType;
private $tag;
private $buffer;
private $tokens;
private $seenTag;
private $line;
private $otag;
private $ctag;
private $otagLen;
private $ctagLen;
/**
* Scan and tokenize template source.
*
* @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered.
*
* @param string $text Mustache template source to tokenize
* @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null)
*
* @return array Set of Mustache tokens
*/
public function scan($text, $delimiters = null)
{
// Setting mbstring.func_overload makes things *really* slow.
// Let's do everyone a favor and scan this string as ASCII instead.
$encoding = null;
if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
$encoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
}
$this->reset();
if ($delimiters = trim($delimiters)) {
$this->setDelimiters($delimiters);
}
$len = strlen($text);
for ($i = 0; $i < $len; $i++) {
switch ($this->state) {
case self::IN_TEXT:
if ($this->tagChange($this->otag, $this->otagLen, $text, $i)) {
$i--;
$this->flushBuffer();
$this->state = self::IN_TAG_TYPE;
} else {
$char = $text[$i];
$this->buffer .= $char;
if ($char === "\n") {
$this->flushBuffer();
$this->line++;
}
}
break;
case self::IN_TAG_TYPE:
$i += $this->otagLen - 1;
$char = $text[$i + 1];
if (isset(self::$tagTypes[$char])) {
$tag = $char;
$this->tagType = $tag;
} else {
$tag = null;
$this->tagType = self::T_ESCAPED;
}
if ($this->tagType === self::T_DELIM_CHANGE) {
$i = $this->changeDelimiters($text, $i);
$this->state = self::IN_TEXT;
} elseif ($this->tagType === self::T_PRAGMA) {
$i = $this->addPragma($text, $i);
$this->state = self::IN_TEXT;
} else {
if ($tag !== null) {
$i++;
}
$this->state = self::IN_TAG;
}
$this->seenTag = $i;
break;
default:
if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) {
$token = array(
self::TYPE => $this->tagType,
self::NAME => trim($this->buffer),
self::OTAG => $this->otag,
self::CTAG => $this->ctag,
self::LINE => $this->line,
self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen
);
if ($this->tagType === self::T_UNESCAPED) {
// Clean up `{{{ tripleStache }}}` style tokens.
if ($this->ctag === '}}') {
if (($i + 2 < $len) && $text[$i + 2] === '}') {
$i++;
} else {
$msg = sprintf(
'Mismatched tag delimiters: %s on line %d',
$token[self::NAME],
$token[self::LINE]
);
throw new Mustache_Exception_SyntaxException($msg, $token);
}
} else {
$lastName = $token[self::NAME];
if (substr($lastName, -1) === '}') {
$token[self::NAME] = trim(substr($lastName, 0, -1));
} else {
$msg = sprintf(
'Mismatched tag delimiters: %s on line %d',
$token[self::NAME],
$token[self::LINE]
);
throw new Mustache_Exception_SyntaxException($msg, $token);
}
}
}
$this->buffer = '';
$i += $this->ctagLen - 1;
$this->state = self::IN_TEXT;
$this->tokens[] = $token;
} else {
$this->buffer .= $text[$i];
}
break;
}
}
$this->flushBuffer();
// Restore the user's encoding...
if ($encoding) {
mb_internal_encoding($encoding);
}
return $this->tokens;
}
/**
* Helper function to reset tokenizer internal state.
*/
private function reset()
{
$this->state = self::IN_TEXT;
$this->tagType = null;
$this->tag = null;
$this->buffer = '';
$this->tokens = array();
$this->seenTag = false;
$this->line = 0;
$this->otag = '{{';
$this->ctag = '}}';
$this->otagLen = 2;
$this->ctagLen = 2;
}
/**
* Flush the current buffer to a token.
*/
private function flushBuffer()
{
if (strlen($this->buffer) > 0) {
$this->tokens[] = array(
self::TYPE => self::T_TEXT,
self::LINE => $this->line,
self::VALUE => $this->buffer
);
$this->buffer = '';
}
}
/**
* Change the current Mustache delimiters. Set new `otag` and `ctag` values.
*
* @param string $text Mustache template source
* @param int $index Current tokenizer index
*
* @return int New index value
*/
private function changeDelimiters($text, $index)
{
$startIndex = strpos($text, '=', $index) + 1;
$close = '='.$this->ctag;
$closeIndex = strpos($text, $close, $index);
$this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
$this->tokens[] = array(
self::TYPE => self::T_DELIM_CHANGE,
self::LINE => $this->line,
);
return $closeIndex + strlen($close) - 1;
}
/**
* Set the current Mustache `otag` and `ctag` delimiters.
*
* @param string $delimiters
*/
private function setDelimiters($delimiters)
{
list($otag, $ctag) = explode(' ', $delimiters);
$this->otag = $otag;
$this->ctag = $ctag;
$this->otagLen = strlen($otag);
$this->ctagLen = strlen($ctag);
}
/**
* Add pragma token.
*
* Pragmas are hoisted to the front of the template, so all pragma tokens
* will appear at the front of the token list.
*
* @param string $text
* @param int $index
*
* @return int New index value
*/
private function addPragma($text, $index)
{
$end = strpos($text, $this->ctag, $index);
$pragma = trim(substr($text, $index + 2, $end - $index - 2));
// Pragmas are hoisted to the front of the template.
array_unshift($this->tokens, array(
self::TYPE => self::T_PRAGMA,
self::NAME => $pragma,
self::LINE => 0,
));
return $end + $this->ctagLen - 1;
}
/**
* Test whether it's time to change tags.
*
* @param string $tag Current tag name
* @param int $tagLen Current tag name length
* @param string $text Mustache template source
* @param int $index Current tokenizer index
*
* @return boolean True if this is a closing section tag
*/
private function tagChange($tag, $tagLen, $text, $index)
{
return substr($text, $index, $tagLen) === $tag;
}
}

View File

@ -39,6 +39,28 @@ interface renderable {
// intentionally empty
}
/**
* Interface marking other classes having the ability to export their data for use by templates.
*
* @copyright 2015 Damyon Wiese
* @package core
* @category output
* @since 2.9
*/
interface templatable {
/**
* Function to export the renderer data in a format that is suitable for a
* mustache template. This means:
* 1. No complex types - only stdClass, array, int, string, float, bool
* 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks).
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output);
}
/**
* Data structure representing a file picker.
*
@ -602,6 +624,16 @@ class single_button implements renderable {
*/
var $actions = array();
/**
* @var array $params URL Params
*/
var $params;
/**
* @var string Action id
*/
var $actionid;
/**
* Constructor
* @param moodle_url $url

View File

@ -337,7 +337,10 @@ class standard_renderer_factory extends renderer_factory_base {
throw new coding_exception('Request for an unknown renderer class. Searched for: ' . var_export($classnames, true));
}
return new $classname($page, $target);
$renderer = new $classname($page, $target);
$renderer->set_component($component);
$renderer->set_subtype($subtype);
return $renderer;
}
}
@ -401,7 +404,10 @@ class theme_overridden_renderer_factory extends renderer_factory_base {
$newclassname = $prefix . '_' . $classnamedetails['classname'] . $suffix;
}
if (class_exists($newclassname)) {
return new $newclassname($page, $target);
$renderer = new $newclassname($page, $target);
$renderer->set_component($component);
$renderer->set_subtype($subtype);
return $renderer;
}
}
}
@ -412,7 +418,10 @@ class theme_overridden_renderer_factory extends renderer_factory_base {
if (class_exists($newclassname)) {
// Use the specialised renderer for given target, default renderer might also decide
// to implement support for more targets.
return new $newclassname($page, $target);
$renderer = new $newclassname($page, $target);
$renderer->set_component($component);
$renderer->set_subtype($subtype);
return $renderer;
}
}
}
@ -427,7 +436,10 @@ class theme_overridden_renderer_factory extends renderer_factory_base {
$newclassname = $prefix . '_' . $classnamedetails['classname'];
}
if (class_exists($newclassname)) {
return new $newclassname($page, $target);
$renderer = new $newclassname($page, $target);
$renderer->set_component($component);
$renderer->set_subtype($subtype);
return $renderer;
}
}
}
@ -438,7 +450,10 @@ class theme_overridden_renderer_factory extends renderer_factory_base {
if ($classnamedetails['validwithoutprefix']) {
$newclassname = $classnamedetails['classname'];
if (class_exists($newclassname)) {
return new $newclassname($page, $target);
$renderer = new $newclassname($page, $target);
$renderer->set_component($component);
$renderer->set_subtype($subtype);
return $renderer;
}
}
}

View File

@ -66,6 +66,129 @@ class renderer_base {
*/
protected $target;
/**
* @var Mustache_Engine $mustache The mustache template compiler
*/
private $mustache;
/**
* @var string $component The component used when requesting this renderer.
*/
private $component;
/**
* @var string $subtype The subtype used when requesting this renderer.
*/
private $subtype;
/**
* This is not done in the constructor because that would be a
* compatibility breaking change, and we can just pass this always in the
* renderer factory, immediately after creating the renderer.
* @since 2.9
* @param string $subtype
*/
public function set_subtype($subtype) {
$this->subtype = $subtype;
}
/**
* This is not done in the constructor because that would be a
* compatibility breaking change, and we can just pass this always in the
* renderer factory, immediately after creating the renderer.
* @since 2.9
* @param string $component
*/
public function set_component($component) {
$this->component = $component;
}
/**
* Return an instance of the mustache class.
*
* @since 2.9
* @return Mustache_Engine
*/
protected function get_mustache() {
global $CFG;
if ($this->mustache === null) {
require_once($CFG->dirroot . '/lib/mustache/src/Mustache/Autoloader.php');
Mustache_Autoloader::register();
$themename = $this->page->theme->name;
$themerev = theme_get_revision();
$target = $this->target;
$cachedir = make_localcache_directory("mustache/$themerev/$themename/$target");
$loaderoptions = array();
// Where are all the places we should look for templates?
$suffix = $this->component;
if ($this->subtype !== null) {
$suffix .= '_' . $this->subtype;
}
// Start with an empty list.
$loader = new Mustache_Loader_CascadingLoader(array());
$loaderdir = $CFG->dirroot . '/theme/' . $themename . '/templates/' . $suffix;
if (is_dir($loaderdir)) {
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
}
// Search each of the parent themes second.
foreach ($this->page->theme->parents as $parent) {
$loaderdir = $CFG->dirroot . '/theme/' . $parent . '/templates/' . $suffix;
if (is_dir($loaderdir)) {
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
}
}
// Look in a components templates dir for a base implementation.
$compdirectory = core_component::get_component_directory($suffix);
if ($compdirectory) {
$loaderdir = $compdirectory . '/templates';
if (is_dir($loaderdir)) {
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
}
}
// Look in the core templates dir as a final fallback.
$compdirectory = $CFG->libdir;
if ($compdirectory) {
$loaderdir = $compdirectory . '/templates';
if (is_dir($loaderdir)) {
$loader->addLoader(new \core\output\mustache_filesystem_loader($loaderdir, $loaderoptions));
}
}
$stringhelper = new \core\output\mustache_string_helper();
$jshelper = new \core\output\mustache_javascript_helper($this->page->requires);
$pixhelper = new \core\output\mustache_pix_helper($this);
// We only expose the variables that are exposed to JS templates.
$safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this);
$helpers = array('config' => $safeconfig,
'str' => array($stringhelper, 'str'),
'js' => array($jshelper, 'help'),
'pix' => array($pixhelper, 'pix'));
$this->mustache = new Mustache_Engine(array(
'cache' => $cachedir,
'escape' => 's',
'loader' => $loader,
'helpers' => $helpers));
}
return $this->mustache;
}
/**
* Constructor
*
@ -83,6 +206,39 @@ class renderer_base {
$this->target = $target;
}
/**
* Renders a template by name with the given context.
*
* The provided data needs to be array/stdClass made up of only simple types.
* Simple types are array,stdClass,bool,int,float,string
*
* @since 2.9
* @param array|stdClass $context Context containing data for the template.
* @return string|boolean
*/
public function render_from_template($templatename, $context) {
static $templatecache = array();
$mustache = $this->get_mustache();
// Provide 1 random value that will not change within a template
// but will be different from template to template. This is useful for
// e.g. aria attributes that only work with id attributes and must be
// unique in a page.
$mustache->addHelper('uniqid', new \core\output\mustache_uniqid_helper());
if (isset($templatecache[$templatename])) {
$template = $templatecache[$templatename];
} else {
try {
$template = $mustache->loadTemplate($templatename);
$templatecache[$templatename] = $template;
} catch (Mustache_Exception_UnknownTemplateException $e) {
throw new moodle_exception('Unknown template: ' . $templatename);
}
}
return trim($template->render($context));
}
/**
* Returns rendered widget.
*
@ -2016,7 +2172,11 @@ class core_renderer extends renderer_base {
protected function render_pix_icon(pix_icon $icon) {
$attributes = $icon->attributes;
$attributes['src'] = $this->pix_url($icon->pix, $icon->component);
return html_writer::empty_tag('img', $attributes);
$templatecontext = array();
foreach ($attributes as $name => $value) {
$templatecontext[] = array('name' => $name, 'value' => $value);
}
return $this->render_from_template('core/pix_icon', array('attributes' => $templatecontext));
}
/**

View File

@ -293,6 +293,42 @@ class page_requirements_manager {
$this->js_module($this->find_module('core_filepicker'));
}
/**
* Return the safe config values that get set for javascript in "M.cfg".
*
* @since 2.9
* @return array List of safe config values that are available to javascript.
*/
public function get_config_for_javascript(moodle_page $page, renderer_base $renderer) {
global $CFG;
if (empty($this->M_cfg)) {
// JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot.
// Otherwise, in some situations, users will get warnings about insecure content
// on secure pages from their web browser.
$this->M_cfg = array(
'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above.
'sesskey' => sesskey(),
'loadingicon' => $renderer->pix_url('i/loading_small', 'moodle')->out(false),
'themerev' => theme_get_revision(),
'slasharguments' => (int)(!empty($CFG->slasharguments)),
'theme' => $page->theme->name,
'jsrev' => $this->get_jsrev(),
'admin' => $CFG->admin,
'svgicons' => $page->theme->use_svg_icons()
);
if ($CFG->debugdeveloper) {
$this->M_cfg['developerdebug'] = true;
}
if (defined('BEHAT_SITE_RUNNING')) {
$this->M_cfg['behatsiterunning'] = true;
}
}
return $this->M_cfg;
}
/**
* Initialise with the bits of JavaScript that every Moodle page should have.
*
@ -302,26 +338,8 @@ class page_requirements_manager {
protected function init_requirements_data(moodle_page $page, core_renderer $renderer) {
global $CFG;
// JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot.
// Otherwise, in some situations, users will get warnings about insecure content
// on secure pages from their web browser.
$this->M_cfg = array(
'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above.
'sesskey' => sesskey(),
'loadingicon' => $renderer->pix_url('i/loading_small', 'moodle')->out(false),
'themerev' => theme_get_revision(),
'slasharguments' => (int)(!empty($CFG->slasharguments)),
'theme' => $page->theme->name,
'jsrev' => $this->get_jsrev(),
'svgicons' => $page->theme->use_svg_icons()
);
if ($CFG->debugdeveloper) {
$this->M_cfg['developerdebug'] = true;
}
if (defined('BEHAT_SITE_RUNNING')) {
$this->M_cfg['behatsiterunning'] = true;
}
// Init the js config.
$this->get_config_for_javascript($page, $renderer);
// Accessibility stuff.
$this->skip_link_to('maincontent', get_string('tocontent', 'access'));

View File

@ -0,0 +1,31 @@
{{!
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/>.
}}
{{!
Moodle pix_icon template.
The purpose of this template is to render a pix_icon.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* attributes Array of name / value pairs.
}}
<img {{#attributes}}{{name}}="{{value}}" {{/attributes}}/>

View File

@ -265,4 +265,16 @@
<license>MIT</license>
<version>1.2.0</version>
</library>
<library>
<location>mustache</location>
<name>Mustache</name>
<license>MIT</license>
<version>2.7.0</version>
</library>
<library>
<location>amd/src/mustache.js</location>
<name>Mustache.js</name>
<license>MIT</license>
<version>1.0.0</version>
</library>
</libraries>

View File

@ -3,6 +3,7 @@ information provided here is intended especially for developers.
=== 2.9 ===
* Support for rendering templates from php or javascript has been added. See MDL-49152.
* Support for loading AMD javascript modules has been added. See MDL-49046.
* Webservice core_course_delete_courses now return warning messages on any failures and does not try to rollback the entire deletion.
* \core\event\course_viewed 'other' argument renamed from coursesectionid to coursesectionnumber as it contains the section number.