mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
Merge branch 'MDL-49152-master' of git://github.com/damyon/moodle
This commit is contained in:
commit
f4e1913530
2
lib/amd/build/ajax.min.js
vendored
2
lib/amd/build/ajax.min.js
vendored
@ -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
1
lib/amd/build/mustache.min.js
vendored
Normal 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={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},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
1
lib/amd/build/notification.min.js
vendored
Normal 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
1
lib/amd/build/str.min.js
vendored
Normal 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
1
lib/amd/build/templates.min.js
vendored
Normal 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
1
lib/amd/build/url.min.js
vendored
Normal 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
1
lib/amd/build/yui.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
define(function(){return Y});
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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
608
lib/amd/src/mustache.js
Normal 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 = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"/": '/'
|
||||
};
|
||||
|
||||
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
105
lib/amd/src/notification.js
Normal 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
141
lib/amd/src/str.js
Normal 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
374
lib/amd/src/templates.js
Normal 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
91
lib/amd/src/url.js
Normal 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
30
lib/amd/src/yui.js
vendored
Normal 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;
|
||||
});
|
133
lib/classes/output/external.php
Normal file
133
lib/classes/output/external.php
Normal 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');
|
||||
}
|
||||
}
|
||||
|
54
lib/classes/output/mustache_filesystem_loader.php
Normal file
54
lib/classes/output/mustache_filesystem_loader.php
Normal 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);
|
||||
}
|
||||
}
|
59
lib/classes/output/mustache_javascript_helper.php
Normal file
59
lib/classes/output/mustache_javascript_helper.php
Normal 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));
|
||||
}
|
||||
}
|
78
lib/classes/output/mustache_pix_helper.php
Normal file
78
lib/classes/output/mustache_pix_helper.php
Normal 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));
|
||||
}
|
||||
}
|
||||
|
79
lib/classes/output/mustache_string_helper.php
Normal file
79
lib/classes/output/mustache_string_helper.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
52
lib/classes/output/mustache_uniqid_helper.php
Normal file
52
lib/classes/output/mustache_uniqid_helper.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
35
lib/mustache/CONTRIBUTING.md
Normal file
35
lib/mustache/CONTRIBUTING.md
Normal 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
21
lib/mustache/LICENSE
Normal 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
71
lib/mustache/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
Mustache.php
|
||||
============
|
||||
|
||||
A [Mustache](http://mustache.github.com/) implementation in PHP.
|
||||
|
||||
[](https://packagist.org/packages/mustache/mustache)
|
||||
[](http://travis-ci.org/bobthecow/mustache.php)
|
||||
[](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.
|
24
lib/mustache/composer.json
Normal file
24
lib/mustache/composer.json
Normal 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/" }
|
||||
}
|
||||
}
|
11
lib/mustache/readme_moodle.txt
Normal file
11
lib/mustache/readme_moodle.txt
Normal 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"
|
74
lib/mustache/src/Mustache/Autoloader.php
Normal file
74
lib/mustache/src/Mustache/Autoloader.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
lib/mustache/src/Mustache/Cache.php
Normal file
38
lib/mustache/src/Mustache/Cache.php
Normal 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);
|
||||
}
|
60
lib/mustache/src/Mustache/Cache/AbstractCache.php
Normal file
60
lib/mustache/src/Mustache/Cache/AbstractCache.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
159
lib/mustache/src/Mustache/Cache/FilesystemCache.php
Normal file
159
lib/mustache/src/Mustache/Cache/FilesystemCache.php
Normal 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));
|
||||
}
|
||||
}
|
49
lib/mustache/src/Mustache/Cache/NoopCache.php
Normal file
49
lib/mustache/src/Mustache/Cache/NoopCache.php
Normal 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);
|
||||
}
|
||||
}
|
646
lib/mustache/src/Mustache/Compiler.php
Normal file
646
lib/mustache/src/Mustache/Compiler.php
Normal 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;
|
||||
}
|
||||
}
|
206
lib/mustache/src/Mustache/Context.php
Normal file
206
lib/mustache/src/Mustache/Context.php
Normal 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 '';
|
||||
}
|
||||
}
|
785
lib/mustache/src/Mustache/Engine.php
Normal file
785
lib/mustache/src/Mustache/Engine.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
18
lib/mustache/src/Mustache/Exception.php
Normal file
18
lib/mustache/src/Mustache/Exception.php
Normal 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.
|
||||
}
|
@ -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.
|
||||
}
|
18
lib/mustache/src/Mustache/Exception/LogicException.php
Normal file
18
lib/mustache/src/Mustache/Exception/LogicException.php
Normal 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.
|
||||
}
|
18
lib/mustache/src/Mustache/Exception/RuntimeException.php
Normal file
18
lib/mustache/src/Mustache/Exception/RuntimeException.php
Normal 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.
|
||||
}
|
36
lib/mustache/src/Mustache/Exception/SyntaxException.php
Normal file
36
lib/mustache/src/Mustache/Exception/SyntaxException.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
172
lib/mustache/src/Mustache/HelperCollection.php
Normal file
172
lib/mustache/src/Mustache/HelperCollection.php
Normal 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);
|
||||
}
|
||||
}
|
49
lib/mustache/src/Mustache/LambdaHelper.php
Normal file
49
lib/mustache/src/Mustache/LambdaHelper.php
Normal 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);
|
||||
}
|
||||
}
|
27
lib/mustache/src/Mustache/Loader.php
Normal file
27
lib/mustache/src/Mustache/Loader.php
Normal 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);
|
||||
}
|
79
lib/mustache/src/Mustache/Loader/ArrayLoader.php
Normal file
79
lib/mustache/src/Mustache/Loader/ArrayLoader.php
Normal 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;
|
||||
}
|
||||
}
|
69
lib/mustache/src/Mustache/Loader/CascadingLoader.php
Normal file
69
lib/mustache/src/Mustache/Loader/CascadingLoader.php
Normal 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);
|
||||
}
|
||||
}
|
124
lib/mustache/src/Mustache/Loader/FilesystemLoader.php
Normal file
124
lib/mustache/src/Mustache/Loader/FilesystemLoader.php
Normal 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;
|
||||
}
|
||||
}
|
123
lib/mustache/src/Mustache/Loader/InlineLoader.php
Normal file
123
lib/mustache/src/Mustache/Loader/InlineLoader.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
lib/mustache/src/Mustache/Loader/MutableLoader.php
Normal file
35
lib/mustache/src/Mustache/Loader/MutableLoader.php
Normal 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);
|
||||
}
|
39
lib/mustache/src/Mustache/Loader/StringLoader.php
Normal file
39
lib/mustache/src/Mustache/Loader/StringLoader.php
Normal 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;
|
||||
}
|
||||
}
|
144
lib/mustache/src/Mustache/Logger.php
Normal file
144
lib/mustache/src/Mustache/Logger.php
Normal 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());
|
||||
}
|
121
lib/mustache/src/Mustache/Logger/AbstractLogger.php
Normal file
121
lib/mustache/src/Mustache/Logger/AbstractLogger.php
Normal 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);
|
||||
}
|
||||
}
|
194
lib/mustache/src/Mustache/Logger/StreamLogger.php
Normal file
194
lib/mustache/src/Mustache/Logger/StreamLogger.php
Normal 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);
|
||||
}
|
||||
}
|
317
lib/mustache/src/Mustache/Parser.php
Normal file
317
lib/mustache/src/Mustache/Parser.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
181
lib/mustache/src/Mustache/Template.php
Normal file
181
lib/mustache/src/Mustache/Template.php
Normal 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;
|
||||
}
|
||||
}
|
331
lib/mustache/src/Mustache/Tokenizer.php
Normal file
331
lib/mustache/src/Mustache/Tokenizer.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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'));
|
||||
|
31
lib/templates/pix_icon.mustache
Normal file
31
lib/templates/pix_icon.mustache
Normal 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}}/>
|
@ -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>
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user