MDL-71051 core_user: editing user profiles - modal form, template

This commit is contained in:
Marina Glancy
2021-04-28 11:49:27 +02:00
parent bc74e13322
commit abb2c72b26
7 changed files with 304 additions and 192 deletions
+2
View File
@@ -0,0 +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()}})}});
//# sourceMappingURL=edit_profile_fields.min.js.map
@@ -0,0 +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 <http://www.gnu.org/licenses/>.\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"}
+52
View File
@@ -0,0 +1,52 @@
// 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/>.
import ModalForm from 'core_form/modalform';
import {get_string as getString} from 'core/str';
/**
* User profile fields editor
*
* @module core_user/edit_profile_fields
* @package core_user
* @copyright 2021 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Selectors = {
actions: {
editCategory: '[data-action="editcategory"]',
},
};
export const init = () => {
document.addEventListener('click', function(e) {
const element = e.target.closest(Selectors.actions.editCategory);
if (element) {
e.preventDefault();
const title = element.getAttribute('data-id') ?
getString('profileeditcategory', 'admin', element.getAttribute('data-name')) :
getString('profilecreatenewcategory', 'admin');
const form = new ModalForm({
formClass: 'core_user\\form\\profile_category_form',
args: {id: element.getAttribute('data-id')},
modalConfig: {title},
returnFocus: element,
});
form.addEventListener(form.events.FORM_SUBMITTED, () => window.location.reload());
form.show();
}
});
};
+60 -33
View File
@@ -14,38 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the profile field category form.
*
* @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 moodleform;
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); // It must be included from a Moodle page.
}
require_once($CFG->dirroot.'/lib/formslib.php');
use context;
use core_form\dynamic_form;
use moodle_url;
/**
* Class category_form
* Modal form to edit profile category
*
* @copyright 2007 onwards Shane Elliot {@link http://pukunui.com}
* @package core_user
* @copyright 2021 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class profile_category_form extends moodleform {
class profile_category_form extends dynamic_form {
/**
* Define the form.
* Form definition
*/
public function definition () {
global $USER, $CFG;
protected function definition() {
$mform = $this->_form;
$strrequired = get_string('required');
@@ -59,9 +46,6 @@ class profile_category_form extends moodleform {
$mform->addElement('text', 'name', get_string('profilecategoryname', 'admin'), 'maxlength="255" size="30"');
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', $strrequired, 'required', null, 'client');
$this->add_action_buttons(true);
}
/**
@@ -72,18 +56,16 @@ class profile_category_form extends moodleform {
* @return array
*/
public function validation($data, $files) {
global $CFG, $DB;
global $DB;
$errors = parent::validation($data, $files);
$data = (object)$data;
$duplicate = $DB->get_field('user_info_category', 'id', array('name' => $data->name));
$duplicate = $DB->get_field('user_info_category', 'id', ['name' => $data['name']]);
// Check the name is unique.
if (!empty($data->id)) { // We are editing an existing record.
$olddata = $DB->get_record('user_info_category', array('id' => $data->id));
if (!empty($data['id'])) { // We are editing an existing record.
$olddata = $DB->get_record('user_info_category', ['id' => $data['id']]);
// Name has changed, new name in use, new name in use by another record.
$dupfound = (($olddata->name !== $data->name) && $duplicate && ($data->id != $duplicate));
$dupfound = (($olddata->name !== $data['name']) && $duplicate && ($data['id'] != $duplicate));
} else { // New profile category.
$dupfound = $duplicate;
}
@@ -94,5 +76,50 @@ class profile_category_form extends moodleform {
return $errors;
}
}
/**
* 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_category($this->get_data());
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
global $DB;
if ($id = $this->optional_param('id', 0, PARAM_INT)) {
$this->set_data($DB->get_record('user_info_category', ['id' => $id], '*', MUST_EXIST));
}
}
/**
* 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);
return new moodle_url('/user/profile/index.php',
['action' => 'editcategory', 'id' => $id]);
}
}
+30 -17
View File
@@ -484,6 +484,32 @@ function profile_list_categories() {
return array_map('format_string', $categories);
}
/**
* Create or update a profile category
*
* @param stdClass $data
* @throws coding_exception
* @throws dml_exception
*/
function profile_save_category(stdClass $data) {
global $DB;
if (empty($data->id)) {
unset($data->id);
$data->sortorder = $DB->count_records('user_info_category') + 1;
$data->id = $DB->insert_record('user_info_category', $data, true);
$createdcategory = $DB->get_record('user_info_category', array('id' => $data->id));
\core\event\user_info_category_created::create_from_category($createdcategory)->trigger();
} else {
$DB->update_record('user_info_category', $data);
$updatedcateogry = $DB->get_record('user_info_category', array('id' => $data->id));
\core\event\user_info_category_updated::create_from_category($updatedcateogry)->trigger();
}
profile_reorder_categories();
profile_purge_user_fields_cache();
}
/**
* Edit a category
@@ -492,7 +518,9 @@ function profile_list_categories() {
* @param string $redirect
*/
function profile_edit_category($id, $redirect) {
global $DB, $OUTPUT;
global $DB, $OUTPUT, $CFG;
debugging('Function profile_edit_category() deprecated without replacement', DEBUG_DEVELOPER);
$categoryform = new \core_user\form\profile_category_form();
@@ -504,23 +532,8 @@ function profile_edit_category($id, $redirect) {
redirect($redirect);
} else {
if ($data = $categoryform->get_data()) {
if (empty($data->id)) {
unset($data->id);
$data->sortorder = $DB->count_records('user_info_category') + 1;
$data->id = $DB->insert_record('user_info_category', $data, true);
$createdcategory = $DB->get_record('user_info_category', array('id' => $data->id));
\core\event\user_info_category_created::create_from_category($createdcategory)->trigger();
} else {
$DB->update_record('user_info_category', $data);
$updatedcateogry = $DB->get_record('user_info_category', array('id' => $data->id));
\core\event\user_info_category_updated::create_from_category($updatedcateogry)->trigger();
}
profile_reorder_categories();
profile_purge_user_fields_cache();
profile_save_category($data);
redirect($redirect);
}
if (empty($id)) {
+27 -142
View File
@@ -32,10 +32,7 @@ $action = optional_param('action', '', PARAM_ALPHA);
$redirect = $CFG->wwwroot.'/user/profile/index.php';
$strchangessaved = get_string('changessaved');
$strcancelled = get_string('cancelled');
$strdefaultcategory = get_string('profiledefaultcategory', 'admin');
$strnofields = get_string('profilenofieldsdefined', 'admin');
$strcreatefield = get_string('profilecreatefield', 'admin');
@@ -98,12 +95,6 @@ switch ($action) {
profile_edit_field($id, $datatype, $redirect);
die;
break;
case 'editcategory':
$id = optional_param('id', 0, PARAM_INT);
profile_edit_category($id, $redirect);
die;
break;
default:
// Normal form.
}
@@ -124,14 +115,9 @@ if (empty($categories)) {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('profilefields', 'admin'));
$outputcategories = [];
foreach ($categories as $category) {
$table = new html_table();
$table->head = array(get_string('profilefield', 'admin'), get_string('edit'));
$table->align = array('left', 'right');
$table->width = '95%';
$table->attributes['class'] = 'generaltable profilefield';
$table->data = array();
$outputfields = [];
if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) {
foreach ($fields as $field) {
$fieldname = format_string($field->name);
@@ -140,139 +126,38 @@ foreach ($categories as $category) {
if (class_exists($classname) && method_exists($classname, 'get_fieldname')) {
$fieldname = $classname::get_fieldname($field->name);
}
$table->data[] = array($fieldname, profile_field_icons($field));
$outputfields[] = [
'id' => $field->id,
'shortname' => $field->shortname,
'name' => $fieldname,
'isfirst' => !count($outputfields),
'islast' => count($outputfields) == count($fields) - 1,
];
}
}
echo $OUTPUT->heading(format_string($category->name) .' '.profile_category_icons($category));
if (count($table->data)) {
echo html_writer::table($table);
} else {
echo $OUTPUT->notification($strnofields);
}
} // End of $categories foreach.
echo '<hr />';
echo '<div class="profileeditor">';
$outputcategories[] = [
'id' => $category->id,
'name' => format_string($category->name),
'fields' => $outputfields,
'hasfields' => count($outputfields),
'isfirst' => !count($outputcategories),
'islast' => count($outputcategories) == count($categories) - 1,
'candelete' => count($categories) > 1,
];
}
// Create a new field link.
$options = profile_list_datatypes();
$popupurl = new moodle_url('/user/profile/index.php?id=0&action=editfield');
echo $OUTPUT->single_select($popupurl, 'datatype', $options, '', array('' => get_string('choosedots')), 'newfieldform', array('label' => $strcreatefield));
$singleselect = new single_select($popupurl, 'datatype', $options, '', ['' => get_string('choosedots')], 'newfieldform');
$singleselect->set_label($strcreatefield);
// Add a div with a class so themers can hide, style or reposition the text.
html_writer::start_tag('div', array('class' => 'adminuseractionhint'));
echo get_string('or', 'lesson');
html_writer::end_tag('div');
// Create a new category link.
$options = array('action' => 'editcategory');
echo $OUTPUT->single_button(new moodle_url('index.php', $options), get_string('profilecreatecategory', 'admin'));
echo '</div>';
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)
]);
echo $OUTPUT->footer();
die;
/***** Some functions relevant to this script *****/
/**
* Create a string containing the editing icons for the user profile categories
* @param stdClass $category the category object
* @return string the icon string
*/
function profile_category_icons($category) {
global $CFG, $USER, $DB, $OUTPUT;
$strdelete = get_string('delete');
$strmoveup = get_string('moveup');
$strmovedown = get_string('movedown');
$stredit = get_string('edit');
$categorycount = $DB->count_records('user_info_category');
$fieldcount = $DB->count_records('user_info_field', array('categoryid' => $category->id));
// Edit.
$editstr = '<a title="'.$stredit.'" href="index.php?id='.$category->id.'&amp;action=editcategory">' .
$OUTPUT->pix_icon('t/edit', $stredit) .'</a> ';
// Delete.
// Can only delete the last category if there are no fields in it.
if (($categorycount > 1) or ($fieldcount == 0)) {
$editstr .= '<a title="'.$strdelete.'"';
$editstr .= ' href="index.php?id='.$category->id.'&amp;action=deletecategory&amp;sesskey='.sesskey() . '">';
$editstr .= $OUTPUT->pix_icon('t/delete', $strdelete).'</a> ';
} else {
$editstr .= $OUTPUT->spacer() . ' ';
}
// Move up.
if ($category->sortorder > 1) {
$editstr .= '<a title="'.$strmoveup.'" ';
$editstr .= ' href="index.php?id='.$category->id.'&amp;action=movecategory&amp;dir=up&amp;sesskey='.sesskey().'">';
$editstr .= $OUTPUT->pix_icon('t/up', $strmoveup) . '</a> ';
} else {
$editstr .= $OUTPUT->spacer() . ' ';
}
// Move down.
if ($category->sortorder < $categorycount) {
$editstr .= '<a title="'.$strmovedown.'" ';
$editstr .= ' href="index.php?id='.$category->id.'&amp;action=movecategory&amp;dir=down&amp;sesskey='.sesskey().'">';
$editstr .= $OUTPUT->pix_icon('t/down', $strmovedown) . '</a> ';
} else {
$editstr .= $OUTPUT->spacer() . ' ';
}
return $editstr;
}
/**
* Create a string containing the editing icons for the user profile fields
* @param stdClass $field the field object
* @return string the icon string
*/
function profile_field_icons($field) {
global $CFG, $USER, $DB, $OUTPUT;
$strdelete = get_string('delete');
$strmoveup = get_string('moveup');
$strmovedown = get_string('movedown');
$stredit = get_string('edit');
$fieldcount = $DB->count_records('user_info_field', array('categoryid' => $field->categoryid));
$datacount = $DB->count_records('user_info_data', array('fieldid' => $field->id));
// Edit.
$editstr = '<a title="'.$stredit.'" href="index.php?id='.$field->id.'&amp;action=editfield">';
$editstr .= $OUTPUT->pix_icon('t/edit', $stredit) . '</a> ';
// Delete.
$editstr .= '<a title="'.$strdelete.'" href="index.php?id='.$field->id.'&amp;action=deletefield&amp;sesskey='.sesskey().'">';
$editstr .= $OUTPUT->pix_icon('t/delete', $strdelete) . '</a> ';
// Move up.
if ($field->sortorder > 1) {
$editstr .= '<a title="'.$strmoveup.'" ';
$editstr .= ' href="index.php?id='.$field->id.'&amp;action=movefield&amp;dir=up&amp;sesskey='.sesskey().'">';
$editstr .= $OUTPUT->pix_icon('t/up', $strmoveup) . '</a> ';
} else {
$editstr .= $OUTPUT->spacer() . ' ';
}
// Move down.
if ($field->sortorder < $fieldcount) {
$editstr .= '<a title="'.$strmovedown.'" ';
$editstr .= ' href="index.php?id='.$field->id.'&amp;action=movefield&amp;dir=down&amp;sesskey='.sesskey().'">';
$editstr .= $OUTPUT->pix_icon('t/down', $strmovedown) . '</a> ';
} else {
$editstr .= $OUTPUT->spacer() . ' ';
}
return $editstr;
}
+132
View File
@@ -0,0 +1,132 @@
{{!
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/>.
}}
{{!
@template core_user/edit_profile_fields
UI for editing profile fields
Example context (json):
{
"baseurl": "index.php",
"sesskey": "12345",
"categories": [
{
"id": 1,
"name": "Cat1",
"fields": [
{"id": 1, "name": "Field1", "isfirst": true, "islast": false},
{"id": 2, "name": "Field2", "isfirst": false, "islast": false},
{"id": 3, "name": "Field3", "isfirst": false, "islast": true}
],
"hasfields": true,
"isfirst": true,
"candelete": true
},
{
"id": 2,
"name": "Cat2",
"candelete": true
},
{
"id": 3,
"name": "Cat3",
"islast": true,
"candelete": true
}
],
"elementselect": {"options": [{"name": "Choose..."}]}
}
}}
{{#categories}}
<h2>
{{{name}}}
<a href="#" data-action="editcategory" data-id="{{id}}" data-name="{{name}}">
{{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}</a>
{{#candelete}}
<a href="{{baseurl}}?action=deletecategory&id={{id}}&sesskey={{sesskey}}">
{{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}}</a>
{{/candelete}}
{{^isfirst}}
<a href="{{baseurl}}?id={{id}}&action=movecategory&dir=up&amp;sesskey={{sesskey}}">
{{#pix}}t/up, core, {{#str}}moveup{{/str}}{{/pix}}</a>
{{/isfirst}}
{{#isfirst}}{{#pix}}spacer, moodle{{/pix}}{{/isfirst}}
{{^islast}}
<a href="{{baseurl}}?id={{id}}&action=movecategory&dir=down&amp;sesskey={{sesskey}}">
{{#pix}}t/down, core, {{#str}}movedown{{/str}}{{/pix}}</a>
{{/islast}}
</h2>
<table class="generaltable fullwidth profilefield">
{{#hasfields}}
<thead>
<tr>
<th scope="col" class="col-8">{{#str}}profilefield, admin{{/str}}</th>
<th scope="col" class="col-3 text-right">{{#str}}edit{{/str}}</th>
</tr>
</thead>
<tbody>
{{#fields}}
<tr>
<td class="col-8">
{{{name}}}
</td>
<td class="col-3 text-right">
<a href="{{baseurl}}?action=editfield&id={{id}}">
{{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}</a>
<a href="{{baseurl}}?action=deletefield&id={{id}}&sesskey={{sesskey}}">
{{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}}</a>
{{^isfirst}}
<a href="{{baseurl}}?id={{id}}&action=movefield&dir=up&amp;sesskey={{sesskey}}">
{{#pix}}t/up, core, {{#str}}moveup{{/str}}{{/pix}}</a>
{{/isfirst}}
{{#isfirst}}{{#pix}}spacer, moodle{{/pix}}{{/isfirst}}
{{^islast}}
<a href="{{baseurl}}?id={{id}}&action=movefield&dir=down&amp;sesskey={{sesskey}}">
{{#pix}}t/down, core, {{#str}}movedown{{/str}}{{/pix}}</a>
{{/islast}}
{{#islast}}{{#pix}}spacer, moodle{{/pix}}{{/islast}}
</td>
</tr>
{{/fields}}
</tbody>
{{/hasfields}}
{{^hasfields}}
<thead>
<tr class="nofields alert alert-danger alert-block fade in">
<td>
{{#str}}profilenofieldsdefined, admin{{/str}}
</td>
</tr>
</thead>
{{/hasfields}}
</table>
{{/categories}}
<hr>
<div class="profileeditor">
{{#elementselect}}{{> core/single_select}}{{/elementselect}}
{{#str}}or, lesson{{/str}}
<a tabindex="0" role="button" class="btn btn-secondary" data-action="editcategory">{{#str}}profilecreatecategory, admin{{/str}}</a>
</div>
{{#js}}
require(['core_user/edit_profile_fields'], function(s) {
s.init();
});
{{/js}}