MDL-71196 completion: final removal of get_completion_state support.

This commit is contained in:
Paul Holden 2023-05-05 10:32:01 +01:00
parent a1d5d1b2f7
commit 334b6c75a4
No known key found for this signature in database
GPG Key ID: A81A96D6045F6164
15 changed files with 32 additions and 854 deletions

View File

@ -152,17 +152,6 @@ class cm_completion_details {
$details = $this->sort_completion_details($details);
}
} else {
if (function_exists($this->cminfo->modname . '_get_completion_state')) {
// If the plugin does not have the custom completion implementation but implements the get_completion_state() callback,
// fallback to displaying the overall completion state of the activity.
$details = [
'plugincompletionstate' => (object)[
'status' => $this->get_overall_completion(),
'description' => get_string('completeactivity', 'completion')
]
];
}
}
return $details;

View File

@ -5,6 +5,8 @@ information provided here is intended especially for developers.
* A trait class, core_completion/form/form_trait has been added to reuse code for adding and validation completion settings to any
form.
* New method is_manual() has been added to `core_completion/cm_completion_details`
* Support for deprecated `[modname]_get_completion_state` callbacks has been removed, custom completion rules must be implemented
by appropriate `mod_[modname]\completion\custom_completion` class instead
=== 4.0 ===
* New method mark_course_completions_activity_criteria() has been added to mark course completions instantly. It is

View File

@ -720,19 +720,6 @@ class completion_info {
/** @var activity_custom_completion $cmcompletion */
$cmcompletion = new $cmcompletionclass($cminfo, $userid, $completionstate);
$response = $cmcompletion->get_overall_completion_state() != COMPLETION_INCOMPLETE;
} else {
// Fallback to the get_completion_state callback.
$cmcompletionclass = "mod_{$cminfo->modname}\\completion\\custom_completion";
$function = $cminfo->modname . '_get_completion_state';
if (!function_exists($function)) {
$this->internal_systemerror("Module {$cminfo->modname} claims to support FEATURE_COMPLETION_HAS_RULES " .
"but does not implement the custom completion class $cmcompletionclass which extends " .
"\core_completion\activity_custom_completion.");
}
debugging("*_get_completion_state() callback functions such as $function have been deprecated and should no " .
"longer be used. Please implement the custom completion class $cmcompletionclass which extends " .
"\core_completion\activity_custom_completion.", DEBUG_DEVELOPER);
$response = $function($this->course, $cm, $userid, COMPLETION_AND, $completionstate);
}
if (!$response) {

View File

@ -23,39 +23,11 @@
*/
/**
* Obtains the automatic completion state for this module based on any conditions
* in assign settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_assign\completion\custom_completion
* @param object $course Course
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function assign_get_completion_state($course, $cm, $userid, $type) {
global $CFG;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
require_once($CFG->dirroot . '/mod/assign/locallib.php');
$assign = new assign(null, $cm, $course);
// If completion option is enabled, evaluate it and return true/false.
if ($assign->get_instance()->completionsubmit) {
if ($assign->get_instance()->teamsubmission) {
$submission = $assign->get_group_submission($userid, 0, false);
} else {
$submission = $assign->get_user_submission($userid, false);
}
return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
} else {
// Completion option is not enabled so just return $type.
return $type;
}
function assign_get_completion_state() {
$completionclass = \mod_assign\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}
/**

View File

@ -176,38 +176,6 @@ class lib_test extends \advanced_testcase {
$this->assertMatchesRegularExpression('/50.5/', $result->info);
}
/**
* Ensure that assign_get_completion_state reflects the correct status at each point.
*/
public function test_assign_get_completion_state() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$assign = $this->create_instance($course, [
'submissiondrafts' => 0,
'completionsubmit' => 1
]);
$this->setUser($student);
$result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
$this->assertFalse($result);
$this->add_submission($student, $assign);
$result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
$this->assertFalse($result);
$this->submit_for_grading($student, $assign);
$result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
$this->assertTrue($result);
$this->mark_submission($teacher, $assign, $student, 50.0);
$result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
$this->assertTrue($result);
}
/**
* Tests for mod_assign_refresh_events.
*/

View File

