diff --git a/enrol/fee/classes/plugin.php b/enrol/fee/classes/plugin.php
index 0e01c4e7251..1844de0ee6d 100644
--- a/enrol/fee/classes/plugin.php
+++ b/enrol/fee/classes/plugin.php
@@ -197,25 +197,21 @@ class enrol_fee_plugin extends enrol_plugin {
if (abs($cost) < 0.01) { // No cost, other enrolment methods (instances) should be used.
echo '
'.get_string('nocost', 'enrol_fee').'
';
} else {
- $localisedcost = format_float($cost, -1, true);
- if (isguestuser()) { // Force login only for guest user, not real users with guest role.
- $wwwroot = $CFG->wwwroot;
- echo '
';
- }
+ $locale = get_string('localecldr', 'langconfig');
+ $fmt = NumberFormatter::create($locale, NumberFormatter::CURRENCY);
+ $localisedcost = numfmt_format_currency($fmt, $cost, $instance->currency);
+ $data = [
+ 'isguestuser' => isguestuser(),
+ 'cost' => $localisedcost,
+ 'currency' => $instance->currency,
+ 'amount' => $cost,
+ 'instanceid' => $instance->id,
+ 'description' => get_string('purchasedescription', 'enrol_fee',
+ format_string($course->fullname, true, ['context' => $context])),
+ ];
+ echo $OUTPUT->render_from_template('enrol_fee/payment_region', $data);
}
return $OUTPUT->box(ob_get_clean());
diff --git a/enrol/fee/templates/payment_region.mustache b/enrol/fee/templates/payment_region.mustache
new file mode 100644
index 00000000000..0939cf5e0cd
--- /dev/null
+++ b/enrol/fee/templates/payment_region.mustache
@@ -0,0 +1,79 @@
+{{!
+ 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 .
+}}
+{{!
+ @template enrol_fee/payment_region
+
+ This template will render information about course fee along with a button for payment.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * data-amount
+ * data-currency
+ * data-component
+ * data-componentid
+ * data-description
+
+ Context variables required for this template:
+ * cost - Human readable cost string including amount and currency
+ * amount - amount of the cost
+ * currency - currency of the cost
+ * instanceid - Id of the enrolment instance
+ * description - The description for this purchase
+
+ Example context (json):
+ {
+ "cost": "$108.50",
+ "amount": 108.50,
+ "currency": "AUD",
+ "instanceid": 11,
+ "description": "Enrolment in course Introduction to algorithms",
+ "isguestuser": false
+ }
+
+}}
+
+{{#js}}
+ require(['core_payment/gateways_modal'], function(modal) {
+ modal.registerEventListeners(document.querySelector('#gateways-modal-trigger-{{ uniqid }}'));
+ });
+{{/js}}
diff --git a/lang/en/langconfig.php b/lang/en/langconfig.php
index 7ec6c455991..8d7be165132 100644
--- a/lang/en/langconfig.php
+++ b/lang/en/langconfig.php
@@ -32,6 +32,7 @@ $string['iso6392'] = 'eng';
$string['labelsep'] = ': ';
$string['listsep'] = ',';
$string['locale'] = 'en_AU.UTF-8';
+$string['localecldr'] = 'en-AU';
$string['localewin'] = 'English_Australia.1252';
$string['localewincharset'] = '';
$string['oldcharset'] = 'ISO-8859-1';
diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index 3fe5a858aee..cbd3140b36e 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -1126,6 +1126,7 @@ $string['ip_address'] = 'IP address';
$string['jump'] = 'Jump';
$string['jumpto'] = 'Jump to...';
$string['keep'] = 'Keep';
+$string['labelvalue'] = '{$a->label}: {$a->value}';
$string['langltr'] = 'Language direction left-to-right';
$string['langrtl'] = 'Language direction right-to-left';
$string['language'] = 'Language';
diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js
index c32988c5128..60b32130f11 100644
--- a/payment/amd/build/gateways_modal.min.js
+++ b/payment/amd/build/gateways_modal.min.js
@@ -1,2 +1,2 @@
-define ("core_payment/gateways_modal",["exports","core/modal_factory","core/templates","core/str","./repository","./selectors","core/modal_events","core/toast","core/notification"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.registerEventListeners=void 0;b=j(b);c=j(c);f=j(f);g=j(g);i=j(i);var m="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function j(a){return a&&a.__esModule?a:{default:a}}function k(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}a.registerEventListeners=function registerEventListeners(a){var b=document.querySelector(a);b.addEventListener("click",function(a){a.preventDefault();n(b,{focusOnClose:a.target})})};var n=function(){var a=l(regeneratorRuntime.mark(function a(j){var k,l,m,n,p,q,r,s,t,u,v=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:k=1.\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getGatewaysSupportingCurrency} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\n\n/**\n * Register event listeners for the module.\n *\n * @param {string} nodeSelector The root to listen to.\n */\nexport const registerEventListeners = (nodeSelector) => {\n const rootNode = document.querySelector(nodeSelector);\n\n rootNode.addEventListener('click', (e) => {\n e.preventDefault();\n show(rootNode, {focusOnClose: e.target});\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n addToastRegion(modal.getRoot()[0]);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(ModalEvents.save, (e) => {\n const root = modal.getRoot()[0];\n const gateway = (root.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.amount,\n rootNode.dataset.currency,\n rootNode.dataset.component,\n rootNode.dataset.componentid,\n rootNode.dataset.description,\n ({success, message = ''}) => {\n modal.hide();\n if (success) {\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.reload();\n } else {\n Notification.alert('', message);\n }\n },\n );\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message));\n }\n\n e.preventDefault();\n });\n\n const currency = rootNode.dataset.currency;\n const gateways = await getGatewaysSupportingCurrency(currency);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(modal.getRoot().find(Selectors.regions.gatewaysContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {number} amount Amount of payment\n * @param {string} currency The currency in the three-character ISO-4217 format\n * @param {string} component Name of the component that the componentid belongs to\n * @param {number} componentid An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @param {processPaymentCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nconst processPayment = async(gateway, amount, currency, component, componentid, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n\n paymentMethod.process(amount, currency, component, componentid, description, callback);\n};\n\n/**\n * The callback definition for processPayment.\n *\n * @callback processPaymentCallback\n * @param {bool} success\n * @param {string} message\n */\n"],"file":"gateways_modal.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/gateways_modal.js"],"names":["registerEventListenersBySelector","nodeSelector","document","querySelectorAll","forEach","element","registerEventListeners","rootNode","addEventListener","e","preventDefault","show","focusOnClose","target","ModalFactory","types","SAVE_CANCEL","Templates","render","type","title","body","create","modal","getRoot","on","ModalEvents","hidden","destroy","focus","save","root","gateway","querySelector","Selectors","values","value","processPayment","dataset","amount","currency","component","componentid","description","success","message","hide","Notification","addNotification","location","reload","alert","then","gateways","context","renderForPromise","html","js","replaceNodeContents","find","regions","gatewaysContainer","callback","paymentMethod","process"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getGatewaysSupportingCurrency} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\n\n/**\n * Register event listeners for the module.\n *\n * @param {string} nodeSelector The root to listen to.\n */\nexport const registerEventListenersBySelector = (nodeSelector) => {\n document.querySelectorAll(nodeSelector).forEach((element) => {\n registerEventListeners(element);\n });\n};\n\n/**\n * Register event listeners for the module.\n *\n * @param {HTMLElement} rootNode The root to listen to.\n */\nexport const registerEventListeners = (rootNode) => {\n rootNode.addEventListener('click', (e) => {\n e.preventDefault();\n show(rootNode, {focusOnClose: e.target});\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n addToastRegion(modal.getRoot()[0]);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(ModalEvents.save, (e) => {\n const root = modal.getRoot()[0];\n const gateway = (root.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.amount,\n rootNode.dataset.currency,\n rootNode.dataset.component,\n rootNode.dataset.componentid,\n rootNode.dataset.description,\n ({success, message = ''}) => {\n modal.hide();\n if (success) {\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.reload();\n } else {\n Notification.alert('', message);\n }\n },\n );\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message));\n }\n\n e.preventDefault();\n });\n\n const currency = rootNode.dataset.currency;\n const gateways = await getGatewaysSupportingCurrency(currency);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(modal.getRoot().find(Selectors.regions.gatewaysContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {number} amount Amount of payment\n * @param {string} currency The currency in the three-character ISO-4217 format\n * @param {string} component Name of the component that the componentid belongs to\n * @param {number} componentid An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @param {processPaymentCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nconst processPayment = async(gateway, amount, currency, component, componentid, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n\n paymentMethod.process(amount, currency, component, componentid, description, callback);\n};\n\n/**\n * The callback definition for processPayment.\n *\n * @callback processPaymentCallback\n * @param {bool} success\n * @param {string} message\n */\n"],"file":"gateways_modal.min.js"}
\ No newline at end of file
diff --git a/payment/amd/src/gateways_modal.js b/payment/amd/src/gateways_modal.js
index 9d65b6e48fa..1d299157dd5 100644
--- a/payment/amd/src/gateways_modal.js
+++ b/payment/amd/src/gateways_modal.js
@@ -36,9 +36,18 @@ import Notification from 'core/notification';
*
* @param {string} nodeSelector The root to listen to.
*/
-export const registerEventListeners = (nodeSelector) => {
- const rootNode = document.querySelector(nodeSelector);
+export const registerEventListenersBySelector = (nodeSelector) => {
+ document.querySelectorAll(nodeSelector).forEach((element) => {
+ registerEventListeners(element);
+ });
+};
+/**
+ * Register event listeners for the module.
+ *
+ * @param {HTMLElement} rootNode The root to listen to.
+ */
+export const registerEventListeners = (rootNode) => {
rootNode.addEventListener('click', (e) => {
e.preventDefault();
show(rootNode, {focusOnClose: e.target});
diff --git a/payment/classes/helper.php b/payment/classes/helper.php
index e3b4f1a8746..58c8925ed76 100644
--- a/payment/classes/helper.php
+++ b/payment/classes/helper.php
@@ -76,21 +76,6 @@ class helper {
return $gateways;
}
- /**
- * Requires the JS libraries for the pay button.
- */
- public static function gateways_modal_requirejs(): void {
- global $PAGE;
-
- static $done = false;
- if ($done) {
- return;
- }
-
- $PAGE->requires->js_call_amd('core_payment/gateways_modal', 'registerEventListeners', ['#gateways-modal-trigger']);
- $done = true;
- }
-
/**
* Returns the attributes to place on a pay button.
*