diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index d17b0da0ddb..5f5820a1221 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -2806,12 +2806,13 @@ class backup_completion_defaults_structure_step extends backup_structure_step { $cc = new backup_nested_element('course_completion_defaults'); $defaults = new backup_nested_element('course_completion_default', array('id'), array( - 'modulename', 'completion', 'completionview', 'completionusegrade', 'completionexpected', 'customrules' + 'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade', + 'completionexpected', 'customrules' )); // Use module name instead of module id so we can insert into another site later. $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade, - d.completionexpected, d.customrules + d.completionpassgrade, d.completionexpected, d.customrules FROM {course_completion_defaults} d join {modules} m on d.module = m.id WHERE d.course = ?"; $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID)); diff --git a/completion/classes/bulkedit_form.php b/completion/classes/bulkedit_form.php index a67cce75f69..0e6159aee3c 100644 --- a/completion/classes/bulkedit_form.php +++ b/completion/classes/bulkedit_form.php @@ -137,7 +137,8 @@ class core_completion_bulkedit_form extends core_completion_edit_base_form { // Completion: Don't let them choose automatic completion without turning // on some conditions. if (array_key_exists('completion', $data) && - $data['completion'] == COMPLETION_TRACKING_AUTOMATIC && !empty($data['completionusegrade'])) { + $data['completion'] == COMPLETION_TRACKING_AUTOMATIC && + (!empty($data['completionusegrade']) || !empty($data['completionpassgrade']))) { require_once($CFG->libdir.'/gradelib.php'); $moduleswithoutgradeitem = []; foreach ($this->cms as $cm) { diff --git a/completion/classes/edit_base_form.php b/completion/classes/edit_base_form.php index 505c7891689..903a51ab00a 100644 --- a/completion/classes/edit_base_form.php +++ b/completion/classes/edit_base_form.php @@ -199,10 +199,19 @@ abstract class core_completion_edit_base_form extends moodleform { // Automatic completion once it's graded. if ($this->support_grades()) { - $mform->addElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'), + $group = []; + $group[] = $mform->createElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'), get_string('completionusegrade_desc', 'completion')); - $mform->disabledIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); - $mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion'); + $mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion', '', true); + + $group[] = $mform->createElement('advcheckbox', 'completionpassgrade', get_string('completionpassgrade', 'completion'), + get_string('completionpassgrade_desc', 'completion')); + $mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion', '', true); + $mform->disabledIf('completionpassgrade', 'completionusegrade', 'notchecked'); + + $mform->addGroup($group, 'completionpassgroup', get_string('completionpassgrade', 'completion'), '   ', false); + $mform->disabledIf('completionpassgroup', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); + $autocompletionpossible = true; } @@ -247,7 +256,7 @@ abstract class core_completion_edit_base_form extends moodleform { // on some conditions. if (array_key_exists('completion', $data) && $data['completion'] == COMPLETION_TRACKING_AUTOMATIC) { - if (empty($data['completionview']) && empty($data['completionusegrade']) && + if (empty($data['completionview']) && empty($data['completionusegrade']) && empty($data['completionpassgrade']) && !$this->completion_rule_enabled($data)) { $errors['completion'] = get_string('badautocompletion', 'completion'); } diff --git a/completion/classes/manager.php b/completion/classes/manager.php index 2f66775e7f0..146ae16f565 100644 --- a/completion/classes/manager.php +++ b/completion/classes/manager.php @@ -176,7 +176,13 @@ class manager { } if ($moduledata instanceof cm_info && !is_null($moduledata->completiongradeitemnumber) || ($moduledata instanceof stdClass && !empty($moduledata->completionusegrade))) { - $activeruledescriptions[] = get_string('completionusegrade_desc', 'completion'); + + $description = 'completionusegrade_desc'; + if (!empty($moduledata->completionpassgrade)) { + $description = 'completionpassgrade_desc'; + } + + $activeruledescriptions[] = get_string($description, 'completion'); } // Now, ask the module to provide descriptions for its custom conditional completion rules. @@ -351,8 +357,11 @@ class manager { protected function apply_completion_cm(\cm_info $cm, $data, $updateinstance) { global $DB; - $defaults = ['completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED, - 'completionexpected' => 0, 'completiongradeitemnumber' => null]; + $defaults = [ + 'completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED, + 'completionexpected' => 0, 'completiongradeitemnumber' => null, + 'completionpassgrade' => 0 + ]; $data += ['completion' => $cm->completion, 'completionexpected' => $cm->completionexpected, diff --git a/completion/criteria/completion_criteria_activity.php b/completion/criteria/completion_criteria_activity.php index 118db635b31..3b80836636a 100644 --- a/completion/criteria/completion_criteria_activity.php +++ b/completion/criteria/completion_criteria_activity.php @@ -240,6 +240,10 @@ class completion_criteria_activity extends completion_criteria { if (!is_null($cm->completiongradeitemnumber)) { $details['requirement'][] = get_string('achievinggrade', 'completion'); } + + if ($cm->completionpassgrade) { + $details['requirement'][] = get_string('achievingpassinggrade', 'completion'); + } } $details['requirement'] = implode(', ', $details['requirement']); diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index c99ca7122e3..4ee1b1a2ee7 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -370,6 +370,9 @@ abstract class moodleform_mod extends moodleform { if ($mform->elementExists('completionusegrade')) { $mform->freeze('completionusegrade'); } + if ($mform->elementExists('completionpassgrade')) { + $mform->freeze('completionpassgrade'); + } if ($mform->elementExists('completiongradeitemnumber')) { $mform->freeze('completiongradeitemnumber'); } @@ -468,6 +471,28 @@ abstract class moodleform_mod extends moodleform { ); } } + + if (isset($data['completionpassgrade']) && $data['completionpassgrade']) { + // We need to check whether there's a valid gradepass value. + // This can either be in completiongradeitemnumber when there are multiple options OR, + // The first grade item if completionusegrade is specified. + $validategradepass = false; + if (isset($data['completiongradeitemnumber'])) { + if ($data['completiongradeitemnumber'] == (string)$itemnumber) { + $validategradepass = true; + } + } else if (isset($data['completionusegrade']) && $data['completionusegrade']) { + $validategradepass = true; + } + + // Confirm gradepass is a valid non-zero value. + if ($validategradepass && (!isset($data[$gradepassfieldname]) || grade_floatval($data[$gradepassfieldname]) == 0)) { + $errors['completionpassgrade'] = get_string( + 'activitygradetopassnotset', + 'completion' + ); + } + } } // Completion: Don't let them choose automatic completion without turning @@ -482,7 +507,7 @@ abstract class moodleform_mod extends moodleform { $rulesenabled = !empty($data['completionview']); // Use grade to complete (only one grade item). - $rulesenabled = $rulesenabled || !empty($data['completionusegrade']); + $rulesenabled = $rulesenabled || !empty($data['completionusegrade']) || !empty($data['completionpassgrade']); // Use grade to complete (specific grade item). if (!$rulesenabled && isset($data['completiongradeitemnumber'])) { @@ -724,16 +749,28 @@ abstract class moodleform_mod extends moodleform { $mform->hideIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); $mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion'); + // Complete if the user has reached the pass grade. + $mform->addElement( + 'checkbox', + 'completionpassgrade', null, + get_string('completionpassgrade_desc', 'completion') + ); + $mform->hideIf('completionpassgrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); + $mform->disabledIf('completionpassgrade', 'completionusegrade', 'notchecked'); + $mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion'); + // The disabledIf logic differs between ratings and other grade items due to different field types. if ($this->_features->rating) { // If using the rating system, there is no grade unless ratings are enabled. $mform->disabledIf('completionusegrade', 'assessed', 'eq', 0); + $mform->disabledIf('completionpassgrade', 'assessed', 'eq', 0); } else { // All other field types use the '$gradefieldname' field's modgrade_type. $itemnumbers = array_keys($itemnames); $itemnumber = array_shift($itemnumbers); $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade'); $mform->disabledIf('completionusegrade', "{$gradefieldname}[modgrade_type]", 'eq', 'none'); + $mform->disabledIf('completionpassgrade', "{$gradefieldname}[modgrade_type]", 'eq', 'none'); } } else if (count($itemnames) > 1) { // There are multiple grade items in this activity. @@ -752,6 +789,16 @@ abstract class moodleform_mod extends moodleform { $options ); $mform->hideIf('completiongradeitemnumber', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); + + // Complete if the user has reached the pass grade. + $mform->addElement( + 'checkbox', + 'completionpassgrade', null, + get_string('completionpassgrade_desc', 'completion') + ); + $mform->hideIf('completionpassgrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC); + $mform->disabledIf('completionpassgrade', 'completiongradeitemnumber', 'eq', ''); + $mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion'); } } diff --git a/lang/en/completion.php b/lang/en/completion.php index 9c60666c087..1f4ec18664e 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -25,6 +25,7 @@ */ $string['achievinggrade'] = 'Achieving grade'; +$string['achievingpassinggrade'] = 'Achieving passing grade'; $string['activities'] = 'Activities'; $string['activitieslabel'] = 'Activities / resources'; $string['activityaggregation'] = 'Condition requires'; @@ -35,6 +36,7 @@ $string['activitiescompletednote'] = 'Note: Activity completion must be set for $string['activitycompletion'] = 'Activity completion'; $string['activitycompletionupdated'] = 'Changes saved'; $string['activitygradenotrequired'] = 'Grade not required'; +$string['activitygradetopassnotset'] = 'This activity does not have a valid grade to pass set. It may be set in the Grade section of the activity settings.'; $string['affectedactivities'] = 'The changes will affect the following {$a} activities or resources:'; $string['aggregationmethod'] = 'Aggregation method'; $string['all'] = 'All'; @@ -106,6 +108,9 @@ $string['completionondate'] = 'Date'; $string['completionondatevalue'] = 'Date when course will be marked as complete'; $string['completionduration'] = 'Enrolment'; $string['completionsettingslocked'] = 'Completion settings locked'; +$string['completionpassgrade'] = 'Require passing grade'; +$string['completionpassgrade_desc'] = 'Student must receive a passing grade to complete this activity'; +$string['completionpassgrade_help'] = 'If enabled, the activity is considered complete when a student receives a passing grade.'; $string['completionusegrade'] = 'Require grade'; $string['completionusegrade_desc'] = 'Student must receive a grade to complete this activity'; $string['completionusegrade_help'] = 'If enabled, the activity is considered complete when a student receives a grade. If a pass grade for the activity is set, then pass and fail icons are displayed in the activity completion report.'; @@ -141,6 +146,7 @@ $string['deletecompletiondata'] = 'Delete completion data'; $string['dependencies'] = 'Dependencies'; $string['dependenciescompleted'] = 'Completion of other courses'; $string['detail_desc:receivegrade'] = 'Receive a grade'; +$string['detail_desc:receivepassgrade'] = 'Receive a passing grade'; $string['detail_desc:view'] = 'View'; $string['hiddenrules'] = 'Some settings specific to {$a} have been hidden. To view unselect other activities'; $string['editcoursecompletionsettings'] = 'Edit course completion settings'; diff --git a/lib/db/install.xml b/lib/db/install.xml index eeb1af648de..a35a832e9e7 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -304,6 +304,7 @@ + @@ -3866,6 +3867,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index f6919eb54d5..5f509472de4 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2862,5 +2862,21 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2021092400.03); } + if ($oldversion < 2021100300.01) { + $table = new xmldb_table('course_completion_defaults'); + + // Adding fields to table course_completion_defaults. + $field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null, + XMLDB_NOTNULL, null, '0', 'completionusegrade'); + + // Conditionally launch add field for course_completion_defaults. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2021100300.01); + } + return true; } diff --git a/mod/workshop/mod_form.php b/mod/workshop/mod_form.php index db9a52eaaf6..2860ecf1002 100644 --- a/mod/workshop/mod_form.php +++ b/mod/workshop/mod_form.php @@ -32,6 +32,7 @@ require_once($CFG->dirroot . '/course/moodleform_mod.php'); require_once(__DIR__ . '/locallib.php'); require_once($CFG->libdir . '/filelib.php'); +use core_grades\component_gradeitems; /** * Module settings form for Workshop instances */ @@ -462,6 +463,23 @@ class mod_workshop_mod_form extends moodleform_mod { } } + // We need to do a custom completion validation because workshop grade items identifiers divert from standard. + // Refer to validation defined in moodleform_mod.php. + if (isset($data['completionpassgrade']) && $data['completionpassgrade'] && + isset($data['completiongradeitemnumber'])) { + $itemnames = component_gradeitems::get_itemname_mapping_for_component('mod_workshop'); + $gradepassfield = $itemnames[(int) $data['completiongradeitemnumber']] . 'gradepass'; + if (!isset($data[$gradepassfield]) || grade_floatval($data[$gradepassfield]) == 0) { + $errors['completionpassgrade'] = get_string( + 'activitygradetopassnotset', + 'completion' + ); + } else { + // We have validated grade pass. Unset any errors. + unset($errors['completionpassgrade']); + } + } + if (!$data['submissiontypetextavailable'] && !$data['submissiontypefileavailable']) { // One submission type must be available. $errors['submissiontypes'] = get_string('nosubmissiontype', 'workshop'); diff --git a/version.php b/version.php index e59cc0b730b..6b98bb9c1af 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021100300.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021100300.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev (Build: 20211003)'; // Human-friendly version name