From c9e93b8084e45d6773e57932165739738c075889 Mon Sep 17 00:00:00 2001
From: Roeland Jago Douma <roeland@famdouma.nl>
Date: Thu, 27 Sep 2018 16:20:57 +0200
Subject: [PATCH] Compile contactmenu handlebars templates

Fixes #11029
For https://github.com/orgs/nextcloud/projects/18

Ship the compiled handlebars templates. This makes it possible to have a
scricter CSP.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
---
 core/js/contactsmenu.js                 |  99 ++++----------------
 core/js/contactsmenu/contact.handlebars |  34 +++++++
 core/js/contactsmenu/error.handlebars   |   4 +
 core/js/contactsmenu/list.handlebars    |   8 ++
 core/js/contactsmenu/loading.handlebars |   4 +
 core/js/contactsmenu/menu.handlebars    |   4 +
 core/js/contactsmenu_templates.js       | 117 ++++++++++++++++++++++++
 core/js/core.json                       |   1 +
 lib/private/legacy/template.php         |   1 +
 9 files changed, 191 insertions(+), 81 deletions(-)
 create mode 100644 core/js/contactsmenu/contact.handlebars
 create mode 100644 core/js/contactsmenu/error.handlebars
 create mode 100644 core/js/contactsmenu/list.handlebars
 create mode 100644 core/js/contactsmenu/loading.handlebars
 create mode 100644 core/js/contactsmenu/menu.handlebars
 create mode 100644 core/js/contactsmenu_templates.js

