diff --git a/admin/tool/uploadcourse/classes/course.php b/admin/tool/uploadcourse/classes/course.php index 655d75b87e1..b58f655d0a2 100644 --- a/admin/tool/uploadcourse/classes/course.php +++ b/admin/tool/uploadcourse/classes/course.php @@ -22,6 +22,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use tool_uploadcourse\permissions; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); require_once($CFG->dirroot . '/course/lib.php'); @@ -448,6 +450,11 @@ class tool_uploadcourse_course { return false; } + if ($error = permissions::check_permission_to_delete($this->shortname)) { + $this->error('coursedeletionpermission', $error); + return false; + } + $this->do = self::DO_DELETE; return true; } @@ -680,9 +687,20 @@ class tool_uploadcourse_course { return false; } + if ($error = permissions::check_permission_to_update($coursedata)) { + $this->error('cannotupdatepermission', $error); + return false; + } + $this->do = self::DO_UPDATE; } else { $coursedata = $this->get_final_create_data($coursedata); + + if ($error = permissions::check_permission_to_create($coursedata)) { + $this->error('courseuploadnotallowed', $error); + return false; + } + $this->do = self::DO_CREATE; } @@ -799,6 +817,7 @@ class tool_uploadcourse_course { $this->data = $coursedata; // Get enrolment data. Where the course already exists, we can also perform validation. + // Some data is impossible to validate without the existing course, we will do it again during actual upload. $this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata); $courseid = $coursedata['id'] ?? 0; $errors = $this->validate_enrolment_data($courseid, $this->enrolmentdata); @@ -823,6 +842,11 @@ class tool_uploadcourse_course { return false; } + if ($this->restoredata && ($error = permissions::check_permission_to_restore($this->do, $this->data))) { + $this->error('courserestorepermission', $error); + return false; + } + // We can only reset courses when allowed and we are updating the course. if ($this->importoptions['reset'] || $this->options['reset']) { if ($this->do !== self::DO_UPDATE) { @@ -833,6 +857,11 @@ class tool_uploadcourse_course { $this->error('courseresetnotallowed', new lang_string('courseresetnotallowed', 'tool_uploadcourse')); return false; } + + if ($error = permissions::check_permission_to_reset($this->data)) { + $this->error('courseresetpermission', $error); + return false; + } } return true; @@ -889,7 +918,7 @@ class tool_uploadcourse_course { $rc->execute_plan(); $this->status('courserestored', new lang_string('courserestored', 'tool_uploadcourse')); } else { - $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse')); + $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringcourse', 'tool_uploadcourse')); } $rc->destroy(); } @@ -913,7 +942,7 @@ class tool_uploadcourse_course { /** * Validate passed enrolment data against an existing course * - * @param int $courseid + * @param int $courseid id of the course where enrolment methods are created/updated or 0 if it is a new course * @param array[] $enrolmentdata * @return lang_string[] Errors keyed on error code */ @@ -1050,8 +1079,13 @@ class tool_uploadcourse_course { // Create/update enrolment. $plugin = $enrolmentplugins[$enrolmethod]; - if ($plugin->is_csv_upload_supported()) { + // In case we could not properly validate enrolment data before the course existed + // let's repeat it again here. + $errors = $plugin->validate_enrol_plugin_data($method, $course->id); + + if (!$errors) { $status = ($todisable) ? ENROL_INSTANCE_DISABLED : ENROL_INSTANCE_ENABLED; + $method += ['status' => $status, 'courseid' => $course->id, 'id' => $instance->id ?? null]; $method = $plugin->fill_enrol_custom_fields($method, $course->id); // Create a new instance if necessary. @@ -1150,9 +1184,9 @@ class tool_uploadcourse_course { $plugin->update_instance($instance, $modifiedinstance); } else { - $this->error('errorunsupportedmethod', - new lang_string('errorunsupportedmethod', 'tool_uploadcourse', - $enrolmethod)); + foreach ($errors as $key => $message) { + $this->error($key, $message); + } } } } diff --git a/admin/tool/uploadcourse/classes/helper.php b/admin/tool/uploadcourse/classes/helper.php index 8d26c7590e1..95291f6bdc8 100644 --- a/admin/tool/uploadcourse/classes/helper.php +++ b/admin/tool/uploadcourse/classes/helper.php @@ -540,6 +540,11 @@ class tool_uploadcourse_helper { $params = array('idnumber' => $idnumber); $id = $DB->get_field_select('course_categories', 'id', 'idnumber = :idnumber', $params, IGNORE_MISSING); + if ($id && !core_course_category::get($id, IGNORE_MISSING)) { + // Category is not visible to the current user. + $id = false; + } + // Little hack to be able to differenciate between the cache not set and a category not found. if ($id === false) { $id = -1; @@ -578,6 +583,11 @@ class tool_uploadcourse_helper { break; } $record = reset($records); + if (!core_course_category::get($id, IGNORE_MISSING)) { + // Category is not visible to the current user. + $id = -1; + break; + } $id = $record->id; $parent = $record->id; } else { diff --git a/admin/tool/uploadcourse/classes/permissions.php b/admin/tool/uploadcourse/classes/permissions.php new file mode 100644 index 00000000000..e27909a773a --- /dev/null +++ b/admin/tool/uploadcourse/classes/permissions.php @@ -0,0 +1,262 @@ +. + +namespace tool_uploadcourse; + +use context_course; +use context_coursecat; +use core_course_category; +use core_tag_tag; +use lang_string; +use tool_uploadcourse_course; + +/** + * Checks various permissions related to the course upload process. + * + * @package tool_uploadcourse + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class permissions { + + /** + * Check permission to use tool_uploadcourse in a given category. + * + * @param int $catid + * @param lang_string|null $customerror + * @return lang_string|null + */ + protected static function check_permission_to_use_uploadcourse_tool( + int $catid, + ?lang_string $customerror = null + ): ?lang_string { + $category = core_course_category::get($catid, IGNORE_MISSING); + if (!$category || !has_capability('tool/uploadcourse:use', $category->get_context())) { + if ($customerror) { + return $customerror; + } + return new lang_string('courseuploadnotallowed', 'tool_uploadcourse', + $category ? $category->get_formatted_name() : $catid); + } + return null; + } + + /** + * Check capabilities to delete a course and to use tool_uploadcourse for it. + * + * @param string $shortname course shortname + * @return lang_string|null + */ + public static function check_permission_to_delete(string $shortname): ?lang_string { + global $DB; + $course = $DB->get_record('course', ['shortname' => $shortname]); + if ($error = self::check_permission_to_use_uploadcourse_tool($course->category)) { + return $error; + } + + if (!has_capability('moodle/course:delete', context_course::instance($course->id))) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:delete')); + } + return null; + } + + /** + * Check capability in a course (that exists or is about to be created). + * + * @param int $do one of tool_uploadcourse_course::DO_UPDATE or tool_uploadcourse_course::DO_ADD + * @param array $coursedata data to update/create course with, must contain either 'id' or 'category' respectively + * @param string $capability capability to check + * @return lang_string|null error string or null + */ + protected static function check_capability(int $do, array $coursedata, string $capability): ?lang_string { + if ($do == tool_uploadcourse_course::DO_UPDATE) { + $context = context_course::instance($coursedata['id']); + $hascap = has_capability($capability, $context); + } else { + $catcontext = context_coursecat::instance($coursedata['category']); + $hascap = guess_if_creator_will_have_course_capability($capability, $catcontext); + } + + if (!$hascap) { + return new lang_string('nopermissions', 'error', get_capability_string($capability)); + } + + return null; + } + + /** + * Check permission to update the course. + * + * This checks capabilities: + * - to use tool_uploadcourse in the category where course is in and in the category where it will be moved to (if applicable). + * - to change course category (if applicable). + * - to update course details. + * - to force course language (if applicable). + * - to change course idnumber, shortname, fullname, summary, visibility, tags (if applicable). + * + * @param array $coursedata data to update a course with, always contains 'id' + * @return lang_string|null + */ + public static function check_permission_to_update(array $coursedata): ?lang_string { + $course = get_course($coursedata['id']); + + if ($error = self::check_permission_to_use_uploadcourse_tool($course->category, + new lang_string('courseuploadupdatenotallowed', 'tool_uploadcourse'))) { + return $error; + } + + if (!has_capability('moodle/course:update', context_course::instance($course->id))) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:update')); + } + + // If user requested to change course category check permissions to use tool in target category + // and capabilities to change category. + if (!empty($coursedata['category']) && $coursedata['category'] != $course->category) { + if ($error = self::check_permission_to_use_uploadcourse_tool($coursedata['category'])) { + return $error; + } + + if (!has_capability('moodle/course:changecategory', context_coursecat::instance($course->category))) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changecategory')); + } + + if (!has_capability('moodle/course:changecategory', context_coursecat::instance($coursedata['category']))) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changecategory')); + } + } + + $context = context_course::instance($coursedata['id']); + + // If lang is specified, check the user is allowed to set that field. + if (!empty($coursedata['lang']) && $coursedata['lang'] !== $course->lang) { + if (!has_capability('moodle/course:setforcedlanguage', $context)) { + return new lang_string('cannotforcelang', 'tool_uploadcourse'); + } + } + + // Check permission to change course idnumber. + if (array_key_exists('idnumber', $coursedata) && $coursedata['idnumber'] !== $course->idnumber && + !has_capability('moodle/course:changeidnumber', $context)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changeidnumber')); + } + + // Check permission to change course shortname. + if (array_key_exists('shortname', $coursedata) && $coursedata['shortname'] !== $course->shortname && + !has_capability('moodle/course:changeshortname', $context)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changeshortname')); + } + + // Check permission to change course fullname. + if (array_key_exists('fullname', $coursedata) && $coursedata['fullname'] !== $course->fullname && + !has_capability('moodle/course:changefullname', $context)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changefullname')); + } + + // Check permission to change course summary. + if (array_key_exists('summary', $coursedata) && $coursedata['summary'] !== $course->summary && + !has_capability('moodle/course:changesummary', $context)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:changesummary')); + } + + // Check permission to change course visibility. + if (array_key_exists('visible', $coursedata) && $coursedata['visible'] !== $course->visible && + !has_capability('moodle/course:visibility', $context)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:visibility')); + } + + // If tags are specified and enabled check if user can updat them. + if (core_tag_tag::is_enabled('core', 'course') && + (array_key_exists('tags', $coursedata) && strval($coursedata['tags']) !== '') && + ($error = self::check_capability(tool_uploadcourse_course::DO_UPDATE, $coursedata, 'moodle/course:tag'))) { + return $error; + } + + return null; + } + + /** + * Check permission to create course. + * + * This checks capabilities: + * - to use tool_uploadcourse in the category where course will be created. + * - to create a course. + * - to force course language (if applicable). + * - to set course tags (if applicable). + * + * @param array $coursedata data to create a course with, always contains 'category' + * @return lang_string|null + */ + public static function check_permission_to_create(array $coursedata): ?lang_string { + + if ($error = self::check_permission_to_use_uploadcourse_tool($coursedata['category'])) { + return $error; + } + + $catcontext = context_coursecat::instance($coursedata['category']); + + // Check user is allowed to create courses in this category. + if (!has_capability('moodle/course:create', $catcontext)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:create')); + } + + // If lang is specified, check the user is allowed to set that field. + if (!empty($coursedata['lang'])) { + if (!guess_if_creator_will_have_course_capability('moodle/course:setforcedlanguage', $catcontext)) { + return new lang_string('cannotforcelang', 'tool_uploadcourse'); + } + } + + // Check permission to change course visibility. + if (array_key_exists('visible', $coursedata) && !$coursedata['visible'] && + !guess_if_creator_will_have_course_capability('moodle/course:visibility', $catcontext)) { + return new lang_string('nopermissions', 'error', get_capability_string('moodle/course:visibility')); + } + + // If tags are specified and enabled check if user can updat them. + if (core_tag_tag::is_enabled('core', 'course') && + (array_key_exists('tags', $coursedata) && strval($coursedata['tags']) !== '') && + ($error = self::check_capability(tool_uploadcourse_course::DO_CREATE, $coursedata, 'moodle/course:tag'))) { + return $error; + } + + return null; + } + + /** + * Check if the user is able to reset a course. + * + * Capability to use the tool and update the course is already checked earlier. + * + * @param array $coursedata data to update course with, always contains 'id' + * @return lang_string|null error string or null + */ + public static function check_permission_to_reset(array $coursedata): ?lang_string { + return self::check_capability(tool_uploadcourse_course::DO_UPDATE, $coursedata, 'moodle/course:reset'); + } + + /** + * Check if the user is able to restore the mbz into a course. + * + * This method does not need to check if the course can be updated/created, this is checked earlier. + * + * @param int $do one of tool_uploadcourse_course::DO_UPDATE or tool_uploadcourse_course::DO_ADD + * @param array $coursedata data to update/create course with, must contain either 'id' or 'category' respectively + * @return lang_string|null error string or null + */ + public static function check_permission_to_restore(int $do, array $coursedata): ?lang_string { + return self::check_capability($do, $coursedata, 'moodle/restore:restorecourse'); + } +} diff --git a/admin/tool/uploadcourse/classes/processor.php b/admin/tool/uploadcourse/classes/processor.php index 53e0f1a6dcc..bbd2ddaec23 100644 --- a/admin/tool/uploadcourse/classes/processor.php +++ b/admin/tool/uploadcourse/classes/processor.php @@ -174,7 +174,7 @@ class tool_uploadcourse_processor { /** * Execute the process. * - * @param object $tracker the output tracker to use. + * @param tool_uploadcourse_tracker $tracker the output tracker to use. * @return void */ public function execute($tracker = null) { @@ -324,7 +324,7 @@ class tool_uploadcourse_processor { * This only returns passed data, along with the errors. * * @param integer $rows number of rows to preview. - * @param object $tracker the output tracker to use. + * @param tool_uploadcourse_tracker $tracker the output tracker to use. * @return array of preview data. */ public function preview($rows = 10, $tracker = null) { diff --git a/admin/tool/uploadcourse/classes/step1_form.php b/admin/tool/uploadcourse/classes/step1_form.php index 4d0543b8dc2..2d37871ed47 100644 --- a/admin/tool/uploadcourse/classes/step1_form.php +++ b/admin/tool/uploadcourse/classes/step1_form.php @@ -74,6 +74,9 @@ class tool_uploadcourse_step1_form extends tool_uploadcourse_base_form { $mform->addElement('hidden', 'showpreview', 1); $mform->setType('showpreview', PARAM_INT); + $mform->addElement('hidden', 'categoryid'); + $mform->setType('categoryid', PARAM_INT); + $this->add_action_buttons(false, get_string('preview', 'tool_uploadcourse')); } } diff --git a/admin/tool/uploadcourse/classes/step2_form.php b/admin/tool/uploadcourse/classes/step2_form.php index 033c2aba273..19d07db0b31 100644 --- a/admin/tool/uploadcourse/classes/step2_form.php +++ b/admin/tool/uploadcourse/classes/step2_form.php @@ -82,7 +82,7 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form { $mform->addElement('header', 'defaultheader', get_string('defaultvalues', 'tool_uploadcourse')); $mform->setExpanded('defaultheader', true); - $displaylist = core_course_category::make_categories_list('moodle/course:create'); + $displaylist = core_course_category::make_categories_list('tool/uploadcourse:use'); $mform->addElement('autocomplete', 'defaults[category]', get_string('coursecategory'), $displaylist); $mform->addRule('defaults[category]', null, 'required', null, 'client'); $mform->addHelpButton('defaults[category]', 'coursecategory'); @@ -223,6 +223,9 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form { $mform->addElement('hidden', 'previewrows'); $mform->setType('previewrows', PARAM_INT); + $mform->addElement('hidden', 'categoryid'); + $mform->setType('categoryid', PARAM_INT); + $this->add_action_buttons(true, get_string('uploadcourses', 'tool_uploadcourse')); // Prepare custom fields data. diff --git a/admin/tool/uploadcourse/db/access.php b/admin/tool/uploadcourse/db/access.php new file mode 100644 index 00000000000..b6f5869812b --- /dev/null +++ b/admin/tool/uploadcourse/db/access.php @@ -0,0 +1,36 @@ +. + +/** + * Capability definitions for tool_uploadcourse. + * + * @package tool_uploadcourse + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'tool/uploadcourse:use' => [ + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSECAT, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], +]; diff --git a/admin/tool/uploadcourse/index.php b/admin/tool/uploadcourse/index.php index 8b26e54196d..c2072df8aad 100644 --- a/admin/tool/uploadcourse/index.php +++ b/admin/tool/uploadcourse/index.php @@ -26,15 +26,31 @@ require(__DIR__ . '/../../../config.php'); require_once($CFG->libdir . '/adminlib.php'); require_once($CFG->libdir . '/csvlib.class.php'); -admin_externalpage_setup('tooluploadcourse'); - $importid = optional_param('importid', '', PARAM_INT); +$categoryid = optional_param('categoryid', 0, PARAM_INT); $previewrows = optional_param('previewrows', 10, PARAM_INT); $returnurl = new moodle_url('/admin/tool/uploadcourse/index.php'); +if ($categoryid) { + // When categoryid is specified, setup the page for this category and check capability in its context. + require_login(null, false); + $category = core_course_category::get($categoryid); + $categoryname = isset($category) ? $category->get_formatted_name() : $SITE->fullname; + $context = context_coursecat::instance($categoryid); + require_capability('tool/uploadcourse:use', $context); + $PAGE->set_context($context); + $PAGE->set_url(new moodle_url('/admin/tool/uploadcourse/index.php', ['categoryid' => $categoryid])); + $PAGE->set_pagelayout('admin'); + $PAGE->set_title("$categoryname: " . get_string('uploadcourses', 'tool_uploadcourse')); + $PAGE->set_heading($categoryname); +} else { + admin_externalpage_setup('tooluploadcourse'); +} + if (empty($importid)) { $mform1 = new tool_uploadcourse_step1_form(); + $mform1->set_data(['categoryid' => $categoryid]); if ($form1data = $mform1->get_data()) { $importid = csv_import_reader::get_new_iid('uploadcourse'); $cir = new csv_import_reader($importid, 'uploadcourse'); @@ -58,7 +74,8 @@ if (empty($importid)) { } // Data to set in the form. -$data = array('importid' => $importid, 'previewrows' => $previewrows); +$categorydefaults = $categoryid ? ['category' => $categoryid] : []; +$data = ['importid' => $importid, 'previewrows' => $previewrows, 'categoryid' => $categoryid, 'defaults' => $categorydefaults]; if (!empty($form1data)) { // Get options from the first form to pass it onto the second. foreach ($form1data->options as $key => $value) { @@ -117,7 +134,7 @@ if ($form2data = $mform2->is_cancelled()) { // Weird but we still need to provide a value, setting the default step1_form one. $options = array('mode' => tool_uploadcourse_processor::MODE_CREATE_NEW); } - $processor = new tool_uploadcourse_processor($cir, $options, array()); + $processor = new tool_uploadcourse_processor($cir, $options, $categorydefaults); echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('uploadcoursespreview', 'tool_uploadcourse')); $processor->preview($previewrows, new tool_uploadcourse_tracker(tool_uploadcourse_tracker::OUTPUT_HTML)); diff --git a/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php b/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php index 7000b2f39a6..0520a1af995 100644 --- a/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php +++ b/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php @@ -67,6 +67,8 @@ $string['coursetemplatename'] = 'Restore from this course after upload'; $string['coursetemplatename_help'] = 'Enter an existing course shortname to use as a template for the creation of all courses.'; $string['coursetorestorefromdoesnotexist'] = 'The course to restore from does not exist'; $string['courseupdated'] = 'Course updated'; +$string['courseuploadnotallowed'] = 'No permission to upload courses in category: {$a}'; +$string['courseuploadupdatenotallowed'] = "Course with this shortname exists and you don't have permission to use upload course tool to update it"; $string['createall'] = 'Create all, increment shortname if needed'; $string['createnew'] = 'Create new courses only, skip existing ones'; $string['createorupdate'] = 'Create new courses, or update existing ones'; @@ -128,6 +130,7 @@ $string['updatemodedoessettonothing'] = 'Update mode does not allow anything to $string['updateonly'] = 'Only update existing courses'; $string['updatewithdataordefaults'] = 'Update with CSV data and defaults'; $string['updatewithdataonly'] = 'Update with CSV data only'; +$string['uploadcourse:use'] = 'Use upload course tool'; $string['uploadcourses'] = 'Upload courses'; $string['uploadcourses_help'] = 'Courses may be uploaded via text file. The format of the file should be as follows: diff --git a/admin/tool/uploadcourse/lib.php b/admin/tool/uploadcourse/lib.php new file mode 100644 index 00000000000..53371c244f5 --- /dev/null +++ b/admin/tool/uploadcourse/lib.php @@ -0,0 +1,44 @@ +. + +/** + * Plugin callbacks for tool_uploadcourse. + * + * @package tool_uploadcourse + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Extends the navigation of the category admin menu with the upload courses link. + * + * @param navigation_node $navigation The navigation node to extend + * @param context $coursecategorycontext The context of the course category + */ +function tool_uploadcourse_extend_navigation_category_settings(navigation_node $navigation, context $coursecategorycontext): void { + if (has_capability('tool/uploadcourse:use', $coursecategorycontext)) { + $title = get_string('uploadcourses', 'tool_uploadcourse'); + $path = new moodle_url('/admin/tool/uploadcourse/index.php', ['categoryid' => $coursecategorycontext->instanceid]); + $settingsnode = navigation_node::create( + $title, + $path, + navigation_node::TYPE_SETTING, + null, + null, + new pix_icon('i/course', '')); + $navigation->add_node($settingsnode); + } +} diff --git a/admin/tool/uploadcourse/settings.php b/admin/tool/uploadcourse/settings.php index 3914e01bd50..62beddca4d5 100644 --- a/admin/tool/uploadcourse/settings.php +++ b/admin/tool/uploadcourse/settings.php @@ -24,7 +24,10 @@ defined('MOODLE_INTERNAL') || die(); -if ($hassiteconfig) { - $ADMIN->add('courses', new admin_externalpage('tooluploadcourse', - get_string('uploadcourses', 'tool_uploadcourse'), "$CFG->wwwroot/$CFG->admin/tool/uploadcourse/index.php")); -} +$ADMIN->add('courses', + new admin_externalpage('tooluploadcourse', + get_string('uploadcourses', 'tool_uploadcourse'), + "$CFG->wwwroot/$CFG->admin/tool/uploadcourse/index.php", + 'tool/uploadcourse:use' + ) +); diff --git a/admin/tool/uploadcourse/tests/behat/create.feature b/admin/tool/uploadcourse/tests/behat/create.feature index 5bde8621cec..e44888eb9b5 100644 --- a/admin/tool/uploadcourse/tests/behat/create.feature +++ b/admin/tool/uploadcourse/tests/behat/create.feature @@ -145,3 +145,39 @@ Feature: An admin can create courses using a CSV file And I am on the "C2" "enrolment methods" page And I should see "manualtest" And I should not see "ltitest" + + @javascript + Scenario: Manager can use upload course tool in course category + Given the following "users" exist: + | username | firstname | lastname | email | + | user1 | User | 1 | user1@example.com | + And the following "categories" exist: + | name | category | idnumber | + | Cat 1 | 0 | CAT1 | + | Cat 2 | 0 | CAT2 | + | Cat 3 | CAT1 | CAT3 | + And the following "role assigns" exist: + | user | role | contextlevel | reference | + | user1 | manager | Category | CAT1 | + When I log in as "user1" + And I am on course index + And I follow "Cat 1" + And I navigate to "Upload courses" in current page administration + And I upload "admin/tool/uploadcourse/tests/fixtures/courses_manager1.csv" file to "File" filemanager + And I click on "Preview" "button" + Then I should see "The course exists and update is not allowed" in the "C1" "table_row" + And I should see "No permission to upload courses in category: Cat 2" in the "C2" "table_row" + And I set the field "Course category" to "Cat 1 / Cat 3" + And I click on "Upload courses" "button" + And I should see "Course created" + And I should see "Courses total: 5" + And I should see "Courses created: 3" + And I should see "Courses errors: 2" + And I am on course index + And I follow "Cat 1" + And I should see "Course 4" + And I follow "Cat 3" + And I should see "Course 5" + # Course 3 did not have category specified in CSV file and it was uploaded to the current category. + And I should see "Course 3" + And I log out diff --git a/admin/tool/uploadcourse/tests/behat/update.feature b/admin/tool/uploadcourse/tests/behat/update.feature index 310108095d4..71773e03d7a 100644 --- a/admin/tool/uploadcourse/tests/behat/update.feature +++ b/admin/tool/uploadcourse/tests/behat/update.feature @@ -87,3 +87,50 @@ Feature: An admin can update courses using a CSV file And I should not see "Manual enrolments" And I should see "Published course" And I should not see "ltitest" + + @javascript + Scenario: Manager can use upload course tool to update courses in course category + Given the following "users" exist: + | username | firstname | lastname | email | + | user1 | User | 1 | user1@example.com | + And the following "categories" exist: + | name | category | idnumber | + | Cat 1 | 0 | CAT1 | + | Cat 2 | 0 | CAT2 | + | Cat 3 | CAT1 | CAT3 | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C01 | CAT1 | + | Course 2 | C02 | CAT2 | + | Course 3 | C03 | CAT3 | + | Course 4 | C04 | CAT3 | + And the following "role assigns" exist: + | user | role | contextlevel | reference | + | user1 | manager | Category | CAT1 | + When I log in as "user1" + And I am on course index + And I follow "Cat 1" + And I navigate to "Upload courses" in current page administration + And I upload "admin/tool/uploadcourse/tests/fixtures/courses_manager2.csv" file to "File" filemanager + And I set the field "Upload mode" to "Only update existing courses" + And I set the field "Update mode" to "Update with CSV data only" + And I click on "Preview" "button" + # Course C01 is in "our" category but can not be moved to Cat 2 because current user can not manage it. + Then I should see "No permission to upload courses in category: Cat 2" in the "C01" "table_row" + # Course C02 can not be updated (no capability in "Cat 2" context). + And I should see "Course with this shortname exists and you don't have permission to use upload course tool to update it" in the "C02" "table_row" + # Course with short name "C05" does not exist. + And I should see "The course does not exist and creating course is not allowed" in the "C05" "table_row" + And I click on "Upload courses" "button" + And I should see "Course updated" + And I should see "Courses total: 5" + And I should see "Courses updated: 2" + And I should see "Courses errors: 3" + And I am on course index + And I follow "Cat 1" + # Course C04 was moved from Cat 3 to Cat 1. + And I should see "Course 4" + And I should see "Course 1" + And I follow "Cat 3" + And I should see "Course 3" + And I log out diff --git a/admin/tool/uploadcourse/tests/course_test.php b/admin/tool/uploadcourse/tests/course_test.php index 959f7bbe3b4..75866511219 100644 --- a/admin/tool/uploadcourse/tests/course_test.php +++ b/admin/tool/uploadcourse/tests/course_test.php @@ -22,14 +22,54 @@ use tool_uploadcourse_course; /** * Course test case. * + * @covers \tool_uploadcourse_course * @package tool_uploadcourse * @copyright 2013 Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late */ class course_test extends \advanced_testcase { - public function test_proceed_without_prepare() { + /** @var \testing_data_generator $datagenerator */ + protected $datagenerator; + /** @var \stdClass $user */ + protected $user; + + /** + * Initialise defaults for each testcase. + * + * @param int $roleid + * @throws \coding_exception + */ + protected function initialise_test(int $roleid = 1): void { + // Reset the database after test. $this->resetAfterTest(true); + + // Get a new data generator. + $this->datagenerator = $this->getDataGenerator(); + + // Create a user. + $this->prepare_user($roleid); + } + + /** + * Create random user and assign default role Manager (roleid = 1). + * + * @param int $roleid + * @throws \coding_exception + */ + protected function prepare_user(int $roleid): void { + // Generate a random user. + $user = $this->datagenerator->create_user(); + + // Log the user in (set the $USER global variable). + $this->setUser($user); + + // Assign a role to the current user. + $this->datagenerator->role_assign($roleid, $user->id, false); + } + + public function test_proceed_without_prepare(): void { + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $data = array(); @@ -39,7 +79,7 @@ class course_test extends \advanced_testcase { } public function test_proceed_when_prepare_failed() { - $this->resetAfterTest(true); + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $data = array(); @@ -50,7 +90,7 @@ class course_test extends \advanced_testcase { } public function test_proceed_when_already_started() { - $this->resetAfterTest(true); + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $data = array('shortname' => 'test', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1); @@ -62,7 +102,7 @@ class course_test extends \advanced_testcase { } public function test_invalid_shortname() { - $this->resetAfterTest(true); + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $data = array('shortname' => '', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1); @@ -88,7 +128,7 @@ class course_test extends \advanced_testcase { } public function test_invalid_fullname_too_long() { - $this->resetAfterTest(); + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; @@ -103,7 +143,7 @@ class course_test extends \advanced_testcase { } public function test_invalid_visibility() { - $this->resetAfterTest(true); + $this->initialise_test(); $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $data = array('shortname' => 'test', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1, 'visible' => 2); @@ -194,9 +234,48 @@ class course_test extends \advanced_testcase { $this->assertArrayHasKey('invaliddownloadcontent', $upload->get_errors()); } + /** + * Test a role's capability to use the upload course tool. + * + * @covers \permissions::check_permission_to_use_uploadcourse_tool + */ + public function test_invalid_role(): void { + global $DB; + + $rolesallowed = ['manager']; + $roles = get_all_roles(); + + $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; + $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; + + foreach ($roles as $role) { + $this->initialise_test($role->id); + + $data = ['shortname' => 'newcourse', 'fullname' => 'New course', 'summary' => 'New', 'category' => 1]; + $co = new tool_uploadcourse_course($mode, $updatemode, $data); + + if (in_array($role->shortname, $rolesallowed)) { + $this->assertTrue($co->prepare()); + $co->proceed(); + $courseid = $DB->get_field('course', 'id', ['shortname' => 'newcourse'], MUST_EXIST); + $this->assertEquals(0, course_get_format($courseid)->get_course()->coursedisplay); + + // Delete course for next assertion. + $importoptions = ['candelete' => true]; + $data = ['shortname' => 'newcourse', 'delete' => 1]; + $co = new tool_uploadcourse_course($mode, $updatemode, $data, [], $importoptions); + $this->assertTrue($co->prepare()); + $co->proceed(); + } else { + $this->assertFalse($co->prepare()); + $this->assertArrayHasKey('courseuploadnotallowed', $co->get_errors()); + } + } + } + public function test_create() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); // Existing course. $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1', 'summary' => 'Yay!')); @@ -245,7 +324,7 @@ class course_test extends \advanced_testcase { public function test_create_with_sections() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING; $defaultnumsections = get_config('moodlecourse', 'numsections'); @@ -275,7 +354,7 @@ class course_test extends \advanced_testcase { public function test_delete() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); @@ -320,7 +399,7 @@ class course_test extends \advanced_testcase { public function test_update() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1')); @@ -398,8 +477,7 @@ class course_test extends \advanced_testcase { public function test_data_saved() { global $DB; - $this->resetAfterTest(true); - $this->setAdminUser(); // To avoid warnings related to 'moodle/course:setforcedlanguage' capability check. + $this->initialise_test(); set_config('downloadcoursecontentallowed', 1); @@ -617,8 +695,7 @@ class course_test extends \advanced_testcase { public function test_default_data_saved() { global $DB; - $this->resetAfterTest(true); - $this->setAdminUser(); + $this->initialise_test(); set_config('downloadcoursecontentallowed', 1); @@ -742,7 +819,7 @@ class course_test extends \advanced_testcase { public function test_rename() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'c1')); $c2 = $this->getDataGenerator()->create_course(array('shortname' => 'c2')); @@ -837,7 +914,7 @@ class course_test extends \advanced_testcase { public function test_restore_course() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $this->setAdminUser(); $c1 = $this->getDataGenerator()->create_course(); @@ -881,7 +958,7 @@ class course_test extends \advanced_testcase { public function test_restore_file() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $this->setAdminUser(); $c1 = $this->getDataGenerator()->create_course(); @@ -933,7 +1010,7 @@ class course_test extends \advanced_testcase { */ public function test_restore_file_settings() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $this->setAdminUser(); // Set admin config setting so that activities are not restored by default. @@ -956,7 +1033,7 @@ class course_test extends \advanced_testcase { } public function test_restore_invalid_file() { - $this->resetAfterTest(); + $this->initialise_test(); // Restore from a non-existing file should not be allowed. $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; @@ -983,7 +1060,7 @@ class course_test extends \advanced_testcase { } public function test_restore_invalid_course() { - $this->resetAfterTest(); + $this->initialise_test(); // Restore from an invalid file should not be allowed. $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; @@ -1000,7 +1077,7 @@ class course_test extends \advanced_testcase { */ public function test_reset() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(); $c1ctx = \context_course::instance($c1->id); @@ -1092,7 +1169,7 @@ class course_test extends \advanced_testcase { public function test_create_bad_category() { global $DB; - $this->resetAfterTest(true); + $this->initialise_test(); // Ensure fails when category cannot be resolved upon creation. $mode = tool_uploadcourse_processor::MODE_CREATE_NEW; @@ -1146,7 +1223,7 @@ class course_test extends \advanced_testcase { } public function test_enrolment_data() { - $this->resetAfterTest(true); + $this->initialise_test(); // We need to set the current user as one with the capability to edit manual enrolment instances in the new course. $this->setAdminUser(); @@ -1425,7 +1502,7 @@ class course_test extends \advanced_testcase { } public function test_idnumber_problems() { - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'sntaken', 'idnumber' => 'taken')); $c2 = $this->getDataGenerator()->create_course(); @@ -1476,7 +1553,7 @@ class course_test extends \advanced_testcase { } public function test_generate_shortname() { - $this->resetAfterTest(true); + $this->initialise_test(); $c1 = $this->getDataGenerator()->create_course(array('shortname' => 'taken')); @@ -1529,7 +1606,7 @@ class course_test extends \advanced_testcase { public function test_mess_with_frontpage() { global $SITE; - $this->resetAfterTest(true); + $this->initialise_test(); // Updating the front page. $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY; diff --git a/admin/tool/uploadcourse/tests/fixtures/courses_manager1.csv b/admin/tool/uploadcourse/tests/fixtures/courses_manager1.csv new file mode 100644 index 00000000000..123264f6422 --- /dev/null +++ b/admin/tool/uploadcourse/tests/fixtures/courses_manager1.csv @@ -0,0 +1,6 @@ +shortname,fullname,summary,idnumber,category_idnumber +C1,Course 1,Summary 1,ID1,CAT2 +C2,Course 2,Summary 2,ID2,CAT2 +C3,Course 3,Summary 3,ID3, +C4,Course 4,Summary 4,ID4,CAT1 +C5,Course 5,Summary 5,ID5,CAT3 diff --git a/admin/tool/uploadcourse/tests/fixtures/courses_manager2.csv b/admin/tool/uploadcourse/tests/fixtures/courses_manager2.csv new file mode 100644 index 00000000000..9f93eede7d1 --- /dev/null +++ b/admin/tool/uploadcourse/tests/fixtures/courses_manager2.csv @@ -0,0 +1,6 @@ +shortname,category_idnumber +C01,CAT2 +C02,CAT2 +C03, +C04,CAT1 +C05,CAT3 diff --git a/admin/tool/uploadcourse/tests/helper_test.php b/admin/tool/uploadcourse/tests/helper_test.php index c94464cfc0e..5f822e1d1a0 100644 --- a/admin/tool/uploadcourse/tests/helper_test.php +++ b/admin/tool/uploadcourse/tests/helper_test.php @@ -381,12 +381,15 @@ class helper_test extends \advanced_testcase { $c1 = $this->getDataGenerator()->create_category(array('idnumber' => 'C1')); $c2 = $this->getDataGenerator()->create_category(array('idnumber' => 'C2')); + $c3 = $this->getDataGenerator()->create_category(['idnumber' => 'C3', 'visible' => false]); // Doubled for cache check. $this->assertEquals($c1->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C1')); $this->assertEquals($c1->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C1')); $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C2')); $this->assertEquals($c2->id, tool_uploadcourse_helper::resolve_category_by_idnumber('C2')); + $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('C3')); + $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('C3')); $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('DoesNotExist')); $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_idnumber('DoesNotExist')); } @@ -436,8 +439,8 @@ class helper_test extends \advanced_testcase { // Hidden parent. $path = array('Cat 2', 'Cat 2.1', 'Cat 2.1.2'); - $this->assertEquals($cat2_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path)); - $this->assertEquals($cat2_1_2->id, tool_uploadcourse_helper::resolve_category_by_path($path)); + $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path)); + $this->assertEmpty(tool_uploadcourse_helper::resolve_category_by_path($path)); // Does not exist. $path = array('No cat 3', 'Cat 1.2'); diff --git a/admin/tool/uploadcourse/tests/processor_test.php b/admin/tool/uploadcourse/tests/processor_test.php index c18218b875e..ff71ca63eb6 100644 --- a/admin/tool/uploadcourse/tests/processor_test.php +++ b/admin/tool/uploadcourse/tests/processor_test.php @@ -35,6 +35,7 @@ class processor_test extends \advanced_testcase { public function test_basic() { global $DB; $this->resetAfterTest(true); + $this->setAdminUser(); $content = array( "shortname,fullname,summary", @@ -134,6 +135,7 @@ class processor_test extends \advanced_testcase { public function test_shortname_template() { global $DB; $this->resetAfterTest(true); + $this->setAdminUser(); $content = array( "shortname,fullname,summary,idnumber", diff --git a/admin/tool/uploadcourse/version.php b/admin/tool/uploadcourse/version.php index dab004f215d..9e26b1061ac 100644 --- a/admin/tool/uploadcourse/version.php +++ b/admin/tool/uploadcourse/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023100900; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2023112800; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2023100400; // Requires this Moodle version. $plugin->component = 'tool_uploadcourse'; // Full name of the plugin (used for diagnostics). diff --git a/enrol/cohort/lib.php b/enrol/cohort/lib.php index 6c2859bb074..685377f913c 100644 --- a/enrol/cohort/lib.php +++ b/enrol/cohort/lib.php @@ -620,15 +620,19 @@ class enrol_cohort_plugin extends enrol_plugin { * @return lang_string|null Error */ public function validate_plugin_data_context(array $enrolmentdata, ?int $courseid = null) : ?lang_string { - $error = null; if (isset($enrolmentdata['customint1'])) { $cohortid = $enrolmentdata['customint1']; $coursecontext = \context_course::instance($courseid); if (!cohort_get_cohort($cohortid, $coursecontext)) { - $error = new lang_string('contextcohortnotallowed', 'cohort', $enrolmentdata['cohortidnumber']); + return new lang_string('contextcohortnotallowed', 'cohort', $enrolmentdata['cohortidnumber']); } } - return $error; + $enrolmentdata += [ + 'customint1' => null, + 'customint2' => null, + 'roleid' => 0, + ]; + return parent::validate_plugin_data_context($enrolmentdata, $courseid); } /** diff --git a/enrol/cohort/tests/lib_test.php b/enrol/cohort/tests/lib_test.php index 238f4ef81bf..47e805d705a 100644 --- a/enrol/cohort/tests/lib_test.php +++ b/enrol/cohort/tests/lib_test.php @@ -246,7 +246,11 @@ class lib_test extends \advanced_testcase { $enrolmentdata = [ 'customint1' => $cohort1->id, 'cohortidnumber' => $cohort1->idnumber, + 'courseid' => $course->id, + 'id' => null, + 'status' => ENROL_INSTANCE_ENABLED, ]; + $enrolmentdata = $cohortplugin->fill_enrol_custom_fields($enrolmentdata, $course->id); $error = $cohortplugin->validate_plugin_data_context($enrolmentdata, $course->id); $this->assertNull($error); } @@ -313,6 +317,7 @@ class lib_test extends \advanced_testcase { */ public function test_validate_enrol_plugin_data() { $this->resetAfterTest(); + $this->setAdminUser(); $cat = $this->getDataGenerator()->create_category(); $cat1 = $this->getDataGenerator()->create_category(['parent' => $cat->id]); diff --git a/enrol/guest/lib.php b/enrol/guest/lib.php index a9b7d9b6eb8..ea76a3d2aab 100644 --- a/enrol/guest/lib.php +++ b/enrol/guest/lib.php @@ -519,6 +519,16 @@ class enrol_guest_plugin extends enrol_plugin { return $instance; } + /** + * Fill custom fields data for a given enrolment plugin. + * + * @param array $enrolmentdata enrolment data. + * @param int $courseid Course ID. + * @return array Updated enrolment data with custom fields info. + */ + public function fill_enrol_custom_fields(array $enrolmentdata, int $courseid): array { + return $enrolmentdata + ['password' => '']; + } } /** diff --git a/enrol/manual/lib.php b/enrol/manual/lib.php index 9299f4b86b8..bbbcb1874e0 100644 --- a/enrol/manual/lib.php +++ b/enrol/manual/lib.php @@ -675,6 +675,20 @@ class enrol_manual_plugin extends enrol_plugin { } return $instance; } + + /** + * Fill custom fields data for a given enrolment plugin. + * + * @param array $enrolmentdata enrolment data. + * @param int $courseid Course ID. + * @return array Updated enrolment data with custom fields info. + */ + public function fill_enrol_custom_fields(array $enrolmentdata, int $courseid): array { + return $enrolmentdata + [ + 'expirynotify' => 0, + 'expirythreshold' => 0, + ]; + } } /** diff --git a/enrol/meta/lib.php b/enrol/meta/lib.php index cca79a9eea2..cb0d7ac902c 100644 --- a/enrol/meta/lib.php +++ b/enrol/meta/lib.php @@ -468,7 +468,10 @@ class enrol_meta_plugin extends enrol_plugin { } else if (isset($enrolmentdata['groupname'])) { $enrolmentdata['customint2'] = groups_get_group_by_name($courseid, $enrolmentdata['groupname']); } - return $enrolmentdata; + return $enrolmentdata + [ + 'customint1' => null, + 'customint2' => null, + ]; } /** diff --git a/enrol/meta/tests/plugin_test.php b/enrol/meta/tests/plugin_test.php index 02a4b613f7f..070f806b662 100644 --- a/enrol/meta/tests/plugin_test.php +++ b/enrol/meta/tests/plugin_test.php @@ -1108,13 +1108,13 @@ class plugin_test extends \advanced_testcase { $enrolmentdata = $metaplugin->fill_enrol_custom_fields($enrolmentdata, $course1->id); $this->assertArrayHasKey('customint1', $enrolmentdata); $this->assertEquals($course2->id, $enrolmentdata['customint1']); - $this->assertArrayNotHasKey('customint2', $enrolmentdata); + $this->assertNull($enrolmentdata['customint2']); $enrolmentdata['metacoursename'] = 'notexist'; $enrolmentdata = $metaplugin->fill_enrol_custom_fields($enrolmentdata, $course1->id); $this->assertArrayHasKey('customint1', $enrolmentdata); $this->assertFalse($enrolmentdata['customint1']); - $this->assertArrayNotHasKey('customint2', $enrolmentdata); + $this->assertNull($enrolmentdata['customint2']); $enrolmentdata['metacoursename'] = $course2->shortname; diff --git a/enrol/self/lib.php b/enrol/self/lib.php index ab838958b3c..b3bb22c1c4d 100644 --- a/enrol/self/lib.php +++ b/enrol/self/lib.php @@ -1110,6 +1110,16 @@ class enrol_self_plugin extends enrol_plugin { return $instance; } + /** + * Fill custom fields data for a given enrolment plugin. + * + * @param array $enrolmentdata enrolment data. + * @param int $courseid Course ID. + * @return array Updated enrolment data with custom fields info. + */ + public function fill_enrol_custom_fields(array $enrolmentdata, int $courseid): array { + return $enrolmentdata + ['password' => '']; + } } /** diff --git a/lib/enrollib.php b/lib/enrollib.php index e77e246ca48..d27174a7bbd 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -2707,6 +2707,9 @@ abstract class enrol_plugin { /** * Check if enrolment plugin is supported in csv course upload. * + * If supported, plugins are also encouraged to override methods: + * {@see self::fill_enrol_custom_fields()}, {@see self::validate_plugin_data_context()} + * * @return bool */ public function is_csv_upload_supported(): bool { @@ -3485,7 +3488,10 @@ abstract class enrol_plugin { /** * Fill custom fields data for a given enrolment plugin. * - * @param array $enrolmentdata enrolment data. + * For example: resolve linked entities from the idnumbers (cohort, role, group, etc.) + * Also fill the default values that are not specified. + * + * @param array $enrolmentdata enrolment data received in CSV file in tool_uploadcourse * @param int $courseid Course ID. * @return array Updated enrolment data with custom fields info. */ @@ -3514,11 +3520,31 @@ abstract class enrol_plugin { /** * Check if plugin custom data is allowed in relevant context. * + * This is called from the tool_uploadcourse if the plugin supports instance creation in + * upload course ({@see self::is_csv_upload_supported()}) + * + * The fallback is to call the edit_instance_validation() but it will be better if the plugins + * implement this method to return better error messages. + * * @param array $enrolmentdata enrolment data to validate. * @param int|null $courseid Course ID. * @return lang_string|null Error */ public function validate_plugin_data_context(array $enrolmentdata, ?int $courseid = null) : ?lang_string { + if ($courseid) { + $enrolmentdata += ['courseid' => $courseid, 'id' => 0, 'status' => ENROL_INSTANCE_ENABLED]; + $instance = (object)[ + 'id' => null, + 'courseid' => $courseid, + 'status' => $enrolmentdata['status'], + 'type' => $this->get_name(), + ]; + $formerrors = $this->edit_instance_validation($enrolmentdata, [], $instance, context_course::instance($courseid)); + if (!empty($formerrors)) { + $errors = array_map(fn($key) => "{$key}: {$formerrors[$key]}", array_keys($formerrors)); + return new lang_string('errorcannotcreateorupdateenrolment', 'tool_uploadcourse', $errors); + } + } return null; }