diff --git a/admin/templates/setting_configtext.mustache b/admin/templates/setting_configtext.mustache
index 1238c8e3e98..e9bb7642c9b 100644
--- a/admin/templates/setting_configtext.mustache
+++ b/admin/templates/setting_configtext.mustache
@@ -27,6 +27,8 @@
* forceltr - always display as ltr
* attributes - list of additional attributes containing name, value
* readonly - bool
+ * data - list of arbitrary data attributes
+ * maxcharacter - maximum character
Example context (json):
{
@@ -36,12 +38,19 @@
"size": "21",
"forceltr": false,
"readonly": false,
- "attributes": [ { "name": "readonly", "value": "readonly" } ]
+ "attributes": [ { "name": "readonly", "value": "readonly" } ],
+ "data": [ { "key": "validation_max_length", "value": "10" } ],
+ "maxcharacter": false
}
}}
{{!
Setting configtext.
}}
diff --git a/admin/tool/messageinbound/tests/behat/incoming_mail.feature b/admin/tool/messageinbound/tests/behat/incoming_mail.feature
index b8974244a12..2ed2e3d0752 100644
--- a/admin/tool/messageinbound/tests/behat/incoming_mail.feature
+++ b/admin/tool/messageinbound/tests/behat/incoming_mail.feature
@@ -23,3 +23,13 @@ Feature: Incoming mail configuration
When I navigate to "Server > Email > Incoming mail configuration" in site administration
Then "OAuth 2 service" "select" should exist
And I should see "Testing service" in the "OAuth 2 service" "select"
+
+ @javascript
+ Scenario: Check character limitations of mailbox name
+ When I navigate to "Server > Email > Incoming mail configuration" in site administration
+ And I set the field "Mailbox name" to "frogfrogfrogfrog"
+ Then I should see "Maximum of 15 characters"
+ And the "disabled" attribute of "form#adminsettings button[type='submit']" "css_element" should contain "true"
+ And I set the field "Mailbox name" to "frogfrogfrogfro"
+ And I should not see "Maximum of 15 characters"
+ And the "disabled" attribute of "form#adminsettings button[type='submit']" "css_element" should not be set
diff --git a/lang/en/admin.php b/lang/en/admin.php
index c02559f49ae..8e0b7b48b82 100644
--- a/lang/en/admin.php
+++ b/lang/en/admin.php
@@ -871,6 +871,7 @@ sites. If this is not what you wanted then you should make sure you are updating
from a STABLE branch of the Moodle code. See Moodle Docs for more details.';
$string['maxbytes'] = 'Maximum uploaded file size';
$string['maxconsecutiveidentchars'] = 'Consecutive identical characters';
+$string['maxcharacter'] = '{$a} character maximum';
$string['maxsizeperdownloadcoursefile'] = 'Maximum size per file';
$string['maxsizeperdownloadcoursefile_desc'] = 'The maximum size of each file when downloading course content. Files exceeding this size will be omitted from the download.';
$string['maxeditingtime'] = 'Maximum time to edit posts';
diff --git a/lib/adminlib.php b/lib/adminlib.php
index a4c6ad400c6..4382479843a 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -2447,6 +2447,8 @@ class admin_setting_configtext extends admin_setting {
/** @var int default field size */
public $size;
+ /** @var array List of arbitrary data attributes */
+ protected $datavalues = [];
/**
* Config text constructor
@@ -2530,11 +2532,24 @@ class admin_setting_configtext extends admin_setting {
}
}
+ /**
+ * Set arbitrary data attributes for template.
+ *
+ * @param string $key Attribute key for template.
+ * @param string $value Attribute value for template.
+ */
+ public function set_data_attribute(string $key, string $value): void {
+ $this->datavalues[] = [
+ 'key' => $key,
+ 'value' => $value,
+ ];
+ }
+
/**
* Return an XHTML string for the setting
* @return string Returns an XHTML string
*/
- public function output_html($data, $query='') {
+ public function output_html($data, $query = '') {
global $OUTPUT;
$default = $this->get_defaultsetting();
@@ -2545,6 +2560,8 @@ class admin_setting_configtext extends admin_setting {
'value' => $data,
'forceltr' => $this->get_force_ltr(),
'readonly' => $this->is_readonly(),
+ 'data' => $this->datavalues,
+ 'maxcharacter' => array_key_exists('validation-max-length', $this->datavalues),
];
$element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context);
@@ -2578,6 +2595,7 @@ class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW,
$size=null, $maxlength = 0) {
$this->maxlength = $maxlength;
+ $this->set_data_attribute('validation-max-length', $maxlength);
parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size);
}
@@ -2604,6 +2622,20 @@ class admin_setting_configtext_with_maxlength extends admin_setting_configtext {
return $parentvalidation;
}
}
+
+ /**
+ * Return an XHTML string for the setting.
+ *
+ * @param string $data data.
+ * @param string $query query statement.
+ * @return string Returns an XHTML string
+ */
+ public function output_html($data, $query = ''): string {
+ global $PAGE;
+ $PAGE->requires->js_call_amd('core_form/configtext_maxlength', 'init');
+
+ return parent::output_html($data, $query);
+ }
}
/**
diff --git a/lib/form/amd/build/configtext_maxlength.min.js b/lib/form/amd/build/configtext_maxlength.min.js
new file mode 100644
index 00000000000..67d5447e700
--- /dev/null
+++ b/lib/form/amd/build/configtext_maxlength.min.js
@@ -0,0 +1,3 @@
+define("core_form/configtext_maxlength",["exports","core/str","core/templates","core/notification","core/prefetch"],(function(_exports,_str,_templates,_notification,_prefetch){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification);let registered=!1;_exports.init=()=>{registered||((0,_prefetch.prefetchStrings)("core",["maximumchars"]),(0,_prefetch.prefetchTemplates)(["core_form/setting_validation_failure"]),registered=!0,document.addEventListener("input",(e=>{const maxLengthField=e.target.closest("[data-validation-max-length]");if(maxLengthField)if(maxLengthField.value.length>maxLengthField.dataset.validationMaxLength)maxLengthField.form.addEventListener("submit",submissionCheck),(0,_str.get_string)("maximumchars","core",maxLengthField.dataset.validationMaxLength).then((errorMessage=>_templates.default.renderForPromise("core_form/setting_validation_failure",{fieldid:maxLengthField.id,message:errorMessage}))).then((errorTemplate=>{if(!maxLengthField.dataset.validationFailureId){const formWrapper=maxLengthField.closest(".form-text");_templates.default.prependNodeContents(formWrapper,errorTemplate.html,errorTemplate.js),maxLengthField.dataset.validationFailureId="maxlength_error_".concat(maxLengthField.id),updateSubmitButton()}})).then((()=>{maxLengthField.setAttribute("aria-invalid",!0);const errorField=document.getElementById(maxLengthField.dataset.validationFailureId);errorField&&errorField.setAttribute("aria-describedby",maxLengthField.id)})).catch(_notification.default.exception);else{const validationMessage=document.getElementById(maxLengthField.dataset.validationFailureId);validationMessage&&(validationMessage.parentElement.remove(),delete maxLengthField.dataset.validationFailureId,maxLengthField.removeAttribute("aria-invalid"),updateSubmitButton())}})))};const submissionCheck=e=>{const maxLengthFields=e.target.querySelectorAll("[data-validation-max-length]");Array.from(maxLengthFields).some((maxLengthField=>maxLengthField.value.length>maxLengthField.dataset.validationMaxLength&&(e.preventDefault(),maxLengthField.focus(),!0)))},updateSubmitButton=()=>{const shouldDisable=document.querySelector("form#adminsettings .error");document.querySelector('form#adminsettings button[type="submit"]').disabled=!!shouldDisable}}));
+
+//# sourceMappingURL=configtext_maxlength.min.js.map
\ No newline at end of file
diff --git a/lib/form/amd/build/configtext_maxlength.min.js.map b/lib/form/amd/build/configtext_maxlength.min.js.map
new file mode 100644
index 00000000000..6b337e2049d
--- /dev/null
+++ b/lib/form/amd/build/configtext_maxlength.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"configtext_maxlength.min.js","sources":["../src/configtext_maxlength.js"],"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 * Validation for configtext_maxlength.\n *\n * @module core_form/configtext-maxlength\n * @copyright 2021 The Open University\n */\nimport {get_string as getString} from 'core/str';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport {prefetchStrings, prefetchTemplates} from 'core/prefetch';\n\nlet registered = false;\n\n/**\n * Initialisation function.\n */\nexport const init = () => {\n if (registered) {\n return;\n }\n prefetchStrings('core', [\n 'maximumchars',\n ]);\n\n prefetchTemplates([\n 'core_form/setting_validation_failure',\n ]);\n\n registered = true;\n\n document.addEventListener('input', e => {\n const maxLengthField = e.target.closest('[data-validation-max-length]');\n if (!maxLengthField) {\n return;\n }\n\n if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {\n // Disable the form for this field.\n maxLengthField.form.addEventListener('submit', submissionCheck);\n // Display an error.\n getString('maximumchars', 'core', maxLengthField.dataset.validationMaxLength)\n .then(errorMessage => {\n return Templates.renderForPromise('core_form/setting_validation_failure', {\n fieldid: maxLengthField.id,\n message: errorMessage,\n });\n })\n .then(errorTemplate => {\n if (!maxLengthField.dataset.validationFailureId) {\n const formWrapper = maxLengthField.closest('.form-text');\n Templates.prependNodeContents(formWrapper, errorTemplate.html, errorTemplate.js);\n maxLengthField.dataset.validationFailureId = `maxlength_error_${maxLengthField.id}`;\n // Disable submit button when the message is displayed.\n updateSubmitButton();\n }\n return;\n })\n .then(() => {\n maxLengthField.setAttribute('aria-invalid', true);\n const errorField = document.getElementById(maxLengthField.dataset.validationFailureId);\n if (errorField) {\n errorField.setAttribute('aria-describedby', maxLengthField.id);\n }\n return;\n })\n .catch(Notification.exception);\n } else {\n // Remove the old message.\n const validationMessage = document.getElementById(maxLengthField.dataset.validationFailureId);\n if (validationMessage) {\n validationMessage.parentElement.remove();\n delete maxLengthField.dataset.validationFailureId;\n maxLengthField.removeAttribute('aria-invalid');\n // Enable submit button when the message was removed.\n updateSubmitButton();\n }\n }\n });\n};\n\n/**\n * Handle form submission.\n *\n * @param {Event} e The event.\n */\nconst submissionCheck = e => {\n const maxLengthFields = e.target.querySelectorAll('[data-validation-max-length]');\n const maxLengthFieldsArray = Array.from(maxLengthFields);\n maxLengthFieldsArray.some(maxLengthField => {\n // Focus on the first validation failure.\n if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {\n e.preventDefault();\n maxLengthField.focus();\n return true;\n }\n return false;\n });\n};\n\n/**\n * Update submit button.\n */\nconst updateSubmitButton = () => {\n const shouldDisable = document.querySelector('form#adminsettings .error');\n document.querySelector('form#adminsettings button[type=\"submit\"]').disabled = !!shouldDisable;\n};\n"],"names":["registered","document","addEventListener","e","maxLengthField","target","closest","value","length","dataset","validationMaxLength","form","submissionCheck","then","errorMessage","Templates","renderForPromise","fieldid","id","message","errorTemplate","validationFailureId","formWrapper","prependNodeContents","html","js","updateSubmitButton","setAttribute","errorField","getElementById","catch","Notification","exception","validationMessage","parentElement","remove","removeAttribute","maxLengthFields","querySelectorAll","Array","from","some","preventDefault","focus","shouldDisable","querySelector","disabled"],"mappings":"qbA0BIA,YAAa,gBAKG,KACZA,2CAGY,OAAQ,CACpB,iDAGc,CACd,yCAGJA,YAAa,EAEbC,SAASC,iBAAiB,SAASC,UACzBC,eAAiBD,EAAEE,OAAOC,QAAQ,mCACnCF,kBAIDA,eAAeG,MAAMC,OAASJ,eAAeK,QAAQC,oBAErDN,eAAeO,KAAKT,iBAAiB,SAAUU,qCAErC,eAAgB,OAAQR,eAAeK,QAAQC,qBACpDG,MAAKC,cACKC,mBAAUC,iBAAiB,uCAAwC,CACtEC,QAASb,eAAec,GACxBC,QAASL,iBAGhBD,MAAKO,oBACGhB,eAAeK,QAAQY,oBAAqB,OACvCC,YAAclB,eAAeE,QAAQ,iCACjCiB,oBAAoBD,YAAaF,cAAcI,KAAMJ,cAAcK,IAC7ErB,eAAeK,QAAQY,8CAAyCjB,eAAec,IAE/EQ,yBAIPb,MAAK,KACFT,eAAeuB,aAAa,gBAAgB,SACtCC,WAAa3B,SAAS4B,eAAezB,eAAeK,QAAQY,qBAC9DO,YACAA,WAAWD,aAAa,mBAAoBvB,eAAec,OAIlEY,MAAMC,sBAAaC,eACrB,OAEGC,kBAAoBhC,SAAS4B,eAAezB,eAAeK,QAAQY,qBACrEY,oBACAA,kBAAkBC,cAAcC,gBACzB/B,eAAeK,QAAQY,oBAC9BjB,eAAegC,gBAAgB,gBAE/BV,kCAWVd,gBAAkBT,UACdkC,gBAAkBlC,EAAEE,OAAOiC,iBAAiB,gCACrBC,MAAMC,KAAKH,iBACnBI,MAAKrC,gBAElBA,eAAeG,MAAMC,OAASJ,eAAeK,QAAQC,sBACrDP,EAAEuC,iBACFtC,eAAeuC,SACR,MASbjB,mBAAqB,WACjBkB,cAAgB3C,SAAS4C,cAAc,6BAC7C5C,SAAS4C,cAAc,4CAA4CC,WAAaF"}
\ No newline at end of file
diff --git a/lib/form/amd/src/configtext_maxlength.js b/lib/form/amd/src/configtext_maxlength.js
new file mode 100644
index 00000000000..7a4c29bed01
--- /dev/null
+++ b/lib/form/amd/src/configtext_maxlength.js
@@ -0,0 +1,121 @@
+// 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 .
+
+/**
+ * Validation for configtext_maxlength.
+ *
+ * @module core_form/configtext-maxlength
+ * @copyright 2021 The Open University
+ */
+import {get_string as getString} from 'core/str';
+import Templates from 'core/templates';
+import Notification from 'core/notification';
+import {prefetchStrings, prefetchTemplates} from 'core/prefetch';
+
+let registered = false;
+
+/**
+ * Initialisation function.
+ */
+export const init = () => {
+ if (registered) {
+ return;
+ }
+ prefetchStrings('core', [
+ 'maximumchars',
+ ]);
+
+ prefetchTemplates([
+ 'core_form/setting_validation_failure',
+ ]);
+
+ registered = true;
+
+ document.addEventListener('input', e => {
+ const maxLengthField = e.target.closest('[data-validation-max-length]');
+ if (!maxLengthField) {
+ return;
+ }
+
+ if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {
+ // Disable the form for this field.
+ maxLengthField.form.addEventListener('submit', submissionCheck);
+ // Display an error.
+ getString('maximumchars', 'core', maxLengthField.dataset.validationMaxLength)
+ .then(errorMessage => {
+ return Templates.renderForPromise('core_form/setting_validation_failure', {
+ fieldid: maxLengthField.id,
+ message: errorMessage,
+ });
+ })
+ .then(errorTemplate => {
+ if (!maxLengthField.dataset.validationFailureId) {
+ const formWrapper = maxLengthField.closest('.form-text');
+ Templates.prependNodeContents(formWrapper, errorTemplate.html, errorTemplate.js);
+ maxLengthField.dataset.validationFailureId = `maxlength_error_${maxLengthField.id}`;
+ // Disable submit button when the message is displayed.
+ updateSubmitButton();
+ }
+ return;
+ })
+ .then(() => {
+ maxLengthField.setAttribute('aria-invalid', true);
+ const errorField = document.getElementById(maxLengthField.dataset.validationFailureId);
+ if (errorField) {
+ errorField.setAttribute('aria-describedby', maxLengthField.id);
+ }
+ return;
+ })
+ .catch(Notification.exception);
+ } else {
+ // Remove the old message.
+ const validationMessage = document.getElementById(maxLengthField.dataset.validationFailureId);
+ if (validationMessage) {
+ validationMessage.parentElement.remove();
+ delete maxLengthField.dataset.validationFailureId;
+ maxLengthField.removeAttribute('aria-invalid');
+ // Enable submit button when the message was removed.
+ updateSubmitButton();
+ }
+ }
+ });
+};
+
+/**
+ * Handle form submission.
+ *
+ * @param {Event} e The event.
+ */
+const submissionCheck = e => {
+ const maxLengthFields = e.target.querySelectorAll('[data-validation-max-length]');
+ const maxLengthFieldsArray = Array.from(maxLengthFields);
+ maxLengthFieldsArray.some(maxLengthField => {
+ // Focus on the first validation failure.
+ if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {
+ e.preventDefault();
+ maxLengthField.focus();
+ return true;
+ }
+ return false;
+ });
+};
+
+/**
+ * Update submit button.
+ */
+const updateSubmitButton = () => {
+ const shouldDisable = document.querySelector('form#adminsettings .error');
+ document.querySelector('form#adminsettings button[type="submit"]').disabled = !!shouldDisable;
+};
diff --git a/lib/form/templates/setting_validation_failure.mustache b/lib/form/templates/setting_validation_failure.mustache
new file mode 100644
index 00000000000..dafc693f286
--- /dev/null
+++ b/lib/form/templates/setting_validation_failure.mustache
@@ -0,0 +1,36 @@
+{{!
+ 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 core_form/setting_validation_failure
+ Error displayed when field fails validation (longer than max length).
+
+ Context variables required for this template:
+ * fieldid - element field id
+ * message - error message
+
+ Example context (json):
+ {
+ "fieldid": "test0",
+ "message": "test message"
+ }
+}}
+{{!
+ Setting error template for configtext_maxlength.
+}}
+
+ {{message}}
+