diff --git a/core/js/contactsmenu.js b/core/js/contactsmenu.js
index b0f302e1599..8c9b7060a74 100644
--- a/core/js/contactsmenu.js
+++ b/core/js/contactsmenu.js
@@ -25,67 +25,6 @@
 (function(OC, $, _, Handlebars) {
 	'use strict';
 
-	var MENU_TEMPLATE = ''
-			+ '<label class="hidden-visually" for="contactsmenu-search">' + t('core', 'Search contacts …') + '</label>'
-			+ '<input id="contactsmenu-search" type="search" placeholder="' + t('core', 'Search contacts …') + '" value="{{searchTerm}}">'
-			+ '<div class="content">'
-			+ '</div>';
-	var CONTACTS_LIST_TEMPLATE = ''
-			+ '{{#unless contacts.length}}'
-			+ '<div class="emptycontent">'
-			+ '    <div class="icon-search"></div>'
-			+ '    <h2>' + t('core', 'No contacts found') + '</h2>'
-			+ '</div>'
-			+ '{{/unless}}'
-			+ '<div id="contactsmenu-contacts"></div>'
-			+ '{{#if contactsAppEnabled}}<div class="footer"><a href="{{contactsAppURL}}">' + t('core', 'Show all contacts …') + '</a></div>{{/if}}';
-	var LOADING_TEMPLATE = ''
-			+ '<div class="emptycontent">'
-			+ '    <div class="icon-loading"></div>'
-			+ '    <h2>{{loadingText}}</h2>'
-			+ '</div>';
-	var ERROR_TEMPLATE = ''
-			+ '<div class="emptycontent">'
-			+ '    <div class="icon-search"></div>'
-			+ '    <h2>' + t('core', 'Could not load your contacts') + '</h2>'
-			+ '</div>';
-	var CONTACT_TEMPLATE = ''
-			+ '{{#if contact.avatar}}'
-			+ '<img src="{{contact.avatar}}&size=32" class="avatar"'
-			+ 'srcset="{{contact.avatar}}&size=32 1x, {{contact.avatar}}&size=64 2x, {{contact.avatar}}&size=128 4x" alt="">'
-			+ '{{else}}'
-			+ '<div class="avatar"></div>'
-			+ '{{/if}}'
-			+ '<div class="body">'
-			+ '    <div class="full-name">{{contact.fullName}}</div>'
-			+ '    <div class="last-message">{{contact.lastMessage}}</div>'
-			+ '</div>'
-			+ '{{#if contact.topAction}}'
-			+ '<a class="top-action" href="{{contact.topAction.hyperlink}}" title="{{contact.topAction.title}}">'
-			+ '    <img src="{{contact.topAction.icon}}" alt="{{contact.topAction.title}}">'
-			+ '</a>'
-			+ '{{/if}}'
-			+ '{{#if contact.hasTwoActions}}'
-			+ '<a class="second-action" href="{{contact.secondAction.hyperlink}}" title="{{contact.secondAction.title}}">'
-			+ '    <img src="{{contact.secondAction.icon}}" alt="{{contact.secondAction.title}}">'
-			+ '</a>'
-			+ '{{/if}}'
-			+ '{{#if contact.hasManyActions}}'
-			+ '    <span class="other-actions icon-more"></span>'
-			+ '    <div class="menu popovermenu">'
-			+ '        <ul>'
-			+ '            {{#each contact.actions}}'
-			+ '            <li>'
-			+ '                <a href="{{hyperlink}}">'
-			+ '                    <img src="{{icon}}" alt="">'
-			+ '                    <span>{{title}}</span>'
-			+ '                </a>'
-			+ '            </li>'
-			+ '            {{/each}}'
-			+ '        </ul>'
-			+ '    </div>'
-			+ '{{/if}}';
-
 	/**
 	 * @class Contact
 	 */
@@ -201,10 +140,7 @@
 		 * @returns {undefined}
 		 */
 		template: function(data) {
-			if (!this._template) {
-				this._template = Handlebars.compile(CONTACT_TEMPLATE);
-			}
-			return this._template(data);
+			return OC.ContactsMenu.Templates['contact'](data);
 		},
 
 		/**
@@ -314,10 +250,7 @@
 		 * @returns {string}
 		 */
 		loadingTemplate: function(data) {
-			if (!this._loadingTemplate) {
-				this._loadingTemplate = Handlebars.compile(LOADING_TEMPLATE);
-			}
-			return this._loadingTemplate(data);
+			return OC.ContactsMenu.Templates['loading'](data);
 		},
 
 		/**
@@ -325,10 +258,11 @@
 		 * @returns {string}
 		 */
 		errorTemplate: function(data) {
-			if (!this._errorTemplate) {
-				this._errorTemplate = Handlebars.compile(ERROR_TEMPLATE);
-			}
-			return this._errorTemplate(data);
+			return OC.ContactsMenu.Templates['error'](
+				_.extend({
+					couldNotLoadText: t('core', 'Could not load your contacts')
+				}, data)
+			);
 		},
 
 		/**
@@ -336,10 +270,11 @@
 		 * @returns {string}
 		 */
 		contentTemplate: function(data) {
-			if (!this._contentTemplate) {
-				this._contentTemplate = Handlebars.compile(MENU_TEMPLATE);
-			}
-			return this._contentTemplate(data);
+			return OC.ContactsMenu.Templates['menu'](
+				_.extend({
+					searchContactsText: t('core', 'Search contacts …')
+				}, data)
+			);
 		},
 
 		/**
@@ -347,10 +282,12 @@
 		 * @returns {string}
 		 */
 		contactsTemplate: function(data) {
-			if (!this._contactsTemplate) {
-				this._contactsTemplate = Handlebars.compile(CONTACTS_LIST_TEMPLATE);
-			}
-			return this._contactsTemplate(data);
+			return OC.ContactsMenu.Templates['list'](
+				_.extend({
+					noContactsFoundText: t('core', 'No contacts found'),
+					showAllContactsText: t('core', 'Show all contacts …')
+				}, data)
+			);
 		},
 
 		/**
diff --git a/core/js/contactsmenu/contact.handlebars b/core/js/contactsmenu/contact.handlebars
new file mode 100644
index 00000000000..a30b11462c4
--- /dev/null
+++ b/core/js/contactsmenu/contact.handlebars
@@ -0,0 +1,34 @@
+{{#if contact.avatar}}
+<img src="{{contact.avatar}}&size=32" class="avatar" srcset="{{contact.avatar}}&size=32 1x, {{contact.avatar}}&size=64 2x, {{contact.avatar}}&size=128 4x" alt="">
+{{else}}
+<div class="avatar"></div>
+{{/if}}
+<div class="body">
+	<div class="full-name">{{contact.fullName}}</div>
+	<div class="last-message">{{contact.lastMessage}}</div>
+</div>
+{{#if contact.topAction}}
+<a class="top-action" href="{{contact.topAction.hyperlink}}" title="{{contact.topAction.title}}">
+	<img src="{{contact.topAction.icon}}" alt="{{contact.topAction.title}}">
+</a>
+{{/if}}
+{{#if contact.hasTwoActions}}
+<a class="second-action" href="{{contact.secondAction.hyperlink}}" title="{{contact.secondAction.title}}">
+	<img src="{{contact.secondAction.icon}}" alt="{{contact.secondAction.title}}">
+</a>
+{{/if}}
+{{#if contact.hasManyActions}}
+	<span class="other-actions icon-more"></span>
+	<div class="menu popovermenu">
+		<ul>
+			{{#each contact.actions}}
+			<li>
+				<a href="{{hyperlink}}">
+					<img src="{{icon}}" alt="">
+					<span>{{title}}</span>
+				</a>
+			</li>
+			{{/each}}
+		</ul>
+	</div>
+{{/if}}
diff --git a/core/js/contactsmenu/error.handlebars b/core/js/contactsmenu/error.handlebars
new file mode 100644
index 00000000000..5115595b4e1
--- /dev/null
+++ b/core/js/contactsmenu/error.handlebars
@@ -0,0 +1,4 @@
+<div class="emptycontent">
+	<div class="icon-search"></div>
+	<h2>{{couldNotLoadText}}</h2>
+</div>
diff --git a/core/js/contactsmenu/list.handlebars b/core/js/contactsmenu/list.handlebars
new file mode 100644
index 00000000000..07699204db0
--- /dev/null
+++ b/core/js/contactsmenu/list.handlebars
@@ -0,0 +1,8 @@
+{{#unless contacts.length}}
+<div class="emptycontent">
+	<div class="icon-search"></div>
+	<h2>{{noContactsFoundText}}</h2>
+</div>
+{{/unless}}
+<div id="contactsmenu-contacts"></div>
+{{#if contactsAppEnabled}}<div class="footer"><a href="{{contactsAppURL}}">{{showAllContactsText}}</a></div>{{/if}}
diff --git a/core/js/contactsmenu/loading.handlebars b/core/js/contactsmenu/loading.handlebars
new file mode 100644
index 00000000000..7fb22a6ed8e
--- /dev/null
+++ b/core/js/contactsmenu/loading.handlebars
@@ -0,0 +1,4 @@
+<div class="emptycontent">
+	<div class="icon-loading"></div>
+	<h2>{{loadingText}}</h2>
+</div>
diff --git a/core/js/contactsmenu/menu.handlebars b/core/js/contactsmenu/menu.handlebars
new file mode 100644
index 00000000000..7d7697e780c
--- /dev/null
+++ b/core/js/contactsmenu/menu.handlebars
@@ -0,0 +1,4 @@
+<label class="hidden-visually" for="contactsmenu-search">{{searchContactsText}}</label>
+<input id="contactsmenu-search" type="search" placeholder="{{searchContactsText}}" value="{{searchTerm}}">
+<div class="content">
+</div>
diff --git a/core/js/contactsmenu_templates.js b/core/js/contactsmenu_templates.js
new file mode 100644
index 00000000000..8e30b21cb2a
--- /dev/null
+++ b/core/js/contactsmenu_templates.js
@@ -0,0 +1,117 @@
+(function() {
+  var template = Handlebars.template, templates = OC.ContactsMenu.Templates = OC.ContactsMenu.Templates || {};
+templates['contact'] = template({"1":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
+
+  return "<img src=\""
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.avatar : stack1), depth0))
+    + "&size=32\" class=\"avatar\" srcset=\""
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.avatar : stack1), depth0))
+    + "&size=32 1x, "
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.avatar : stack1), depth0))
+    + "&size=64 2x, "
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.avatar : stack1), depth0))
+    + "&size=128 4x\" alt=\"\">\n";
+},"3":function(container,depth0,helpers,partials,data) {
+    return "<div class=\"avatar\"></div>\n";
+},"5":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
+
+  return "<a class=\"top-action\" href=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.topAction : stack1)) != null ? stack1.hyperlink : stack1), depth0))
+    + "\" title=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.topAction : stack1)) != null ? stack1.title : stack1), depth0))
+    + "\">\n	<img src=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.topAction : stack1)) != null ? stack1.icon : stack1), depth0))
+    + "\" alt=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.topAction : stack1)) != null ? stack1.title : stack1), depth0))
+    + "\">\n</a>\n";
+},"7":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
+
+  return "<a class=\"second-action\" href=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.secondAction : stack1)) != null ? stack1.hyperlink : stack1), depth0))
+    + "\" title=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.secondAction : stack1)) != null ? stack1.title : stack1), depth0))
+    + "\">\n	<img src=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.secondAction : stack1)) != null ? stack1.icon : stack1), depth0))
+    + "\" alt=\""
+    + alias2(alias1(((stack1 = ((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.secondAction : stack1)) != null ? stack1.title : stack1), depth0))
+    + "\">\n</a>\n";
+},"9":function(container,depth0,helpers,partials,data) {
+    var stack1;
+
+  return "	<span class=\"other-actions icon-more\"></span>\n	<div class=\"menu popovermenu\">\n		<ul>\n"
+    + ((stack1 = helpers.each.call(depth0,((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.actions : stack1),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "		</ul>\n	</div>\n";
+},"10":function(container,depth0,helpers,partials,data) {
+    var helper, alias1=helpers.helperMissing, alias2="function", alias3=container.escapeExpression;
+
+  return "			<li>\n				<a href=\""
+    + alias3(((helper = (helper = helpers.hyperlink || (depth0 != null ? depth0.hyperlink : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"hyperlink","hash":{},"data":data}) : helper)))
+    + "\">\n					<img src=\""
+    + alias3(((helper = (helper = helpers.icon || (depth0 != null ? depth0.icon : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"icon","hash":{},"data":data}) : helper)))
+    + "\" alt=\"\">\n					<span>"
+    + alias3(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"title","hash":{},"data":data}) : helper)))
+    + "</span>\n				</a>\n			</li>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1, alias1=container.lambda, alias2=container.escapeExpression;
+
+  return ((stack1 = helpers["if"].call(depth0,((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.avatar : stack1),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
+    + "<div class=\"body\">\n	<div class=\"full-name\">"
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.fullName : stack1), depth0))
+    + "</div>\n	<div class=\"last-message\">"
+    + alias2(alias1(((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.lastMessage : stack1), depth0))
+    + "</div>\n</div>\n"
+    + ((stack1 = helpers["if"].call(depth0,((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.topAction : stack1),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers["if"].call(depth0,((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.hasTwoActions : stack1),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + ((stack1 = helpers["if"].call(depth0,((stack1 = (depth0 != null ? depth0.contact : depth0)) != null ? stack1.hasManyActions : stack1),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"useData":true});
+templates['error'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var helper;
+
+  return "<div class=\"emptycontent\">\n	<div class=\"icon-search\"></div>\n	<h2>"
+    + container.escapeExpression(((helper = (helper = helpers.couldNotLoadText || (depth0 != null ? depth0.couldNotLoadText : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0,{"name":"couldNotLoadText","hash":{},"data":data}) : helper)))
+    + "</h2>\n</div>\n";
+},"useData":true});
+templates['list'] = template({"1":function(container,depth0,helpers,partials,data) {
+    var helper;
+
+  return "<div class=\"emptycontent\">\n	<div class=\"icon-search\"></div>\n	<h2>"
+    + container.escapeExpression(((helper = (helper = helpers.noContactsFoundText || (depth0 != null ? depth0.noContactsFoundText : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0,{"name":"noContactsFoundText","hash":{},"data":data}) : helper)))
+    + "</h2>\n</div>\n";
+},"3":function(container,depth0,helpers,partials,data) {
+    var helper, alias1=helpers.helperMissing, alias2="function", alias3=container.escapeExpression;
+
+  return "<div class=\"footer\"><a href=\""
+    + alias3(((helper = (helper = helpers.contactsAppURL || (depth0 != null ? depth0.contactsAppURL : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"contactsAppURL","hash":{},"data":data}) : helper)))
+    + "\">"
+    + alias3(((helper = (helper = helpers.showAllContactsText || (depth0 != null ? depth0.showAllContactsText : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"showAllContactsText","hash":{},"data":data}) : helper)))
+    + "</a></div>";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var stack1;
+
+  return ((stack1 = helpers.unless.call(depth0,((stack1 = (depth0 != null ? depth0.contacts : depth0)) != null ? stack1.length : stack1),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "<div id=\"contactsmenu-contacts\"></div>\n"
+    + ((stack1 = helpers["if"].call(depth0,(depth0 != null ? depth0.contactsAppEnabled : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+    + "\n";
+},"useData":true});
+templates['loading'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var helper;
+
+  return "<div class=\"emptycontent\">\n	<div class=\"icon-loading\"></div>\n	<h2>"
+    + container.escapeExpression(((helper = (helper = helpers.loadingText || (depth0 != null ? depth0.loadingText : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0,{"name":"loadingText","hash":{},"data":data}) : helper)))
+    + "</h2>\n</div>\n";
+},"useData":true});
+templates['menu'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+    var helper, alias1=helpers.helperMissing, alias2="function", alias3=container.escapeExpression;
+
+  return "<label class=\"hidden-visually\" for=\"contactsmenu-search\">"
+    + alias3(((helper = (helper = helpers.searchContactsText || (depth0 != null ? depth0.searchContactsText : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"searchContactsText","hash":{},"data":data}) : helper)))
+    + "</label>\n<input id=\"contactsmenu-search\" type=\"search\" placeholder=\""
+    + alias3(((helper = (helper = helpers.searchContactsText || (depth0 != null ? depth0.searchContactsText : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"searchContactsText","hash":{},"data":data}) : helper)))
+    + "\" value=\""
+    + alias3(((helper = (helper = helpers.searchTerm || (depth0 != null ? depth0.searchTerm : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"searchTerm","hash":{},"data":data}) : helper)))
+    + "\">\n<div class=\"content\">\n</div>\n";
+},"useData":true});
+})();
\ No newline at end of file
diff --git a/core/js/core.json b/core/js/core.json
index 2ebc2e710ed..19f361d2dfe 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -42,6 +42,7 @@
 		"sharedialogshareelistview.js",
 		"octemplate.js",
 		"contactsmenu.js",
+		"contactsmenu_templates.js",
 		"eventsource.js",
 		"config.js",
 		"public/appconfig.js",
diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php
index 1505089d561..9c7da75e5fd 100644
--- a/lib/private/legacy/template.php
+++ b/lib/private/legacy/template.php
@@ -124,6 +124,7 @@ class OC_Template extends \OC\Template\Base {
 			OC_Util::addScript('files/fileinfo');
 			OC_Util::addScript('files/client');
 			OC_Util::addScript('contactsmenu');
+			OC_Util::addScript('contactsmenu_templates');
 
 			if (\OC::$server->getConfig()->getSystemValue('debug')) {
 				// Add the stuff we need always
-- 
GitLab