MDL-52136 core: Add support for quoting variables in mustache helpers
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 12 Feb 2016 04:42:41 +0000 (12:42 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 2 Mar 2016 00:48:46 +0000 (08:48 +0800)
This is required for when helpers include json-encoded variables as arguments.
As an example, imagine a template with content:

{{# str }} somekey, someidentifier, { "fullname": "{{ fullname }}" } {{/ str }}

If the fullname variable were to include the double-quote character (e.g.
John "Trevor" Doe) because of the way in which mustache renders content, it
would become:

{{# str }} somekey, someidentifier, { "fullname": "John "Trevor" Doe" } {{/ str }}

This results in an invalid JSON structure.

To work around this issue, the quote characters in the passed variable
must be escaped:

{{# str }} somekey, someidentifier, { "fullname": "John \"Trevor\" Doe" } {{/ str }}

Unfortunately, Mustache provides no way of doing so natively.

With this function, we can quote the text as appropriate:

{{# str }} somekey, someidentifier, { "fullname": {{# quote }}{{ fullname }}{{/ quote }} } {{/ str }}

This also handles the case where the quoted content includes the Mustache
delimeter ({{ or }}).

For example:
fullname = 'John "}}Trevor{{" Doe'

Ordinarily this would be rendered as:
{{# str }} somekey, someidentifier, { "fullname": "John "}}Trevor{{" Doe" } {{/ str }}

This rendering is both a JSON error, and also a mustache syntax error because of the mustache delimeters.

The quote helper also escapes these by wrapping them in change delimeter
tags:

{{# str }} somekey, someidentifier, { "fullname": "John "{{=<% %>=}}}}<%={{ }}=%>Trevor{{=<% %>=}}{{{{=<% %>=}}" Doe" } {{/ str }}

lib/amd/src/templates.js
lib/classes/output/mustache_quote_helper.php [new file with mode: 0644]
lib/classes/output/mustache_string_helper.php
lib/outputrenderers.php

index bde7e0d..de50cad 100644 (file)
@@ -222,6 +222,27 @@ define([ 'core/mustache',
         return '{{_s' + index + '}}';
     };
 
+    /**
+     * Quote helper used to wrap content in quotes, and escape all quotes present in the content.
+     *
+     * @method quoteHelper
+     * @private
+     * @param {string} sectionText The text to parse the arguments from.
+     * @param {function} helper Used to render subsections of the text.
+     * @return {string}
+     */
+    var quoteHelper = function(sectionText, helper) {
+        var content = helper(sectionText.trim(), this);
+
+        // Escape the {{ and the ".
+        // This involves wrapping {{, and }} in change delimeter tags.
+        content = content
+            .replace('"', '\\"')
+            .replace(/([\{\}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>')
+            ;
+        return '"' + content + '"';
+    };
+
     /**
      * Add some common helper functions to all context objects passed to templates.
      * These helpers match exactly the helpers available in php.
@@ -239,6 +260,7 @@ define([ 'core/mustache',
         context.str = function() { return stringHelper; };
         context.pix = function() { return pixHelper; };
         context.js = function() { return jsHelper; };
+        context.quote = function() { return quoteHelper; };
         context.globals = { config : config };
         context.currentTheme = themeName;
     };
diff --git a/lib/classes/output/mustache_quote_helper.php b/lib/classes/output/mustache_quote_helper.php
new file mode 100644 (file)
index 0000000..2f492fc
--- /dev/null
@@ -0,0 +1,55 @@
+<?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/>.
+
+/**
+ * Wrap content in quotes, and escape all quotes used.
+ *
+ * @package    core
+ * @category   output
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\output;
+
+/**
+ * Wrap content in quotes, and escape all quotes used.
+ *
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mustache_quote_helper {
+
+    /**
+     * Wrap content in quotes, and escape all quotes used.
+     *
+     * Note: This helper is only compatible with the standard {{ }} delimeters.
+     *
+     * @param string $text The text to parse for arguments.
+     * @param Mustache_LambdaHelper $helper Used to render nested mustache variables.
+     * @return string
+     */
+    public function quote($text, \Mustache_LambdaHelper $helper) {
+        // Split the text into an array of variables.
+        $content = trim($text);
+        $content = $helper->render($content);
+
+        // Escape the {{ and the ".
+        $content = str_replace('"', '\\"', $content);
+        $content = preg_replace('([{}]{2,3})', '{{=<% %>=}}${0}<%={{ }}=%>', $content);
+        return '"' . $content . '"';
+    }
+}
index 1b5d52a..0241be5 100644 (file)
@@ -76,4 +76,3 @@ class mustache_string_helper {
         return get_string($key, $component, $a);
     }
 }
-
index 066f82e..78a502f 100644 (file)
@@ -91,6 +91,7 @@ class renderer_base {
 
             $loader = new \core\output\mustache_filesystem_loader();
             $stringhelper = new \core\output\mustache_string_helper();
+            $quotehelper = new \core\output\mustache_quote_helper();
             $jshelper = new \core\output\mustache_javascript_helper($this->page->requires);
             $pixhelper = new \core\output\mustache_pix_helper($this);
 
@@ -99,6 +100,7 @@ class renderer_base {
 
             $helpers = array('config' => $safeconfig,
                              'str' => array($stringhelper, 'str'),
+                             'quote' => array($quotehelper, 'quote'),
                              'js' => array($jshelper, 'help'),
                              'pix' => array($pixhelper, 'pix'));