From ee8d64f6efd8ec51cafdf2332f771affa256f6eb Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Wed, 28 Aug 2019 00:51:47 +1000 Subject: [PATCH] MDL-66489 enrol: Send all form data to WS and do all busines logic there It is discouraged to use JS Date functions. We should pass all raw data to PHP. --- enrol/externallib.php | 64 +++++++++++++ enrol/tests/externallib_test.php | 148 +++++++++++++++++++++++++++++ lib/db/services.php | 8 ++ user/amd/build/status_field.min.js | 2 +- user/amd/src/status_field.js | 38 +------- version.php | 2 +- 6 files changed, 226 insertions(+), 36 deletions(-) diff --git a/enrol/externallib.php b/enrol/externallib.php index 593541e131a..89cff4856fa 100644 --- a/enrol/externallib.php +++ b/enrol/externallib.php @@ -1020,6 +1020,70 @@ class core_enrol_external extends external_api { ); } + /** + * Returns description of submit_user_enrolment_form parameters. + * + * @return external_function_parameters. + */ + public static function submit_user_enrolment_form_parameters() { + return new external_function_parameters([ + 'formdata' => new external_value(PARAM_RAW, 'The data from the event form'), + ]); + } + + /** + * External function that handles the user enrolment form submission. + * + * @param string $formdata The user enrolment form data in s URI encoded param string + * @return array An array consisting of the processing result and error flag, if available + */ + public static function submit_user_enrolment_form($formdata) { + global $CFG, $DB, $PAGE; + + // Parameter validation. + $params = self::validate_parameters(self::submit_user_enrolment_form_parameters(), ['formdata' => $formdata]); + + $data = []; + parse_str($params['formdata'], $data); + + $userenrolment = $DB->get_record('user_enrolments', ['id' => $data['ue']], '*', MUST_EXIST); + $instance = $DB->get_record('enrol', ['id' => $userenrolment->enrolid], '*', MUST_EXIST); + $plugin = enrol_get_plugin($instance->enrol); + $course = get_course($instance->courseid); + $context = context_course::instance($course->id); + self::validate_context($context); + + require_once("$CFG->dirroot/enrol/editenrolment_form.php"); + $customformdata = [ + 'ue' => $userenrolment, + 'modal' => true, + 'enrolinstancename' => $plugin->get_instance_name($instance) + ]; + $mform = new enrol_user_enrolment_form(null, $customformdata, 'post', '', null, true, $data); + + if ($validateddata = $mform->get_data()) { + require_once($CFG->dirroot . '/enrol/locallib.php'); + $manager = new course_enrolment_manager($PAGE, $course); + $result = $manager->edit_enrolment($userenrolment, $validateddata); + + return ['result' => $result]; + } else { + return ['result' => false, 'validationerror' => true]; + } + } + + /** + * Returns description of submit_user_enrolment_form() result value + * + * @return external_description + */ + public static function submit_user_enrolment_form_returns() { + return new external_single_structure([ + 'result' => new external_value(PARAM_BOOL, 'True if the user\'s enrolment was successfully updated'), + 'validationerror' => new external_value(PARAM_BOOL, 'Indicates invalid form data', VALUE_DEFAULT, false), + ]); + } + /** * Returns description of unenrol_user_enrolment() parameters * diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php index da07c87ab97..ee4ca4bba3b 100644 --- a/enrol/tests/externallib_test.php +++ b/enrol/tests/externallib_test.php @@ -958,6 +958,154 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals(ENROL_USER_SUSPENDED, $ue->status); } + /** + * dataProvider for test_submit_user_enrolment_form(). + */ + public function submit_user_enrolment_form_provider() { + $now = new DateTime(); + + $nextmonth = clone($now); + $nextmonth->add(new DateInterval('P1M')); + + return [ + 'Invalid data' => [ + 'customdata' => [ + 'status' => ENROL_USER_ACTIVE, + 'timestart' => [ + 'day' => $now->format('j'), + 'month' => $now->format('n'), + 'year' => $now->format('Y'), + 'hour' => $now->format('G'), + 'minute' => 0, + 'enabled' => 1, + ], + 'timeend' => [ + 'day' => $now->format('j'), + 'month' => $now->format('n'), + 'year' => $now->format('Y'), + 'hour' => $now->format('G'), + 'minute' => 0, + 'enabled' => 1, + ], + ], + 'expectedresult' => false, + 'validationerror' => true + ], + 'Valid data' => [ + 'customdata' => [ + 'status' => ENROL_USER_ACTIVE, + 'timestart' => [ + 'day' => $now->format('j'), + 'month' => $now->format('n'), + 'year' => $now->format('Y'), + 'hour' => $now->format('G'), + 'minute' => 0, + 'enabled' => 1, + ], + 'timeend' => [ + 'day' => $nextmonth->format('j'), + 'month' => $nextmonth->format('n'), + 'year' => $nextmonth->format('Y'), + 'hour' => $nextmonth->format('G'), + 'minute' => 0, + 'enabled' => 1, + ], + ], + 'expectedresult' => true, + 'validationerror' => false + ], + 'Suspend user' => [ + 'customdata' => [ + 'status' => ENROL_USER_SUSPENDED, + ], + 'expectedresult' => true, + 'validationerror' => false + ], + ]; + } + + /** + * @param array $customdata The data we are providing to the webservice. + * @param bool $expectedresult The result we are expecting to receive from the webservice. + * @param bool $validationerror The validationerror we are expecting to receive from the webservice. + * @dataProvider submit_user_enrolment_form_provider + */ + public function test_submit_user_enrolment_form($customdata, $expectedresult, $validationerror) { + global $CFG, $DB; + + $this->resetAfterTest(true); + $datagen = $this->getDataGenerator(); + + /** @var enrol_manual_plugin $manualplugin */ + $manualplugin = enrol_get_plugin('manual'); + + $studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST); + $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST); + $course = $datagen->create_course(); + $user = $datagen->create_user(); + $teacher = $datagen->create_user(); + + $instanceid = null; + $instances = enrol_get_instances($course->id, true); + foreach ($instances as $inst) { + if ($inst->enrol == 'manual') { + $instanceid = (int)$inst->id; + break; + } + } + if (empty($instanceid)) { + $instanceid = $manualplugin->add_default_instance($course); + if (empty($instanceid)) { + $instanceid = $manualplugin->add_instance($course); + } + } + $this->assertNotNull($instanceid); + + $instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST); + $manualplugin->enrol_user($instance, $user->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE); + $manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE); + $ueid = (int) $DB->get_field( + 'user_enrolments', + 'id', + ['enrolid' => $instance->id, 'userid' => $user->id], + MUST_EXIST + ); + + // Login as teacher. + $teacher->ignoresesskey = true; + $this->setUser($teacher); + + $formdata = [ + 'ue' => $ueid, + 'ifilter' => 0, + 'status' => null, + 'timestart' => null, + 'timeend' => null, + ]; + + $formdata = array_merge($formdata, $customdata); + + require_once("$CFG->dirroot/enrol/editenrolment_form.php"); + $formdata = enrol_user_enrolment_form::mock_generate_submit_keys($formdata); + + $querystring = http_build_query($formdata, '', '&'); + + $result = external_api::clean_returnvalue( + core_enrol_external::submit_user_enrolment_form_returns(), + core_enrol_external::submit_user_enrolment_form($querystring) + ); + + $this->assertEquals( + ['result' => $expectedresult, 'validationerror' => $validationerror], + $result, + '', 0.0, 10, true); + + if (!empty($result['result'])) { + $ue = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST); + $this->assertEquals($formdata['status'], $ue->status); + } + } + /** * Test for core_enrol_external::unenrol_user_enrolment(). */ diff --git a/lib/db/services.php b/lib/db/services.php index 708f2ccdb80..397c45771ec 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -660,6 +660,14 @@ $functions = array( 'type' => 'write', 'ajax' => true, ), + 'core_enrol_submit_user_enrolment_form' => array( + 'classname' => 'core_enrol_external', + 'methodname' => 'submit_user_enrolment_form', + 'classpath' => 'enrol/externallib.php', + 'description' => 'Submit form data for enrolment form', + 'type' => 'write', + 'ajax' => true, + ), 'core_enrol_unenrol_user_enrolment' => array( 'classname' => 'core_enrol_external', 'methodname' => 'unenrol_user_enrolment', diff --git a/user/amd/build/status_field.min.js b/user/amd/build/status_field.min.js index a26e99fbe9d..8d8d633fb18 100644 --- a/user/amd/build/status_field.min.js +++ b/user/amd/build/status_field.min.js @@ -1 +1 @@ -define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment","core/ajax"],function(a,b,c,d,e,f,g,h,i){var j={EDIT_ENROLMENT:'[data-action="editenrolment"]',SHOW_DETAILS:'[data-action="showdetails"]',UNENROL:'[data-action="unenrol"]'},k=function(a){this.contextid=a.contextid,this.courseid=a.courseid,this.bindEditEnrol(),this.bindUnenrol(),this.bindStatusDetails()};return k.prototype.courseid=0,k.prototype.bindEditEnrol=function(){var a=this;b(j.EDIT_ENROLMENT).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),j=i.data("fullname"),k=h.attr("rel");b.when(c.get_string("edituserenrolment","enrol",j)).then(function(a){return f.create({large:!0,title:a,type:f.types.SAVE_CANCEL})}).done(function(b){b.getRoot().on(g.save,function(c){c.preventDefault(),a.submitEditFormAjax(b)}),b.getRoot().on(g.hidden,function(){b.destroy()}),b.setBody(a.getBody(k)),b.show()}).fail(e.exception)})},k.prototype.bindUnenrol=function(){var a=this;b(j.UNENROL).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),j=[{key:"unenrol",component:"enrol"},{key:"unenrolconfirm",component:"enrol",param:{user:i.data("fullname"),course:i.data("coursename"),enrolinstancename:i.data("enrolinstancename")}}],k=f.create({type:f.types.SAVE_CANCEL});b.when(c.get_strings(j),k).done(function(c,e){var f=c[0],i=c[1];e.setTitle(f),e.setBody(i),e.setSaveButtonText(f),e.getRoot().on(g.save,function(){var c={ueid:b(h).attr("rel")};d.preventDefault(),a.submitUnenrolFormAjax(e,c)}),e.getRoot().on(g.hidden,function(){e.destroy()}),e.show()}).fail(e.exception)})},k.prototype.bindStatusDetails=function(){b(j.SHOW_DETAILS).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),k={fullname:i.data("fullname"),coursename:i.data("coursename"),enrolinstancename:i.data("enrolinstancename"),status:i.data("status"),statusclass:i.find("span").attr("class"),timestart:i.data("timestart"),timeend:i.data("timeend"),timeenrolled:i.data("timeenrolled")},l=[{key:"enroldetails",component:"enrol"}],m=h.next(j.EDIT_ENROLMENT);m.length&&(k.editenrollink=b("
").append(m.clone()).html());var n=c.get_strings(l),o=f.create({large:!0,type:f.types.CANCEL});b.when(n,o).done(function(c,d){var e=a.render("core_user/status_details",k);d.setTitle(c[0]),d.setBody(e),m.length&&d.getRoot().on("click",j.EDIT_ENROLMENT,function(a){a.preventDefault(),d.hide(),b(m).trigger("click")}),d.show(),d.getRoot().on(g.hidden,function(){d.destroy()})}).fail(e.exception)})},k.prototype.submitEditFormAjax=function(a){var c=this,d=a.getRoot().find("form"),f=b(d).find('[name="ue"]').val(),g=b(d).find('[name="status"]').val(),h={courseid:this.courseid,ueid:f,status:g},j=b(d).find('[name="timestart[enabled]"]');if(j.is(":checked")){var k=b(d).find('[name="timestart[year]"]').val(),l=b(d).find('[name="timestart[month]"]').val()-1,m=b(d).find('[name="timestart[day]"]').val(),n=b(d).find('[name="timestart[hour]"]').val(),o=b(d).find('[name="timestart[minute]"]').val(),p=new Date(k,l,m,n,o);h.timestart=p.getTime()/1e3}var q=b(d).find('[name="timeend[enabled]"]');if(q.is(":checked")){var r=b(d).find('[name="timeend[year]"]').val(),s=b(d).find('[name="timeend[month]"]').val()-1,t=b(d).find('[name="timeend[day]"]').val(),u=b(d).find('[name="timeend[hour]"]').val(),v=b(d).find('[name="timeend[minute]"]').val(),w=new Date(r,s,t,u,v);h.timeend=w.getTime()/1e3}var x={methodname:"core_enrol_edit_user_enrolment",args:h};i.call([x])[0].done(function(b){if(b.result)a.hide(),"undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload();else{var e=JSON.stringify(d.serialize());a.setBody(c.getBody(f,e))}}).fail(e.exception)},k.prototype.submitUnenrolFormAjax=function(a,b){var c={methodname:"core_enrol_unenrol_user_enrolment",args:b};i.call([c])[0].done(function(b){b.result?(a.hide(),"undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload()):e.alert(b.errors[0].key,b.errors[0].message)}).fail(e.exception)},k.prototype.getBody=function(a,b){var c={ueid:a};return"undefined"!=typeof b&&(c.formdata=b),h.loadFragment("enrol","user_enrolment_form",this.contextid,c).fail(e.exception)},{init:function(a){new k(a)}}}); \ No newline at end of file +define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment","core/ajax"],function(a,b,c,d,e,f,g,h,i){var j={EDIT_ENROLMENT:'[data-action="editenrolment"]',SHOW_DETAILS:'[data-action="showdetails"]',UNENROL:'[data-action="unenrol"]'},k=function(a){this.contextid=a.contextid,this.courseid=a.courseid,this.bindEditEnrol(),this.bindUnenrol(),this.bindStatusDetails()};return k.prototype.courseid=0,k.prototype.bindEditEnrol=function(){var a=this;b(j.EDIT_ENROLMENT).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),j=i.data("fullname"),k=h.attr("rel");b.when(c.get_string("edituserenrolment","enrol",j)).then(function(a){return f.create({large:!0,title:a,type:f.types.SAVE_CANCEL})}).done(function(b){b.getRoot().on(g.save,function(c){c.preventDefault(),a.submitEditFormAjax(b)}),b.getRoot().on(g.hidden,function(){b.destroy()}),b.setBody(a.getBody(k)),b.show()}).fail(e.exception)})},k.prototype.bindUnenrol=function(){var a=this;b(j.UNENROL).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),j=[{key:"unenrol",component:"enrol"},{key:"unenrolconfirm",component:"enrol",param:{user:i.data("fullname"),course:i.data("coursename"),enrolinstancename:i.data("enrolinstancename")}}],k=f.create({type:f.types.SAVE_CANCEL});b.when(c.get_strings(j),k).done(function(c,e){var f=c[0],i=c[1];e.setTitle(f),e.setBody(i),e.setSaveButtonText(f),e.getRoot().on(g.save,function(){var c={ueid:b(h).attr("rel")};d.preventDefault(),a.submitUnenrolFormAjax(e,c)}),e.getRoot().on(g.hidden,function(){e.destroy()}),e.show()}).fail(e.exception)})},k.prototype.bindStatusDetails=function(){b(j.SHOW_DETAILS).click(function(d){d.preventDefault();var h=b(this),i=h.parent(),k={fullname:i.data("fullname"),coursename:i.data("coursename"),enrolinstancename:i.data("enrolinstancename"),status:i.data("status"),statusclass:i.find("span").attr("class"),timestart:i.data("timestart"),timeend:i.data("timeend"),timeenrolled:i.data("timeenrolled")},l=[{key:"enroldetails",component:"enrol"}],m=h.next(j.EDIT_ENROLMENT);m.length&&(k.editenrollink=b("
").append(m.clone()).html());var n=c.get_strings(l),o=f.create({large:!0,type:f.types.CANCEL});b.when(n,o).done(function(c,d){var e=a.render("core_user/status_details",k);d.setTitle(c[0]),d.setBody(e),m.length&&d.getRoot().on("click",j.EDIT_ENROLMENT,function(a){a.preventDefault(),d.hide(),b(m).trigger("click")}),d.show(),d.getRoot().on(g.hidden,function(){d.destroy()})}).fail(e.exception)})},k.prototype.submitEditFormAjax=function(a){var c=this,d=a.getRoot().find("form"),f=b(d).find('[name="ue"]').val(),g={methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:d.serialize()}};i.call([g])[0].done(function(b){if(b.result)a.hide(),"undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload();else{var e=JSON.stringify(d.serialize());a.setBody(c.getBody(f,e))}}).fail(e.exception)},k.prototype.submitUnenrolFormAjax=function(a,b){var c={methodname:"core_enrol_unenrol_user_enrolment",args:b};i.call([c])[0].done(function(b){b.result?(a.hide(),"undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload()):e.alert(b.errors[0].key,b.errors[0].message)}).fail(e.exception)},k.prototype.getBody=function(a,b){var c={ueid:a};return"undefined"!=typeof b&&(c.formdata=b),h.loadFragment("enrol","user_enrolment_form",this.contextid,c).fail(e.exception)},{init:function(a){new k(a)}}}); \ No newline at end of file diff --git a/user/amd/src/status_field.js b/user/amd/src/status_field.js index 865f33c977d..452ec0dab67 100644 --- a/user/amd/src/status_field.js +++ b/user/amd/src/status_field.js @@ -261,42 +261,12 @@ define(['core/templates', // User enrolment ID. var ueid = $(form).find('[name="ue"]').val(); - // Status. - var status = $(form).find('[name="status"]').val(); - - var params = { - 'courseid': this.courseid, - 'ueid': ueid, - 'status': status - }; - - // Enrol time start. - var timeStartEnabled = $(form).find('[name="timestart[enabled]"]'); - if (timeStartEnabled.is(':checked')) { - var timeStartYear = $(form).find('[name="timestart[year]"]').val(); - var timeStartMonth = $(form).find('[name="timestart[month]"]').val() - 1; - var timeStartDay = $(form).find('[name="timestart[day]"]').val(); - var timeStartHour = $(form).find('[name="timestart[hour]"]').val(); - var timeStartMinute = $(form).find('[name="timestart[minute]"]').val(); - var timeStart = new Date(timeStartYear, timeStartMonth, timeStartDay, timeStartHour, timeStartMinute); - params.timestart = timeStart.getTime() / 1000; - } - - // Enrol time end. - var timeEndEnabled = $(form).find('[name="timeend[enabled]"]'); - if (timeEndEnabled.is(':checked')) { - var timeEndYear = $(form).find('[name="timeend[year]"]').val(); - var timeEndMonth = $(form).find('[name="timeend[month]"]').val() - 1; - var timeEndDay = $(form).find('[name="timeend[day]"]').val(); - var timeEndHour = $(form).find('[name="timeend[hour]"]').val(); - var timeEndMinute = $(form).find('[name="timeend[minute]"]').val(); - var timeEnd = new Date(timeEndYear, timeEndMonth, timeEndDay, timeEndHour, timeEndMinute); - params.timeend = timeEnd.getTime() / 1000; - } var request = { - methodname: 'core_enrol_edit_user_enrolment', - args: params + methodname: 'core_enrol_submit_user_enrolment_form', + args: { + formdata: form.serialize() + } }; Ajax.call([request])[0].done(function(data) { diff --git a/version.php b/version.php index e374ee62973..cce43223121 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2019052001.10; // 20190520 = branching date YYYYMMDD - do not modify! +$version = 2019052001.11; // 20190520 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes.