Skip to content

Commit

Permalink
Merge branch 'MDL-66135' of https://github.com/paulholden/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
stronk7 committed Apr 30, 2020
2 parents 555ec89 + d62fc08 commit fcad901
Show file tree
Hide file tree
Showing 18 changed files with 629 additions and 17 deletions.
36 changes: 33 additions & 3 deletions admin/tool/uploadcourse/classes/course.php
Expand Up @@ -284,6 +284,15 @@ public function get_errors() {
return $this->errors;
}

/**
* Return array of valid fields for default values
*
* @return array
*/
protected function get_valid_fields() {
return array_merge(self::$validfields, \tool_uploadcourse_helper::get_custom_course_field_names());
}

/**
* Assemble the course data based on defaults.
*
Expand All @@ -293,7 +302,7 @@ public function get_errors() {
* @return array
*/
protected function get_final_create_data($data) {
foreach (self::$validfields as $field) {
foreach ($this->get_valid_fields() as $field) {
if (!isset($data[$field]) && isset($this->defaults[$field])) {
$data[$field] = $this->defaults[$field];
}
Expand All @@ -316,9 +325,9 @@ protected function get_final_update_data($data, $usedefaults = false, $missingon
global $DB;
$newdata = array();
$existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
foreach (self::$validfields as $field) {
foreach ($this->get_valid_fields() as $field) {
if ($missingonly) {
if (!is_null($existingdata->$field) and $existingdata->$field !== '') {
if (isset($existingdata->$field) and $existingdata->$field !== '') {
continue;
}
}
Expand Down Expand Up @@ -699,6 +708,27 @@ public function prepare() {
$coursedata[$rolekey] = $rolename;
}

// Custom fields. If the course already exists and mode isn't set to force creation, we can use its context.
if ($exists && $mode !== tool_uploadcourse_processor::MODE_CREATE_ALL) {
$context = context_course::instance($coursedata['id']);
} else {
// The category ID is taken from the defaults if it exists, otherwise from course data.
$context = context_coursecat::instance($this->defaults['category'] ?? $coursedata['category']);
}
$customfielddata = tool_uploadcourse_helper::get_custom_course_field_data($this->rawdata, $this->defaults, $context,
$errors);
if (!empty($errors)) {
foreach ($errors as $key => $message) {
$this->error($key, $message);
}

return false;
}

foreach ($customfielddata as $name => $value) {
$coursedata[$name] = $value;
}

// Some validation.
if (!empty($coursedata['format']) && !in_array($coursedata['format'], tool_uploadcourse_helper::get_course_formats())) {
$this->error('invalidcourseformat', new lang_string('invalidcourseformat', 'tool_uploadcourse'));
Expand Down
100 changes: 98 additions & 2 deletions admin/tool/uploadcourse/classes/helper.php
Expand Up @@ -337,6 +337,103 @@ public static function get_role_names($data, &$errors = array()) {
return $rolenames;
}

/**
* Return array of all custom course fields indexed by their shortname
*
* @return \core_customfield\field_controller[]
*/
public static function get_custom_course_fields(): array {
$result = [];

$fields = \core_course\customfield\course_handler::create()->get_fields();
foreach ($fields as $field) {
$result[$field->get('shortname')] = $field;
}

return $result;
}

/**
* Return array of custom field element names
*
* @return string[]
*/
public static function get_custom_course_field_names(): array {
$result = [];

$fields = self::get_custom_course_fields();
foreach ($fields as $field) {
$controller = \core_customfield\data_controller::create(0, null, $field);
$result[] = $controller->get_form_element_name();
}

return $result;
}

/**
* Return any elements from passed $data whose key matches one of the custom course fields defined for the site
*
* @param array $data
* @param array $defaults
* @param context $context
* @param array $errors Will be populated with any errors
* @return array
*/
public static function get_custom_course_field_data(array $data, array $defaults, context $context,
array &$errors = []): array {

$fields = self::get_custom_course_fields();
$result = [];

$canchangelockedfields = guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context);

foreach ($data as $name => $originalvalue) {
if (preg_match('/^customfield_(?<name>.*)?$/', $name, $matches)
&& isset($fields[$matches['name']])) {

$fieldname = $matches['name'];
$field = $fields[$fieldname];

// Skip field if it's locked and user doesn't have capability to change locked fields.
if ($field->get_configdata_property('locked') && !$canchangelockedfields) {
continue;
}

// Create field data controller.
$controller = \core_customfield\data_controller::create(0, null, $field);
$controller->set('id', 1);

$defaultvalue = $defaults["customfield_{$fieldname}"] ?? $controller->get_default_value();
$value = (empty($originalvalue) ? $defaultvalue : $field->parse_value($originalvalue));

// If we initially had a value, but now don't, then reset it to the default.
if (!empty($originalvalue) && empty($value)) {
$value = $defaultvalue;
}

// Validate data with controller.
$fieldformdata = [$controller->get_form_element_name() => $value];
$validationerrors = $controller->instance_form_validation($fieldformdata, []);
if (count($validationerrors) > 0) {
$errors['customfieldinvalid'] = new lang_string('customfieldinvalid', 'tool_uploadcourse',
$field->get_formatted_name());

continue;
}

$controller->set($controller->datafield(), $value);

// Pass an empty object to the data controller, which will transform it to a correct name/value pair.
$instance = new stdClass();
$controller->instance_form_before_set_data($instance);

$result = array_merge($result, (array) $instance);
}
}

return $result;
}

/**
* Helper to increment an ID number.
*
Expand Down Expand Up @@ -493,5 +590,4 @@ public static function resolve_category_by_path(array $path) {
}
return $id;
}

}
}
14 changes: 14 additions & 0 deletions admin/tool/uploadcourse/classes/step2_form.php
Expand Up @@ -173,6 +173,10 @@ public function definition () {
$mform->addHelpButton('defaults[enablecompletion]', 'enablecompletion', 'completion');
}

// Add custom fields to the form.
$handler = \core_course\customfield\course_handler::create();
$handler->instance_form_definition($mform, 0, 'defaultvaluescustomfieldcategory', 'tool_uploadcourse');

// Hidden fields.
$mform->addElement('hidden', 'importid');
$mform->setType('importid', PARAM_INT);
Expand All @@ -182,6 +186,10 @@ public function definition () {

$this->add_action_buttons(true, get_string('uploadcourses', 'tool_uploadcourse'));

// Prepare custom fields data.
$data = (object) $data;
$handler->instance_form_before_set_data($data);

$this->set_data($data);
}

Expand Down Expand Up @@ -219,6 +227,9 @@ public function definition_after_data() {
$enddate = $format->get_default_course_enddate($mform, array('startdate' => 'defaults[startdate]'));
$mform->setDefault('defaults[enddate]', $enddate);
}

// Tweak the form with values provided by custom fields in use.
\core_course\customfield\course_handler::create()->instance_form_definition_after_data($mform);
}

/**
Expand All @@ -237,6 +248,9 @@ public function validation($data, $files) {
$errors['defaults[enddate]'] = get_string($errorcode, 'error');
}

// Custom fields validation.
array_merge($errors, \core_course\customfield\course_handler::create()->instance_form_validation($data, $files));

return $errors;
}
}
6 changes: 6 additions & 0 deletions admin/tool/uploadcourse/index.php
Expand Up @@ -78,6 +78,12 @@
$options = (array) $form2data->options;
$defaults = (array) $form2data->defaults;

// Custom field defaults.
$customfields = tool_uploadcourse_helper::get_custom_course_field_names();
foreach ($customfields as $customfield) {
$defaults[$customfield] = $form2data->{$customfield};
}

// Restorefile deserves its own logic because formslib does not really appreciate
// when the name of a filepicker is an array...
$options['restorefile'] = '';
Expand Down
2 changes: 2 additions & 0 deletions admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
Expand Up @@ -75,6 +75,7 @@
$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct: {$a}';
$string['csvline'] = 'Line';
$string['defaultvalues'] = 'Default course values';
$string['defaultvaluescustomfieldcategory'] = 'Default values for \'{$a}\'';
$string['encoding'] = 'Encoding';
$string['encoding_help'] = 'Encoding of the CSV file.';
$string['errorwhilerestoringcourse'] = 'Error while restoring the course';
Expand Down Expand Up @@ -102,6 +103,7 @@
$string['nochanges'] = 'No changes';
$string['pluginname'] = 'Course upload';
$string['preview'] = 'Preview';
$string['customfieldinvalid'] = 'Custom field \'{$a}\' is empty or contains invalid data';
$string['reset'] = 'Reset course after upload';
$string['reset_help'] = 'Whether to reset the course after creating/updating it.';
$string['result'] = 'Result';
Expand Down
63 changes: 63 additions & 0 deletions admin/tool/uploadcourse/tests/behat/create.feature
Expand Up @@ -42,3 +42,66 @@ Feature: An admin can create courses using a CSV file
And I should see "Course 1"
And I should see "Course 2"
And I should see "Course 3"

@javascript
Scenario: Creation of new courses with custom fields
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Other | core_course | course | 0 |
And the following "custom fields" exist:
| name | category | type | shortname | configdata |
| Field 1 | Other | checkbox | checkbox | |
| Field 2 | Other | date | date | |
| Field 3 | Other | select | select | {"options":"a\nb\nc"} |
| Field 4 | Other | text | text | |
| Field 5 | Other | textarea | textarea | |
When I upload "admin/tool/uploadcourse/tests/fixtures/courses_custom_fields.csv" file to "File" filemanager
And I set the field "Upload mode" to "Create new courses only, skip existing ones"
And I click on "Preview" "button"
And I click on "Upload courses" "button"
Then I should see "Course created"
And I should see "Courses created: 1"
And I am on site homepage
And I should see "Course fields 1"
And I should see "Field 1: Yes"
And I should see "Field 2: Tuesday, 1 October 2019, 2:00"
And I should see "Field 3: b"
And I should see "Field 4: Hello"
And I should see "Field 5: Goodbye"

@javascript
Scenario: Creation of new courses with custom fields using defaults
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Other | core_course | course | 0 |
And the following "custom fields" exist:
| name | category | type | shortname | configdata |
| Field 1 | Other | checkbox | checkbox | {"checkbydefault":1} |
| Field 2 | Other | date | date | {"includetime":0} |
| Field 3 | Other | select | select | {"options":"a\nb\nc","defaultvalue":"b"} |
| Field 4 | Other | text | text | {"defaultvalue":"Hello"} |
| Field 5 | Other | textarea | textarea | {"defaultvalue":"Some text","defaultvalueformat":1} |
When I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filemanager
And I set the field "Upload mode" to "Create all, increment shortname if needed"
And I click on "Preview" "button"
And I expand all fieldsets
And the field "Field 1" matches value "1"
And the field "Field 3" matches value "b"
And the field "Field 4" matches value "Hello"
And the field "Field 5" matches value "Some text"
# We have to enable the date field manually.
And I set the following fields to these values:
| customfield_date[enabled] | 1 |
| customfield_date[day] | 1 |
| customfield_date[month] | June |
| customfield_date[year] | 2020 |
And I click on "Upload courses" "button"
Then I should see "Course created"
And I should see "Courses created: 3"
And I am on site homepage
And I should see "Course 1"
And I should see "Field 1: Yes"
And I should see "Field 2: 1 June 2020"
And I should see "Field 3: b"
And I should see "Field 4: Hello"
And I should see "Field 5: Some text"
31 changes: 30 additions & 1 deletion admin/tool/uploadcourse/tests/behat/update.feature
Expand Up @@ -7,7 +7,8 @@ Feature: An admin can update courses using a CSV file
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Some random name | C1 | 0 |
| Some random name | C1 | 0 |
| Another course | CF1 | 0 |
And I log in as "admin"
And I navigate to "Courses > Upload courses" in site administration

Expand All @@ -28,3 +29,31 @@ Feature: An admin can update courses using a CSV file
And I should see "Course 1"
And I should not see "Course 2"
And I should not see "Course 3"

@javascript
Scenario: Updating a course with custom fields
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Other | core_course | course | 0 |
And the following "custom fields" exist:
| name | category | type | shortname | configdata |
| Field 1 | Other | checkbox | checkbox | |
| Field 2 | Other | date | date | |
| Field 3 | Other | select | select | {"options":"a\nb\nc"} |
| Field 4 | Other | text | text | |
| Field 5 | Other | textarea | textarea | |
When I upload "admin/tool/uploadcourse/tests/fixtures/courses_custom_fields.csv" file to "File" filemanager
And I set the following fields to these values:
| Upload mode | Only update existing courses |
| Update mode | Update with CSV data only |
And I click on "Preview" "button"
And I click on "Upload courses" "button"
Then I should see "Course updated"
And I should see "Courses updated: 1"
And I am on site homepage
And I should see "Course fields 1"
And I should see "Field 1: Yes"
And I should see "Field 2: Tuesday, 1 October 2019, 2:00"
And I should see "Field 3: b"
And I should see "Field 4: Hello"
And I should see "Field 5: Goodbye"

0 comments on commit fcad901

Please sign in to comment.