@ -23,33 +23,9 @@
*/
/**
* Obtains the automatic completion state for this choice based on any conditions
* in forum settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_choice\completion\custom_completion
* @param object $course Course
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function choice_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get choice details.
$choice = $DB->get_record('choice', array('id' => $cm->instance), '*',
MUST_EXIST);
// If completion option is enabled, evaluate it and return true/false.
if ($choice->completionsubmit) {
return $DB->record_exists('choice_answers', array(
'choiceid' => $choice->id, 'userid' => $userid));
} else {
// Completion option is not enabled so just return $type.
return $type;
}
function choice_get_completion_state() {
$completionclass = \mod_choice\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}

View File

@ -23,43 +23,11 @@
*/
/**
* Obtains the automatic completion state for this database item based on any conditions
* on its settings. The call for this is in completion lib where the modulename is appended
* to the function name. This is why there are unused parameters.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_data\completion\custom_completion
* @since Moodle 3.3
* @param stdClass $course Course
* @param cm_info|stdClass $cm course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function data_get_completion_state($course, $cm, $userid, $type) {
global $DB, $PAGE;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
$result = $type; // Default return value
// Get data details.
if (isset($PAGE->cm->id) && $PAGE->cm->id == $cm->id) {
$data = $PAGE->activityrecord;
} else {
$data = $DB->get_record('data', array('id' => $cm->instance), '*', MUST_EXIST);
}
// If completion option is enabled, evaluate it and return true/false.
if ($data->completionentries) {
$numentries = data_numentries($data, $userid);
// Check the number of entries required against the number of entries already made.
if ($numentries >= $data->completionentries) {
$result = true;
} else {
$result = false;
}
}
return $result;
function data_get_completion_state() {
$completionclass = \mod_data\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}
/**

View File

@ -23,32 +23,9 @@
*/
/**
* Obtains the automatic completion state for this feedback based on the condition
* in feedback settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_feedback\completion\custom_completion
* @param object $course Course
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function feedback_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get feedback details.
$feedback = $DB->get_record('feedback', array('id' => $cm->instance), '*', MUST_EXIST);
// If completion option is enabled, evaluate it and return true/false.
if ($feedback->completionsubmit) {
$params = array('userid' => $userid, 'feedback' => $feedback->id);
return $DB->record_exists('feedback_completed', $params);
} else {
// Completion option is not enabled so just return $type.
return $type;
}
function feedback_get_completion_state() {
$completionclass = \mod_feedback\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}

View File

@ -410,71 +410,11 @@ function forum_get_user_grades() {
}
/**
* Obtains the automatic completion state for this forum based on any conditions
* in forum settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_forum\completion\custom_completion
* @global object
* @global object
* @param object $course Course
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not. (If no conditions, then return
* value depends on comparison type)
*/
function forum_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get forum details.
if (!($forum = $DB->get_record('forum', array('id' => $cm->instance)))) {
throw new Exception("Can't find forum {$cm->instance}");
}
$result = $type; // Default return value.
$postcountparams = array('userid' => $userid, 'forumid' => $forum->id);
$postcountsql = "
SELECT
COUNT(1)
FROM
{forum_posts} fp
INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
WHERE
fp.userid=:userid AND fd.forum=:forumid";
if ($forum->completiondiscussions) {
$value = $forum->completiondiscussions <=
$DB->count_records('forum_discussions', array('forum' => $forum->id, 'userid' => $userid));
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
if ($forum->completionreplies) {
$value = $forum->completionreplies <=
$DB->get_field_sql($postcountsql . ' AND fp.parent<>0', $postcountparams);
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
if ($forum->completionposts) {
$value = $forum->completionposts <= $DB->get_field_sql($postcountsql, $postcountparams);
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
return $result;
function forum_get_completion_state() {
$completionclass = \mod_forum\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}
/**

View File

@ -23,39 +23,9 @@
*/
/**
* Obtains the automatic completion state for this glossary based on any conditions
* in glossary settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_glossary\completion\custom_completion
* @param stdClass $course Course
* @param cm_info|stdClass $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not. (If no conditions, then return value depends on comparison type)
*/
function glossary_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get glossary details.
if (!($glossary = $DB->get_record('glossary', array('id' => $cm->instance)))) {
throw new Exception("Can't find glossary {$cm->instance}");
}
$result = $type; // Default return value.
if ($glossary->completionentries) {
$value = $glossary->completionentries <=
$DB->count_records('glossary_entries', array('glossaryid' => $glossary->id, 'userid' => $userid, 'approved' => 1));
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
return $result;
function glossary_get_completion_state() {
$completionclass = \mod_glossary\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}

View File

@ -23,51 +23,9 @@
*/
/**
* Obtains the automatic completion state for this lesson based on any conditions
* in lesson settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_lesson\completion\custom_completion
* @param stdClass $course Course
* @param cm_info|stdClass $cm course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function lesson_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get lesson details.
$lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST);
$result = $type; // Default return value.
// If completion option is enabled, evaluate it and return true/false.
if ($lesson->completionendreached) {
$value = $DB->record_exists('lesson_timer', array('lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1));
if ($type == COMPLETION_AND) {
$result = $result && $value;
} else {
$result = $result || $value;
}
}
if ($lesson->completiontimespent != 0) {
$duration = $DB->get_field_sql(
"SELECT SUM(lessontime - starttime)
FROM {lesson_timer}
WHERE lessonid = :lessonid
AND userid = :userid",
array('userid' => $userid, 'lessonid' => $lesson->id));
if (!$duration) {
$duration = 0;
}
if ($type == COMPLETION_AND) {
$result = $result && ($lesson->completiontimespent < $duration);
} else {
$result = $result || ($lesson->completiontimespent < $duration);
}
}
return $result;
function lesson_get_completion_state() {
$completionclass = \mod_lesson\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}

View File

@ -27,114 +27,11 @@ use mod_quiz\quiz_settings;
use mod_quiz\task\update_overdue_attempts;
/**
* Internal function used in quiz_get_completion_state. Check passing grade (or no attempts left) requirement for completion.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_quiz\completion\custom_completion
* @param stdClass $course
* @param cm_info|stdClass $cm
* @param int $userid
* @param stdClass $quiz
* @return bool True if the passing grade (or no attempts left) requirement is disabled or met.
* @throws coding_exception
*/
function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz) {
global $CFG;
debugging('quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', DEBUG_DEVELOPER);
if (!$cm->completionpassgrade) {
return true;
}
// Check for passing grade.
require_once($CFG->libdir . '/gradelib.php');
$item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod',
'itemmodule' => 'quiz', 'iteminstance' => $cm->instance, 'outcomeid' => null]);
if ($item) {
$grades = grade_grade::fetch_users_grades($item, [$userid], false);
if (!empty($grades[$userid]) && $grades[$userid]->is_passed($item)) {
return true;
}
}
// If a passing grade is required and exhausting all available attempts is not accepted for completion,
// then this quiz is not complete.
if (!$quiz->completionattemptsexhausted) {
return false;
}
// Check if all attempts are used up.
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
if (!$attempts) {
return false;
}
$lastfinishedattempt = end($attempts);
$context = context_module::instance($cm->id);
$quizobj = quiz_settings::create($quiz->id, $userid);
$accessmanager = new access_manager($quizobj, time(),
has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
return $accessmanager->is_finished(count($attempts), $lastfinishedattempt);
}
/**
* Internal function used in quiz_get_completion_state. Check minimum attempts requirement for completion.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_quiz\completion\custom_completion
* @param int $userid
* @param stdClass $quiz
* @return bool True if minimum attempts requirement is disabled or met.
*/
function quiz_completion_check_min_attempts($userid, $quiz) {
debugging('quiz_completion_check_min_attempts has been deprecated.', DEBUG_DEVELOPER);
if (empty($quiz->completionminattempts)) {
return true;
}
// Check if the user has done enough attempts.
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
return $quiz->completionminattempts <= count($attempts);
}
/**
* Obtains the automatic completion state for this quiz on any conditions
* in quiz settings, such as if all attempts are used or a certain grade is achieved.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_quiz\completion\custom_completion
* @param stdClass $course Course
* @param cm_info|stdClass $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not. (If no conditions, then return
* value depends on comparison type)
*/
function quiz_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
$quiz = $DB->get_record('quiz', ['id' => $cm->instance], '*', MUST_EXIST);
if (!$quiz->completionattemptsexhausted && !$cm->completionpassgrade && !$quiz->completionminattempts) {
return $type;
}
if (!quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz)) {
return false;
}
if (!quiz_completion_check_min_attempts($userid, $quiz)) {
return false;
}
return true;
function quiz_get_completion_state() {
$completionclass = \mod_quiz\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}
/**

View File

@ -134,299 +134,6 @@ class lib_test extends \advanced_testcase {
$this->assertEquals(0, $count);
}
/**
* Setup function for all test_quiz_get_completion_state_* tests.
*
* @param array $completionoptions ['nbstudents'] => int, ['qtype'] => string, ['quizoptions'] => array
* @throws dml_exception
* @return array [$course, $students, $quiz, $cm]
*/
private function setup_quiz_for_testing_completion(array $completionoptions) {
global $CFG, $DB;
$this->resetAfterTest(true);
// Enable completion before creating modules, otherwise the completion data is not written in DB.
$CFG->enablecompletion = true;
// Create a course and students.
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$course = $this->getDataGenerator()->create_course(['enablecompletion' => true]);
$students = [];
for ($i = 0; $i < $completionoptions['nbstudents']; $i++) {
$students[$i] = $this->getDataGenerator()->create_user();
$this->assertTrue($this->getDataGenerator()->enrol_user($students[$i]->id, $course->id, $studentrole->id));
}
// Make a quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$data = array_merge([
'course' => $course->id,
'grade' => 100.0,
'questionsperpage' => 0,
'sumgrades' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC
], $completionoptions['quizoptions']);
$quiz = $quizgenerator->create_instance($data);
$cm = get_coursemodule_from_id('quiz', $quiz->cmid);
// Create a question.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question($completionoptions['qtype'], null, ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz);
// Set grade to pass.
$item = \grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'quiz',
'iteminstance' => $quiz->id, 'outcomeid' => null]);
$item->gradepass = 80;
$item->update();
return [
$course,
$students,
$quiz,
$cm
];
}
/**
* Helper function for all test_quiz_get_completion_state_* tests.
* Starts an attempt, processes responses and finishes the attempt.
*
* @param $attemptoptions ['quiz'] => object, ['student'] => object, ['tosubmit'] => array, ['attemptnumber'] => int
*/
private function do_attempt_quiz($attemptoptions) {
$quizobj = quiz_settings::create($attemptoptions['quiz']->id);
// Start the passing attempt.
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
$attempt = quiz_create_attempt($quizobj, $attemptoptions['attemptnumber'], false, $timenow, false,
$attemptoptions['student']->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptoptions['attemptnumber'], $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Process responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_submitted_actions($timenow, false, $attemptoptions['tosubmit']);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
$attemptobj->process_finish($timenow, false);
}
/**
* Test checking the completion state of a quiz.
* The quiz requires a passing grade to be completed.
*/
public function test_quiz_get_completion_state_completionpass() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 2,
'qtype' => 'numerical',
'quizoptions' => [
'completionusegrade' => 1,
'completionpassgrade' => 1
]
]);
list($passstudent, $failstudent) = $students;
// Do a passing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $passstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results.
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
// Do a failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $failstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results.
$this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return'));
$this->assertDebuggingCalledCount(3, [
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
]);
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires either a passing grade or for all attempts to be used up.
*/
public function test_quiz_get_completion_state_completionexhausted() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 2,
'qtype' => 'numerical',
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpassgrade' => 1,
'completionattemptsexhausted' => 1
]
]);
list($passstudent, $exhauststudent) = $students;
// Start a passing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $passstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results. Quiz is completed by $passstudent because of passing grade.
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
// Do a failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $exhauststudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results. Quiz is not completed by $exhauststudent yet because of failing grade and of remaining attempts.
$this->assertFalse(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
// Do a second failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $exhauststudent,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results. Quiz is completed by $exhauststudent because there are no remaining attempts.
$this->assertTrue(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
$this->assertDebuggingCalledCount(5, [
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
]);
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires a minimum number of attempts.
*/
public function test_quiz_get_completion_state_completionminattempts() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 1,
'qtype' => 'essay',
'quizoptions' => [
'completionminattemptsenabled' => 1,
'completionminattempts' => 2
]
]);
list($student) = $students;
// Do a first attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
]);
// Check the results. Quiz is not completed yet because only one attempt was done.
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
// Do a second attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
]);
// Check the results. Quiz is completed by $student because two attempts were done.
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
$this->assertDebuggingCalledCount(4, [
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
]);
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires a minimum number of attempts AND a passing grade.
* This is somewhat of an edge case as it is hard to imagine a scenario in which these precise settings are useful.
* Nevertheless, this test makes sure these settings interact as intended.
*/
public function test_quiz_get_completion_state_completionminattempts_pass() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 1,
'qtype' => 'numerical',
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpassgrade' => 1,
'completionminattemptsenabled' => 1,
'completionminattempts' => 2
]
]);
list($student) = $students;
// Start a first attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results. Even though one requirement is met (passing grade) quiz is not completed yet because only
// one attempt was done.
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
// Start a second attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => '42']]
]);
// Check the results. Quiz is completed by $student because two attempts were done AND a passing grade was obtained.
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
$this->assertDebuggingCalledCount(4, [
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.',
'quiz_completion_check_min_attempts has been deprecated.',
]);
}
public function test_quiz_get_user_attempts() {
global $DB;
$this->resetAfterTest();

View File

@ -23,119 +23,9 @@
*/
/**
* Obtains the automatic completion state for this scorm based on any conditions
* in scorm settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_scorm\completion\custom_completion
* @param stdClass $course Course
* @param cm_info|stdClass $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not. (If no conditions, then return
* value depends on comparison type)
*/
function scorm_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
$result = $type;
// Get scorm.
if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) {
throw new \moodle_exception('cannotfindscorm');
}
// Only check for existence of tracks and return false if completionstatusrequired or completionscorerequired
// this means that if only view is required we don't end up with a false state.
if ($scorm->completionstatusrequired !== null || $scorm->completionscorerequired !== null) {
// Get user's tracks data.
$tracks = $DB->get_records_sql(
"
SELECT
v.id,
v.scoid,
e.element,
v.value
FROM
{scorm_scoes_value} v
JOIN {scorm_attempt} a on a.id = v.attemptid
JOIN {scorm_element} e on e.id = v.elementid
WHERE
a.scormid = ?
AND a.userid = ?
AND e.element IN
(
'cmi.core.lesson_status',
'cmi.completion_status',
'cmi.success_status',
'cmi.core.score.raw',
'cmi.score.raw'
)
",
array($scorm->id, $userid)
);
if (!$tracks) {
return completion_info::aggregate_completion_states($type, $result, false);
}
}
// Check for status.
if ($scorm->completionstatusrequired !== null) {
// Get status.
$statuses = array_flip(scorm_status_options());
$nstatus = 0;
// Check any track for these values.
$scostatus = array();
foreach ($tracks as $track) {
if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) {
continue;
}
if (array_key_exists($track->value, $statuses)) {
$scostatus[$track->scoid] = true;
$nstatus |= $statuses[$track->value];
}
}
if (!empty($scorm->completionstatusallscos)) {
// Iterate over all scos and make sure each has a lesson_status.
$scos = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id, 'scormtype' => 'sco'));
foreach ($scos as $sco) {
if (empty($scostatus[$sco->id])) {
return completion_info::aggregate_completion_states($type, $result, false);
}
}
return completion_info::aggregate_completion_states($type, $result, true);
} else if ($scorm->completionstatusrequired & $nstatus) {
return completion_info::aggregate_completion_states($type, $result, true);
} else {
return completion_info::aggregate_completion_states($type, $result, false);
}
}
// Check for score.
if ($scorm->completionscorerequired !== null) {
$maxscore = -1;
foreach ($tracks as $track) {
if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) {
continue;
}
if (strlen($track->value) && floatval($track->value) >= $maxscore) {
$maxscore = floatval($track->value);
}
}
if ($scorm->completionscorerequired <= $maxscore) {
return completion_info::aggregate_completion_states($type, $result, true);
} else {
return completion_info::aggregate_completion_states($type, $result, false);
}
}
return $result;
function scorm_get_completion_state() {
$completionclass = \mod_scorm\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}

View File

@ -23,32 +23,9 @@
*/
/**
* Obtains the automatic completion state for this survey based on the condition
* in feedback settings.
*
* @deprecated since Moodle 3.11
* @todo MDL-71196 Final deprecation in Moodle 4.3
* @see \mod_survey\completion\custom_completion
* @param stdClass $course Course
* @param cm_info|stdClass $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*/
function survey_get_completion_state($course, $cm, $userid, $type) {
global $DB;
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
// Get survey details.
$survey = $DB->get_record('survey', array('id' => $cm->instance), '*', MUST_EXIST);
// If completion option is enabled, evaluate it and return true/false.
if ($survey->completionsubmit) {
$params = array('userid' => $userid, 'survey' => $survey->id);
return $DB->record_exists('survey_answers', $params);
} else {
// Completion option is not enabled so just return $type.
return $type;
}
function survey_get_completion_state() {
$completionclass = \mod_survey\completion\custom_completion::class;
throw new coding_exception(__FUNCTION__ . "() has been removed, please use the '{$completionclass}' class instead");
}