From b597b491ec487ccb372d5c7083a2c30930eb9959 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Sat, 20 Mar 2021 12:49:11 +0100 Subject: [PATCH] MDL-71051 core_user: create user profile field form is now modal form --- .../tests/behat/upload_users.feature | 3 +- .../tests/behat/availability_profile.feature | 3 +- calendar/tests/calendartype_test.php | 3 +- lang/en/admin.php | 2 +- user/amd/build/edit_profile_fields.min.js | 2 +- user/amd/build/edit_profile_fields.min.js.map | 2 +- user/amd/src/edit_profile_fields.js | 30 ++++- user/classes/form/profile_field_form.php | 125 +++++++++++++++--- user/profile/definelib.php | 114 ++++++++-------- user/profile/index.php | 32 +++-- user/templates/edit_profile_fields.mustache | 65 +++++---- .../tests/behat/custom_profile_fields.feature | 12 +- 12 files changed, 261 insertions(+), 132 deletions(-) diff --git a/admin/tool/uploaduser/tests/behat/upload_users.feature b/admin/tool/uploaduser/tests/behat/upload_users.feature index 4cdfbd29b0ff1..04fb69b7898b9 100644 --- a/admin/tool/uploaduser/tests/behat/upload_users.feature +++ b/admin/tool/uploaduser/tests/behat/upload_users.feature @@ -69,7 +69,8 @@ Feature: Upload users # Create user profile field. Given I log in as "admin" And I navigate to "Users > Accounts > User profile fields" in site administration - And I set the field "datatype" to "Text area" + And I click on "Create a new profile field" "link" + And I click on "Text area" "link" And I set the following fields to these values: | Short name | superfield | | Name | Super field | diff --git a/availability/condition/profile/tests/behat/availability_profile.feature b/availability/condition/profile/tests/behat/availability_profile.feature index 816e1fe73a81f..690bcd8dc2797 100644 --- a/availability/condition/profile/tests/behat/availability_profile.feature +++ b/availability/condition/profile/tests/behat/availability_profile.feature @@ -65,7 +65,8 @@ Feature: availability_profile # Add custom field. Given I log in as "admin" And I navigate to "Users > Accounts > User profile fields" in site administration - And I set the field "datatype" to "Text input" + And I click on "Create a new profile field" "link" + And I click on "Text input" "link" And I set the following fields to these values: | Short name | superfield | | Name | Super field | diff --git a/calendar/tests/calendartype_test.php b/calendar/tests/calendartype_test.php index 4b32999de5b18..4e6e5dfc08c18 100644 --- a/calendar/tests/calendartype_test.php +++ b/calendar/tests/calendartype_test.php @@ -272,12 +272,13 @@ private function datetime_field_submission_test($type, $date) { $formdata['name'] = 'Name'; $formdata['param1'] = $date['inputminyear']; $formdata['param2'] = $date['inputmaxyear']; + $formdata['datatype'] = 'datetime'; // Mock submitting this. \core_user\form\profile_field_form::mock_submit($formdata); // Create the user datetime form. - $form = new \core_user\form\profile_field_form(null, 'datetime'); + $form = new \core_user\form\profile_field_form(); // Get the data from the submission. $submissiondata = $form->get_data(); diff --git a/lang/en/admin.php b/lang/en/admin.php index 221a232eacf00..8d8d667876fe6 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -988,7 +988,7 @@ $string['profileconfirmcategorydeletion'] = 'There is/are {$a} field/s in this category which will be moved into the category above (or below if in the top category).
Do you still wish to delete this category?'; $string['profileconfirmfielddeletion'] = 'There is/are {$a} user record/s for this field which will be deleted.
Do you still wish to delete this field?'; $string['profilecreatecategory'] = 'Create a new profile category'; -$string['profilecreatefield'] = 'Create a new profile field:'; +$string['profilecreatefield'] = 'Create a new profile field'; $string['profilecreatenewcategory'] = 'Creating a new category'; $string['profilecreatenewfield'] = 'Creating a new \'{$a}\' profile field'; $string['profiledefaultcategory'] = 'Other fields'; diff --git a/user/amd/build/edit_profile_fields.min.js b/user/amd/build/edit_profile_fields.min.js index 77c34c5bd718b..50d9ec4d57b99 100644 --- a/user/amd/build/edit_profile_fields.min.js +++ b/user/amd/build/edit_profile_fields.min.js @@ -1,2 +1,2 @@ -define ("core_user/edit_profile_fields",["exports","core_form/modalform","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d={actions:{editCategory:"[data-action=\"editcategory\"]"}};a.init=function init(){document.addEventListener("click",function(a){var e=a.target.closest(d.actions.editCategory);if(e){a.preventDefault();var f=e.getAttribute("data-id")?(0,c.get_string)("profileeditcategory","admin",e.getAttribute("data-name")):(0,c.get_string)("profilecreatenewcategory","admin"),g=new b.default({formClass:"core_user\\form\\profile_category_form",args:{id:e.getAttribute("data-id")},modalConfig:{title:f},returnFocus:e});g.addEventListener(g.events.FORM_SUBMITTED,function(){return window.location.reload()});g.show()}})}}); +define ("core_user/edit_profile_fields",["exports","core_form/modalform","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d={actions:{editCategory:"[data-action=\"editcategory\"]",editField:"[data-action=\"editfield\"]",createField:"[data-action=\"createfield\"]"}};a.init=function init(){document.addEventListener("click",function(a){var e=a.target.closest(d.actions.editCategory);if(e){a.preventDefault();var f=e.getAttribute("data-id")?(0,c.get_string)("profileeditcategory","admin",e.getAttribute("data-name")):(0,c.get_string)("profilecreatenewcategory","admin"),g=new b.default({formClass:"core_user\\form\\profile_category_form",args:{id:e.getAttribute("data-id")},modalConfig:{title:f},returnFocus:e});g.addEventListener(g.events.FORM_SUBMITTED,function(){return window.location.reload()});g.show()}e=a.target.closest(d.actions.editField);if(e){a.preventDefault();var h=new b.default({formClass:"core_user\\form\\profile_field_form",args:{id:e.getAttribute("data-id")},modalConfig:{title:(0,c.get_string)("profileeditfield","admin",e.getAttribute("data-name"))},returnFocus:e});h.addEventListener(h.events.FORM_SUBMITTED,function(){return window.location.reload()});h.show()}e=a.target.closest(d.actions.createField);if(e){a.preventDefault();var i=new b.default({formClass:"core_user\\form\\profile_field_form",args:{datatype:e.getAttribute("data-datatype"),categoryid:e.getAttribute("data-categoryid")},modalConfig:{title:(0,c.get_string)("profilecreatenewfield","admin",e.getAttribute("data-datatypename"))},returnFocus:e});i.addEventListener(i.events.FORM_SUBMITTED,function(){return window.location.reload()});i.show()}})}}); //# sourceMappingURL=edit_profile_fields.min.js.map diff --git a/user/amd/build/edit_profile_fields.min.js.map b/user/amd/build/edit_profile_fields.min.js.map index 8b7e6ae93684d..1140d959d1a32 100644 --- a/user/amd/build/edit_profile_fields.min.js.map +++ b/user/amd/build/edit_profile_fields.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/edit_profile_fields.js"],"names":["Selectors","actions","editCategory","init","document","addEventListener","e","element","target","closest","preventDefault","title","getAttribute","form","ModalForm","formClass","args","id","modalConfig","returnFocus","events","FORM_SUBMITTED","window","location","reload","show"],"mappings":"iLAeA,uD,GAYMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,gCADT,CADK,C,QAME,QAAPC,CAAAA,IAAO,EAAM,CACtBC,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAASC,CAAT,CAAY,CAC3C,GAAMC,CAAAA,CAAO,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBT,CAAS,CAACC,OAAV,CAAkBC,YAAnC,CAAhB,CACA,GAAIK,CAAJ,CAAa,CACTD,CAAC,CAACI,cAAF,GADS,GAEHC,CAAAA,CAAK,CAAGJ,CAAO,CAACK,YAAR,CAAqB,SAArB,EACV,iBAAU,qBAAV,CAAiC,OAAjC,CAA0CL,CAAO,CAACK,YAAR,CAAqB,WAArB,CAA1C,CADU,CAEV,iBAAU,0BAAV,CAAsC,OAAtC,CAJK,CAKHC,CAAI,CAAG,GAAIC,UAAJ,CAAc,CACvBC,SAAS,CAAE,wCADY,CAEvBC,IAAI,CAAE,CAACC,EAAE,CAAEV,CAAO,CAACK,YAAR,CAAqB,SAArB,CAAL,CAFiB,CAGvBM,WAAW,CAAE,CAACP,KAAK,CAALA,CAAD,CAHU,CAIvBQ,WAAW,CAAEZ,CAJU,CAAd,CALJ,CAWTM,CAAI,CAACR,gBAAL,CAAsBQ,CAAI,CAACO,MAAL,CAAYC,cAAlC,CAAkD,iBAAMC,CAAAA,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EAAN,CAAlD,EACAX,CAAI,CAACY,IAAL,EACH,CACJ,CAhBD,CAiBH,C","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\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\n\n/**\n * User profile fields editor\n *\n * @module core_user/edit_profile_fields\n * @package core_user\n * @copyright 2021 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n actions: {\n editCategory: '[data-action=\"editcategory\"]',\n },\n};\n\nexport const init = () => {\n document.addEventListener('click', function(e) {\n const element = e.target.closest(Selectors.actions.editCategory);\n if (element) {\n e.preventDefault();\n const title = element.getAttribute('data-id') ?\n getString('profileeditcategory', 'admin', element.getAttribute('data-name')) :\n getString('profilecreatenewcategory', 'admin');\n const form = new ModalForm({\n formClass: 'core_user\\\\form\\\\profile_category_form',\n args: {id: element.getAttribute('data-id')},\n modalConfig: {title},\n returnFocus: element,\n });\n form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload());\n form.show();\n }\n });\n};"],"file":"edit_profile_fields.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/edit_profile_fields.js"],"names":["Selectors","actions","editCategory","editField","createField","init","document","addEventListener","e","element","target","closest","preventDefault","title","getAttribute","form","ModalForm","formClass","args","id","modalConfig","returnFocus","events","FORM_SUBMITTED","window","location","reload","show","datatype","categoryid"],"mappings":"iLAeA,uD,GAYMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,gCADT,CAELC,SAAS,CAAE,6BAFN,CAGLC,WAAW,CAAE,+BAHR,CADK,C,QAQE,QAAPC,CAAAA,IAAO,EAAM,CACtBC,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAASC,CAAT,CAAY,CAC3C,GAAIC,CAAAA,CAAO,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBX,CAAS,CAACC,OAAV,CAAkBC,YAAnC,CAAd,CACA,GAAIO,CAAJ,CAAa,CACTD,CAAC,CAACI,cAAF,GADS,GAEHC,CAAAA,CAAK,CAAGJ,CAAO,CAACK,YAAR,CAAqB,SAArB,EACV,iBAAU,qBAAV,CAAiC,OAAjC,CAA0CL,CAAO,CAACK,YAAR,CAAqB,WAArB,CAA1C,CADU,CAEV,iBAAU,0BAAV,CAAsC,OAAtC,CAJK,CAKHC,CAAI,CAAG,GAAIC,UAAJ,CAAc,CACvBC,SAAS,CAAE,wCADY,CAEvBC,IAAI,CAAE,CAACC,EAAE,CAAEV,CAAO,CAACK,YAAR,CAAqB,SAArB,CAAL,CAFiB,CAGvBM,WAAW,CAAE,CAACP,KAAK,CAALA,CAAD,CAHU,CAIvBQ,WAAW,CAAEZ,CAJU,CAAd,CALJ,CAWTM,CAAI,CAACR,gBAAL,CAAsBQ,CAAI,CAACO,MAAL,CAAYC,cAAlC,CAAkD,iBAAMC,CAAAA,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EAAN,CAAlD,EACAX,CAAI,CAACY,IAAL,EACH,CAEDlB,CAAO,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBX,CAAS,CAACC,OAAV,CAAkBE,SAAnC,CAAV,CACA,GAAIM,CAAJ,CAAa,CACTD,CAAC,CAACI,cAAF,GACA,GAAMG,CAAAA,CAAI,CAAG,GAAIC,UAAJ,CAAc,CACvBC,SAAS,CAAE,qCADY,CAEvBC,IAAI,CAAE,CAACC,EAAE,CAAEV,CAAO,CAACK,YAAR,CAAqB,SAArB,CAAL,CAFiB,CAGvBM,WAAW,CAAE,CAACP,KAAK,CAAE,iBAAU,kBAAV,CAA8B,OAA9B,CAAuCJ,CAAO,CAACK,YAAR,CAAqB,WAArB,CAAvC,CAAR,CAHU,CAIvBO,WAAW,CAAEZ,CAJU,CAAd,CAAb,CAMAM,CAAI,CAACR,gBAAL,CAAsBQ,CAAI,CAACO,MAAL,CAAYC,cAAlC,CAAkD,iBAAMC,CAAAA,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EAAN,CAAlD,EACAX,CAAI,CAACY,IAAL,EACH,CAEDlB,CAAO,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBX,CAAS,CAACC,OAAV,CAAkBG,WAAnC,CAAV,CACA,GAAIK,CAAJ,CAAa,CACTD,CAAC,CAACI,cAAF,GACA,GAAMG,CAAAA,CAAI,CAAG,GAAIC,UAAJ,CAAc,CACvBC,SAAS,CAAE,qCADY,CAEvBC,IAAI,CAAE,CAACU,QAAQ,CAAEnB,CAAO,CAACK,YAAR,CAAqB,eAArB,CAAX,CAAkDe,UAAU,CAAEpB,CAAO,CAACK,YAAR,CAAqB,iBAArB,CAA9D,CAFiB,CAGvBM,WAAW,CAAE,CAACP,KAAK,CAAE,iBAAU,uBAAV,CAAmC,OAAnC,CAA4CJ,CAAO,CAACK,YAAR,CAAqB,mBAArB,CAA5C,CAAR,CAHU,CAIvBO,WAAW,CAAEZ,CAJU,CAAd,CAAb,CAMAM,CAAI,CAACR,gBAAL,CAAsBQ,CAAI,CAACO,MAAL,CAAYC,cAAlC,CAAkD,iBAAMC,CAAAA,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EAAN,CAAlD,EACAX,CAAI,CAACY,IAAL,EACH,CACJ,CA1CD,CA2CH,C","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\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\n\n/**\n * User profile fields editor\n *\n * @module core_user/edit_profile_fields\n * @package core_user\n * @copyright 2021 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n actions: {\n editCategory: '[data-action=\"editcategory\"]',\n editField: '[data-action=\"editfield\"]',\n createField: '[data-action=\"createfield\"]',\n },\n};\n\nexport const init = () => {\n document.addEventListener('click', function(e) {\n let element = e.target.closest(Selectors.actions.editCategory);\n if (element) {\n e.preventDefault();\n const title = element.getAttribute('data-id') ?\n getString('profileeditcategory', 'admin', element.getAttribute('data-name')) :\n getString('profilecreatenewcategory', 'admin');\n const form = new ModalForm({\n formClass: 'core_user\\\\form\\\\profile_category_form',\n args: {id: element.getAttribute('data-id')},\n modalConfig: {title},\n returnFocus: element,\n });\n form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload());\n form.show();\n }\n\n element = e.target.closest(Selectors.actions.editField);\n if (element) {\n e.preventDefault();\n const form = new ModalForm({\n formClass: 'core_user\\\\form\\\\profile_field_form',\n args: {id: element.getAttribute('data-id')},\n modalConfig: {title: getString('profileeditfield', 'admin', element.getAttribute('data-name'))},\n returnFocus: element,\n });\n form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload());\n form.show();\n }\n\n element = e.target.closest(Selectors.actions.createField);\n if (element) {\n e.preventDefault();\n const form = new ModalForm({\n formClass: 'core_user\\\\form\\\\profile_field_form',\n args: {datatype: element.getAttribute('data-datatype'), categoryid: element.getAttribute('data-categoryid')},\n modalConfig: {title: getString('profilecreatenewfield', 'admin', element.getAttribute('data-datatypename'))},\n returnFocus: element,\n });\n form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload());\n form.show();\n }\n });\n};"],"file":"edit_profile_fields.min.js"} \ No newline at end of file diff --git a/user/amd/src/edit_profile_fields.js b/user/amd/src/edit_profile_fields.js index 29662356b19ce..04916db48253a 100644 --- a/user/amd/src/edit_profile_fields.js +++ b/user/amd/src/edit_profile_fields.js @@ -28,12 +28,14 @@ import {get_string as getString} from 'core/str'; const Selectors = { actions: { editCategory: '[data-action="editcategory"]', + editField: '[data-action="editfield"]', + createField: '[data-action="createfield"]', }, }; export const init = () => { document.addEventListener('click', function(e) { - const element = e.target.closest(Selectors.actions.editCategory); + let element = e.target.closest(Selectors.actions.editCategory); if (element) { e.preventDefault(); const title = element.getAttribute('data-id') ? @@ -48,5 +50,31 @@ export const init = () => { form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload()); form.show(); } + + element = e.target.closest(Selectors.actions.editField); + if (element) { + e.preventDefault(); + const form = new ModalForm({ + formClass: 'core_user\\form\\profile_field_form', + args: {id: element.getAttribute('data-id')}, + modalConfig: {title: getString('profileeditfield', 'admin', element.getAttribute('data-name'))}, + returnFocus: element, + }); + form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload()); + form.show(); + } + + element = e.target.closest(Selectors.actions.createField); + if (element) { + e.preventDefault(); + const form = new ModalForm({ + formClass: 'core_user\\form\\profile_field_form', + args: {datatype: element.getAttribute('data-datatype'), categoryid: element.getAttribute('data-categoryid')}, + modalConfig: {title: getString('profilecreatenewfield', 'admin', element.getAttribute('data-datatypename'))}, + returnFocus: element, + }); + form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload()); + form.show(); + } }); }; \ No newline at end of file diff --git a/user/classes/form/profile_field_form.php b/user/classes/form/profile_field_form.php index 589c38964ebf0..ff2935c95d009 100644 --- a/user/classes/form/profile_field_form.php +++ b/user/classes/form/profile_field_form.php @@ -14,49 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file contains the Field Form used for profile fields. - * - * @package core_user - * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace core_user\form; +use context; +use core_form\dynamic_form; +use moodle_url; use profile_define_base; -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->dirroot.'/lib/formslib.php'); - /** - * Class field_form + * Class field_form used for profile fields. * + * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class profile_field_form extends \moodleform { +class profile_field_form extends dynamic_form { /** @var profile_define_base $field */ public $field; + /** @var \stdClass */ + protected $fieldrecord; /** * Define the form */ public function definition () { global $CFG; + require_once($CFG->dirroot.'/user/profile/definelib.php'); $mform = $this->_form; // Everything else is dependant on the data type. - $datatype = $this->_customdata; + $datatype = $this->get_field_record()->datatype; require_once($CFG->dirroot.'/user/profile/field/'.$datatype.'/define.class.php'); $newfield = 'profile_define_'.$datatype; $this->field = new $newfield(); - $strrequired = get_string('required'); - // Add some extra hidden fields. $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); @@ -66,8 +59,6 @@ public function definition () { $mform->setType('datatype', PARAM_ALPHA); $this->field->define_form($mform); - - $this->add_action_buttons(true); } @@ -92,10 +83,100 @@ public function validation($data, $files) { /** * Returns the defined editors for the field. - * @return mixed + * @return array + */ + public function editors(): array { + $editors = $this->field->define_editors(); + return is_array($editors) ? $editors : []; + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * Checks if current user has access to this form, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('moodle/site:config', $this->get_context_for_dynamic_submission()); + } + + /** + * Process the form submission, used if form was submitted via AJAX + */ + public function process_dynamic_submission() { + global $CFG; + require_once($CFG->dirroot.'/user/profile/definelib.php'); + profile_save_field($this->get_data(), $this->editors()); + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + $field = $this->get_field_record(); + + // Clean and prepare description for the editor. + $field->description = clean_text($field->description, $field->descriptionformat); + $field->description = array('text' => $field->description, 'format' => $field->descriptionformat, 'itemid' => 0); + // Convert the data format for. + if (is_array($this->editors())) { + foreach ($this->editors() as $editor) { + if (isset($field->$editor)) { + $field->$editor = clean_text($field->$editor, $field->{$editor.'format'}); + $field->$editor = array('text' => $field->$editor, 'format' => $field->{$editor.'format'}, 'itemid' => 0); + } + } + } + + $this->set_data($field); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): moodle_url { + $id = $this->optional_param('id', 0, PARAM_INT); + $datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN); + return new moodle_url('/user/profile/index.php', + ['action' => 'editfield', 'id' => $id, 'datatype' => $id ? null : $datatype]); + } + + /** + * Record for the field from the database (or generic record for a new field) + * + * @return false|mixed|\stdClass + * @throws \coding_exception + * @throws \dml_exception */ - public function editors() { - return $this->field->define_editors(); + public function get_field_record() { + global $DB; + + if (!$this->fieldrecord) { + $id = $this->optional_param('id', 0, PARAM_INT); + if (!$id || !($this->fieldrecord = $DB->get_record('user_info_field', ['id' => $id]))) { + $datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN); + $this->fieldrecord = new \stdClass(); + $this->fieldrecord->datatype = $datatype; + $this->fieldrecord->description = ''; + $this->fieldrecord->descriptionformat = FORMAT_HTML; + $this->fieldrecord->defaultdata = ''; + $this->fieldrecord->defaultdataformat = FORMAT_HTML; + $this->fieldrecord->categoryid = $this->optional_param('categoryid', 0, PARAM_INT); + } + if (!\core_component::get_component_directory('profilefield_'.$this->fieldrecord->datatype)) { + throw new \moodle_exception('fieldnotfound', 'customfield'); + } + } + + return $this->fieldrecord; } } diff --git a/user/profile/definelib.php b/user/profile/definelib.php index ee654c6aa4c3d..f9c2e47d3661d 100644 --- a/user/profile/definelib.php +++ b/user/profile/definelib.php @@ -514,13 +514,18 @@ function profile_save_category(stdClass $data) { /** * Edit a category * + * @deprecated since Moodle 3.11 MDL-71051 - please do not use this function any more. + * @todo MDL-71413 This will be deleted in Moodle 4.3. + * @see profile_save_category() + * * @param int $id * @param string $redirect */ function profile_edit_category($id, $redirect) { global $DB, $OUTPUT, $CFG; - debugging('Function profile_edit_category() deprecated without replacement', DEBUG_DEVELOPER); + debugging('Function profile_edit_category() is deprecated without replacement, see also profile_save_category()', + DEBUG_DEVELOPER); $categoryform = new \core_user\form\profile_category_form(); @@ -553,77 +558,72 @@ function profile_edit_category($id, $redirect) { } /** - * Edit a profile field. + * Save updated field definition or create a new field * - * @param int $id - * @param string $datatype - * @param string $redirect + * @param stdClass $data data from the form profile_field_form + * @param array $editors editors for this form field type */ -function profile_edit_field($id, $datatype, $redirect) { - global $CFG, $DB, $OUTPUT, $PAGE; - - if (!$field = $DB->get_record('user_info_field', array('id' => $id))) { - $field = new stdClass(); - $field->datatype = $datatype; - $field->description = ''; - $field->descriptionformat = FORMAT_HTML; - $field->defaultdata = ''; - $field->defaultdataformat = FORMAT_HTML; +function profile_save_field(stdClass $data, array $editors): void { + global $CFG; + + require_once($CFG->dirroot.'/user/profile/field/'.$data->datatype.'/define.class.php'); + $newfield = 'profile_define_'.$data->datatype; + /** @var profile_define_base $formfield */ + $formfield = new $newfield(); + + // Collect the description and format back into the proper data structure from the editor. + // Note: This field will ALWAYS be an editor. + $data->descriptionformat = $data->description['format']; + $data->description = $data->description['text']; + + // Check whether the default data is an editor, this is (currently) only the textarea field type. + if (is_array($data->defaultdata) && array_key_exists('text', $data->defaultdata)) { + // Collect the default data and format back into the proper data structure from the editor. + $data->defaultdataformat = $data->defaultdata['format']; + $data->defaultdata = $data->defaultdata['text']; } - // Clean and prepare description for the editor. - $field->description = clean_text($field->description, $field->descriptionformat); - $field->description = array('text' => $field->description, 'format' => $field->descriptionformat, 'itemid' => 0); - - $fieldform = new \core_user\form\profile_field_form(null, $field->datatype); - // Convert the data format for. - if (is_array($fieldform->editors())) { - foreach ($fieldform->editors() as $editor) { + if (is_array($editors)) { + foreach ($editors as $editor) { if (isset($field->$editor)) { - $field->$editor = clean_text($field->$editor, $field->{$editor.'format'}); - $field->$editor = array('text' => $field->$editor, 'format' => $field->{$editor.'format'}, 'itemid' => 0); + $field->{$editor.'format'} = $field->{$editor}['format']; + $field->$editor = $field->{$editor}['text']; } } } - $fieldform->set_data($field); + $formfield->define_save($data); + profile_reorder_fields(); + profile_reorder_categories(); +} + +/** + * Edit a profile field. + * + * @deprecated since Moodle 3.11 MDL-71051 - please do not use this function any more. + * @todo MDL-71413 This will be deleted in Moodle 4.3. + * @see profile_save_field() + * + * @param int $id + * @param string $datatype + * @param string $redirect + */ +function profile_edit_field($id, $datatype, $redirect) { + global $OUTPUT, $PAGE; + + debugging('Function profile_edit_field() is deprecated without replacement, see also profile_save_field()', + DEBUG_DEVELOPER); + + $fieldform = new \core_user\form\profile_field_form(); + $fieldform->set_data_for_dynamic_submission(); if ($fieldform->is_cancelled()) { redirect($redirect); } else { if ($data = $fieldform->get_data()) { - require_once($CFG->dirroot.'/user/profile/field/'.$datatype.'/define.class.php'); - $newfield = 'profile_define_'.$datatype; - /** @var profile_define_base $formfield */ - $formfield = new $newfield(); - - // Collect the description and format back into the proper data structure from the editor. - // Note: This field will ALWAYS be an editor. - $data->descriptionformat = $data->description['format']; - $data->description = $data->description['text']; - - // Check whether the default data is an editor, this is (currently) only the textarea field type. - if (is_array($data->defaultdata) && array_key_exists('text', $data->defaultdata)) { - // Collect the default data and format back into the proper data structure from the editor. - $data->defaultdataformat = $data->defaultdata['format']; - $data->defaultdata = $data->defaultdata['text']; - } - - // Convert the data format for. - if (is_array($fieldform->editors())) { - foreach ($fieldform->editors() as $editor) { - if (isset($field->$editor)) { - $field->{$editor.'format'} = $field->{$editor}['format']; - $field->$editor = $field->{$editor}['text']; - } - } - } - - $formfield->define_save($data); - profile_reorder_fields(); - profile_reorder_categories(); + profile_save_field($data, $fieldform->editors()); redirect($redirect); } @@ -632,7 +632,7 @@ function profile_edit_field($id, $datatype, $redirect) { if (empty($id)) { $strheading = get_string('profilecreatenewfield', 'admin', $datatypes[$datatype]); } else { - $strheading = get_string('profileeditfield', 'admin', format_string($field->name)); + $strheading = get_string('profileeditfield', 'admin', format_string($fieldform->get_field_record()->name)); } // Print the page. diff --git a/user/profile/index.php b/user/profile/index.php index 26c701dd809e1..c2806e64fbf99 100644 --- a/user/profile/index.php +++ b/user/profile/index.php @@ -88,13 +88,6 @@ echo $OUTPUT->footer(); die; break; - case 'editfield': - $id = optional_param('id', 0, PARAM_INT); - $datatype = optional_param('datatype', '', PARAM_ALPHA); - - profile_edit_field($id, $datatype, $redirect); - die; - break; default: // Normal form. } @@ -116,7 +109,10 @@ echo $OUTPUT->heading(get_string('profilefields', 'admin')); $outputcategories = []; +$options = profile_list_datatypes(); + foreach ($categories as $category) { + // Category fields. $outputfields = []; if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { foreach ($fields as $field) { @@ -129,6 +125,7 @@ $outputfields[] = [ 'id' => $field->id, 'shortname' => $field->shortname, + 'datatype' => $field->datatype, 'name' => $fieldname, 'isfirst' => !count($outputfields), 'islast' => count($outputfields) == count($fields) - 1, @@ -136,6 +133,19 @@ } } + // Add new field menu. + $menu = new \action_menu(); + $menu->set_alignment(\action_menu::BL, \action_menu::BL); + $menu->set_menu_trigger($strcreatefield); + foreach ($options as $type => $fieldname) { + $action = new \action_menu_link_secondary(new \moodle_url('#'), null, $fieldname, + ['data-action' => 'createfield', 'data-categoryid' => $category->id, 'data-datatype' => $type, + 'data-datatypename' => $fieldname]); + $menu->add($action); + } + $menu->attributes['class'] .= ' float-left mr-1'; + + // Add category information to the template. $outputcategories[] = [ 'id' => $category->id, 'name' => format_string($category->name), @@ -144,18 +154,12 @@ 'isfirst' => !count($outputcategories), 'islast' => count($outputcategories) == count($categories) - 1, 'candelete' => count($categories) > 1, + 'addfieldmenu' => $menu->export_for_template($OUTPUT), ]; } -// Create a new field link. -$options = profile_list_datatypes(); -$popupurl = new moodle_url('/user/profile/index.php?id=0&action=editfield'); -$singleselect = new single_select($popupurl, 'datatype', $options, '', ['' => get_string('choosedots')], 'newfieldform'); -$singleselect->set_label($strcreatefield); - echo $OUTPUT->render_from_template('core_user/edit_profile_fields', [ 'categories' => $outputcategories, - 'elementselect' => $singleselect->export_for_template($OUTPUT), 'sesskey' => sesskey(), 'baseurl' => (new moodle_url('/user/profile/index.php'))->out(false) ]); diff --git a/user/templates/edit_profile_fields.mustache b/user/templates/edit_profile_fields.mustache index 333743dcd3629..97faa9e4b8b42 100644 --- a/user/templates/edit_profile_fields.mustache +++ b/user/templates/edit_profile_fields.mustache @@ -47,30 +47,45 @@ "islast": true, "candelete": true } - ], - "elementselect": {"options": [{"name": "Choose..."}]} + ] } }} + + +
{{#categories}} -

- {{{name}}} - - {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}} - {{#candelete}} - - {{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}} - {{/candelete}} - {{^isfirst}} - - {{#pix}}t/up, core, {{#str}}moveup{{/str}}{{/pix}} - {{/isfirst}} - {{#isfirst}}{{#pix}}spacer, moodle{{/pix}}{{/isfirst}} - {{^islast}} - - {{#pix}}t/down, core, {{#str}}movedown{{/str}}{{/pix}} - {{/islast}} -

+
+
+
+

+ {{{name}}} + + {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}} + {{#candelete}} + + {{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}} + {{/candelete}} + {{^isfirst}} + + {{#pix}}t/up, core, {{#str}}moveup{{/str}}{{/pix}} + {{/isfirst}} + {{#isfirst}}{{#pix}}spacer, moodle{{/pix}}{{/isfirst}} + {{^islast}} + + {{#pix}}t/down, core, {{#str}}movedown{{/str}}{{/pix}} + {{/islast}} +

+
+
+ {{#addfieldmenu}}{{> core/action_menu}}{{/addfieldmenu}} +
+
+ {{#hasfields}} @@ -86,7 +101,7 @@ {{{name}}} {{/hasfields}}
- + {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}} {{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}} @@ -115,14 +130,8 @@
+
{{/categories}} - -
- -
- {{#elementselect}}{{> core/single_select}}{{/elementselect}} - {{#str}}or, lesson{{/str}} - {{#str}}profilecreatecategory, admin{{/str}}
{{#js}} diff --git a/user/tests/behat/custom_profile_fields.feature b/user/tests/behat/custom_profile_fields.feature index a17a063c470ba..31475fc59b7b0 100644 --- a/user/tests/behat/custom_profile_fields.feature +++ b/user/tests/behat/custom_profile_fields.feature @@ -17,7 +17,8 @@ Feature: Custom profile fields should be visible and editable by those with the And I log in as "admin" And I navigate to "Users > Accounts > User profile fields" in site administration - And I set the field "datatype" to "Text input" + And I click on "Create a new profile field" "link" + And I click on "Text input" "link" And I set the following fields to these values: | Short name | notvisible_field | | Name | notvisible_field | @@ -25,7 +26,8 @@ Feature: Custom profile fields should be visible and editable by those with the | Who is this field visible to? | Not visible | And I click on "Save changes" "button" - And I set the field "datatype" to "Text input" + And I click on "Create a new profile field" "link" + And I click on "Text input" "link" And I set the following fields to these values: | Short name | uservisible_field | | Name | uservisible_field | @@ -33,7 +35,8 @@ Feature: Custom profile fields should be visible and editable by those with the | Who is this field visible to? | Visible to user | And I click on "Save changes" "button" - And I set the field "datatype" to "Text input" + And I click on "Create a new profile field" "link" + And I click on "Text input" "link" And I set the following fields to these values: | Short name | everyonevisible_field | | Name | everyonevisible_field | @@ -41,7 +44,8 @@ Feature: Custom profile fields should be visible and editable by those with the | Who is this field visible to? | Visible to everyone | And I click on "Save changes" "button" - And I set the field "datatype" to "Text input" + And I click on "Create a new profile field" "link" + And I click on "Text input" "link" And I set the following fields to these values: | Short name | teachervisible_field | | Name | teachervisible_field |