mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 09:26:35 +02:00
Merge branch 'MDL-32103-master' of git://github.com/ilyatregubov/moodle
This commit is contained in:
commit
730792e51b
26 changed files with 966 additions and 267 deletions
|
@ -555,7 +555,7 @@ class core_grades_external extends external_api {
|
|||
}
|
||||
|
||||
return grade_update($params['source'], $params['courseid'], $itemtype,
|
||||
$itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']);
|
||||
$itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,135 +64,7 @@ class completion_regular_task extends scheduled_task {
|
|||
}
|
||||
}
|
||||
|
||||
if (debugging()) {
|
||||
mtrace('Aggregating completions');
|
||||
}
|
||||
|
||||
// Save time started.
|
||||
$timestarted = time();
|
||||
|
||||
// Grab all criteria and their associated criteria completions.
|
||||
$sql = 'SELECT DISTINCT c.id AS course, cr.id AS criteriaid, crc.userid AS userid,
|
||||
cr.criteriatype AS criteriatype, cc.timecompleted AS timecompleted
|
||||
FROM {course_completion_criteria} cr
|
||||
INNER JOIN {course} c ON cr.course = c.id
|
||||
INNER JOIN {course_completions} crc ON crc.course = c.id
|
||||
LEFT JOIN {course_completion_crit_compl} cc ON cc.criteriaid = cr.id AND crc.userid = cc.userid
|
||||
WHERE c.enablecompletion = 1
|
||||
AND crc.timecompleted IS NULL
|
||||
AND crc.reaggregate > 0
|
||||
AND crc.reaggregate < :timestarted
|
||||
ORDER BY course, userid';
|
||||
$rs = $DB->get_recordset_sql($sql, ['timestarted' => $timestarted]);
|
||||
|
||||
// Check if result is empty.
|
||||
if (!$rs->valid()) {
|
||||
$rs->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$currentuser = null;
|
||||
$currentcourse = null;
|
||||
$completions = [];
|
||||
while (1) {
|
||||
// Grab records for current user/course.
|
||||
foreach ($rs as $record) {
|
||||
// If we are still grabbing the same users completions.
|
||||
if ($record->userid === $currentuser && $record->course === $currentcourse) {
|
||||
$completions[$record->criteriaid] = $record;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate.
|
||||
if (!empty($completions)) {
|
||||
if (debugging()) {
|
||||
mtrace('Aggregating completions for user ' . $currentuser . ' in course ' . $currentcourse);
|
||||
}
|
||||
|
||||
// Get course info object.
|
||||
$info = new \completion_info((object)['id' => $currentcourse]);
|
||||
|
||||
// Setup aggregation.
|
||||
$overall = $info->get_aggregation_method();
|
||||
$activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY);
|
||||
$prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE);
|
||||
$role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE);
|
||||
|
||||
$overallstatus = null;
|
||||
$activitystatus = null;
|
||||
$prerequisitestatus = null;
|
||||
$rolestatus = null;
|
||||
|
||||
// Get latest timecompleted.
|
||||
$timecompleted = null;
|
||||
|
||||
// Check each of the criteria.
|
||||
foreach ($completions as $params) {
|
||||
$timecompleted = max($timecompleted, $params->timecompleted);
|
||||
$completion = new \completion_criteria_completion((array)$params, false);
|
||||
|
||||
// Handle aggregation special cases.
|
||||
if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
|
||||
completion_cron_aggregate($activity, $completion->is_complete(), $activitystatus);
|
||||
} else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
|
||||
completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisitestatus);
|
||||
} else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) {
|
||||
completion_cron_aggregate($role, $completion->is_complete(), $rolestatus);
|
||||
} else {
|
||||
completion_cron_aggregate($overall, $completion->is_complete(), $overallstatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Include role criteria aggregation in overall aggregation.
|
||||
if ($rolestatus !== null) {
|
||||
completion_cron_aggregate($overall, $rolestatus, $overallstatus);
|
||||
}
|
||||
|
||||
// Include activity criteria aggregation in overall aggregation.
|
||||
if ($activitystatus !== null) {
|
||||
completion_cron_aggregate($overall, $activitystatus, $overallstatus);
|
||||
}
|
||||
|
||||
// Include prerequisite criteria aggregation in overall aggregation.
|
||||
if ($prerequisitestatus !== null) {
|
||||
completion_cron_aggregate($overall, $prerequisitestatus, $overallstatus);
|
||||
}
|
||||
|
||||
// If aggregation status is true, mark course complete for user.
|
||||
if ($overallstatus) {
|
||||
if (debugging()) {
|
||||
mtrace('Marking complete');
|
||||
}
|
||||
|
||||
$ccompletion = new \completion_completion([
|
||||
'course' => $params->course,
|
||||
'userid' => $params->userid
|
||||
]);
|
||||
$ccompletion->mark_complete($timecompleted);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the end of the recordset, break the loop.
|
||||
if (!$rs->valid()) {
|
||||
$rs->close();
|
||||
break;
|
||||
}
|
||||
|
||||
// New/next user, update user details, reset completions.
|
||||
$currentuser = $record->userid;
|
||||
$currentcourse = $record->course;
|
||||
$completions = [];
|
||||
$completions[$record->criteriaid] = $record;
|
||||
}
|
||||
|
||||
// Mark all users as aggregated.
|
||||
$sql = "UPDATE {course_completions}
|
||||
SET reaggregate = 0
|
||||
WHERE reaggregate < :timestarted
|
||||
AND reaggregate > 0";
|
||||
$DB->execute($sql, ['timestarted' => $timestarted]);
|
||||
aggregate_completions(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -585,10 +585,12 @@ class completion_info {
|
|||
* must be used; these directly set the specified state.
|
||||
* @param int $userid User ID to be updated. Default 0 = current user
|
||||
* @param bool $override Whether manually overriding the existing completion state.
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return void
|
||||
* @throws moodle_exception if trying to override without permission.
|
||||
*/
|
||||
public function update_state($cm, $possibleresult=COMPLETION_UNKNOWN, $userid=0, $override = false) {
|
||||
public function update_state($cm, $possibleresult=COMPLETION_UNKNOWN, $userid=0,
|
||||
$override = false, $isbulkupdate = false) {
|
||||
global $USER;
|
||||
|
||||
// Do nothing if completion is not enabled for that activity
|
||||
|
@ -662,7 +664,7 @@ class completion_info {
|
|||
$current->completionstate = $newstate;
|
||||
$current->timemodified = time();
|
||||
$current->overrideby = $override ? $USER->id : null;
|
||||
$this->internal_set_data($cm, $current);
|
||||
$this->internal_set_data($cm, $current, $isbulkupdate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1177,9 +1179,11 @@ class completion_info {
|
|||
*
|
||||
* @param stdClass|cm_info $cm Activity
|
||||
* @param stdClass $data Data about completion for that user
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
*/
|
||||
public function internal_set_data($cm, $data) {
|
||||
global $USER, $DB;
|
||||
public function internal_set_data($cm, $data, $isbulkupdate = false) {
|
||||
global $USER, $DB, $CFG;
|
||||
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
|
||||
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
if (!$data->id) {
|
||||
|
@ -1222,6 +1226,17 @@ class completion_info {
|
|||
$completioncache->delete($data->userid . '_' . $cm->course);
|
||||
}
|
||||
|
||||
// For single user actions the code must reevaluate some completion state instantly, see MDL-32103.
|
||||
if ($isbulkupdate) {
|
||||
return;
|
||||
} else {
|
||||
$userdata = ['userid' => $data->userid, 'courseid' => $this->course_id];
|
||||
$coursecompletionid = \core_completion\api::mark_course_completions_activity_criteria($userdata);
|
||||
if ($coursecompletionid) {
|
||||
aggregate_completions($coursecompletionid);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger an event for course module completion changed.
|
||||
$event = \core\event\course_module_completion_updated::create(array(
|
||||
'objectid' => $data->id,
|
||||
|
@ -1421,8 +1436,9 @@ class completion_info {
|
|||
* @param grade_item $item Grade item
|
||||
* @param stdClass $grade
|
||||
* @param bool $deleted
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
*/
|
||||
public function inform_grade_changed($cm, $item, $grade, $deleted) {
|
||||
public function inform_grade_changed($cm, $item, $grade, $deleted, $isbulkupdate = false) {
|
||||
// Bail out now if completion is not enabled for course-module, it is enabled
|
||||
// but is set to manual, grade is not used to compute completion, or this
|
||||
// is a different numbered grade
|
||||
|
@ -1442,7 +1458,7 @@ class completion_info {
|
|||
}
|
||||
|
||||
// OK, let's update state based on this
|
||||
$this->update_state($cm, $possibleresult, $grade->userid);
|
||||
$this->update_state($cm, $possibleresult, $grade->userid, false, $isbulkupdate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1541,3 +1557,157 @@ function completion_cron_aggregate($method, $data, &$state) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate courses completions. This function is called when activity completion status is updated
|
||||
* for single user. Also when regular completion task runs it aggregates completions for all courses and users.
|
||||
*
|
||||
* @param int $coursecompletionid Course completion ID to update (if 0 - update for all courses and users)
|
||||
* @param bool $mtraceprogress To output debug info
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
function aggregate_completions(int $coursecompletionid, bool $mtraceprogress = false) {
|
||||
global $DB;
|
||||
|
||||
if (!$coursecompletionid && $mtraceprogress) {
|
||||
mtrace('Aggregating completions');
|
||||
}
|
||||
// Save time started.
|
||||
$timestarted = time();
|
||||
|
||||
// Grab all criteria and their associated criteria completions.
|
||||
$sql = "SELECT DISTINCT c.id AS courseid, cr.id AS criteriaid, cco.userid, cr.criteriatype, ccocr.timecompleted
|
||||
FROM {course_completion_criteria} cr
|
||||
INNER JOIN {course} c ON cr.course = c.id
|
||||
INNER JOIN {course_completions} cco ON cco.course = c.id
|
||||
LEFT JOIN {course_completion_crit_compl} ccocr
|
||||
ON ccocr.criteriaid = cr.id AND cco.userid = ccocr.userid
|
||||
WHERE c.enablecompletion = 1
|
||||
AND cco.timecompleted IS NULL
|
||||
AND cco.reaggregate > 0";
|
||||
|
||||
if ($coursecompletionid) {
|
||||
$sql .= " AND cco.id = ?";
|
||||
$param = $coursecompletionid;
|
||||
} else {
|
||||
$sql .= " AND cco.reaggregate < ? ORDER BY courseid, cco.userid";
|
||||
$param = $timestarted;
|
||||
}
|
||||
$rs = $DB->get_recordset_sql($sql, [$param]);
|
||||
|
||||
// Check if result is empty.
|
||||
if (!$rs->valid()) {
|
||||
$rs->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$currentuser = null;
|
||||
$currentcourse = null;
|
||||
$completions = [];
|
||||
while (1) {
|
||||
// Grab records for current user/course.
|
||||
foreach ($rs as $record) {
|
||||
// If we are still grabbing the same users completions.
|
||||
if ($record->userid === $currentuser && $record->courseid === $currentcourse) {
|
||||
$completions[$record->criteriaid] = $record;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate.
|
||||
if (!empty($completions)) {
|
||||
if (!$coursecompletionid && $mtraceprogress) {
|
||||
mtrace('Aggregating completions for user ' . $currentuser . ' in course ' . $currentcourse);
|
||||
}
|
||||
|
||||
// Get course info object.
|
||||
$info = new \completion_info((object)['id' => $currentcourse]);
|
||||
|
||||
// Setup aggregation.
|
||||
$overall = $info->get_aggregation_method();
|
||||
$activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY);
|
||||
$prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE);
|
||||
$role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE);
|
||||
|
||||
$overallstatus = null;
|
||||
$activitystatus = null;
|
||||
$prerequisitestatus = null;
|
||||
$rolestatus = null;
|
||||
|
||||
// Get latest timecompleted.
|
||||
$timecompleted = null;
|
||||
|
||||
// Check each of the criteria.
|
||||
foreach ($completions as $params) {
|
||||
$timecompleted = max($timecompleted, $params->timecompleted);
|
||||
$completion = new \completion_criteria_completion((array)$params, false);
|
||||
|
||||
// Handle aggregation special cases.
|
||||
if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
|
||||
completion_cron_aggregate($activity, $completion->is_complete(), $activitystatus);
|
||||
} else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
|
||||
completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisitestatus);
|
||||
} else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) {
|
||||
completion_cron_aggregate($role, $completion->is_complete(), $rolestatus);
|
||||
} else {
|
||||
completion_cron_aggregate($overall, $completion->is_complete(), $overallstatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Include role criteria aggregation in overall aggregation.
|
||||
if ($rolestatus !== null) {
|
||||
completion_cron_aggregate($overall, $rolestatus, $overallstatus);
|
||||
}
|
||||
|
||||
// Include activity criteria aggregation in overall aggregation.
|
||||
if ($activitystatus !== null) {
|
||||
completion_cron_aggregate($overall, $activitystatus, $overallstatus);
|
||||
}
|
||||
|
||||
// Include prerequisite criteria aggregation in overall aggregation.
|
||||
if ($prerequisitestatus !== null) {
|
||||
completion_cron_aggregate($overall, $prerequisitestatus, $overallstatus);
|
||||
}
|
||||
|
||||
// If aggregation status is true, mark course complete for user.
|
||||
if ($overallstatus) {
|
||||
if (!$coursecompletionid && $mtraceprogress) {
|
||||
mtrace('Marking complete');
|
||||
}
|
||||
|
||||
$ccompletion = new \completion_completion([
|
||||
'course' => $params->courseid,
|
||||
'userid' => $params->userid
|
||||
]);
|
||||
$ccompletion->mark_complete($timecompleted);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the end of the recordset, break the loop.
|
||||
if (!$rs->valid()) {
|
||||
$rs->close();
|
||||
break;
|
||||
}
|
||||
|
||||
// New/next user, update user details, reset completions.
|
||||
$currentuser = $record->userid;
|
||||
$currentcourse = $record->courseid;
|
||||
$completions = [];
|
||||
$completions[$record->criteriaid] = $record;
|
||||
}
|
||||
|
||||
// Mark all users as aggregated.
|
||||
if ($coursecompletionid) {
|
||||
$select = "reaggregate > 0 AND id = ?";
|
||||
$param = $coursecompletionid;
|
||||
} else {
|
||||
$select = "reaggregate > 0 AND reaggregate < ?";
|
||||
$param = $timestarted;
|
||||
if (PHPUNIT_TEST) {
|
||||
// MDL-33320: for instant completions we need aggregate to work in a single run.
|
||||
$DB->set_field('course_completions', 'reaggregate', $timestarted - 2);
|
||||
}
|
||||
}
|
||||
$DB->set_field_select('course_completions', 'reaggregate', 0, $select, [$param]);
|
||||
}
|
||||
|
|
|
@ -227,9 +227,10 @@ class grade_category extends grade_object {
|
|||
* In addition to update() as defined in grade_object, call force_regrading of parent categories, if applicable.
|
||||
*
|
||||
* @param string $source from where was the object updated (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source = null, $isbulkupdate = false) {
|
||||
// load the grade item or create a new one
|
||||
$this->load_grade_item();
|
||||
|
||||
|
@ -352,9 +353,10 @@ class grade_category extends grade_object {
|
|||
* This method also creates an associated grade_item if this wasn't done during construction.
|
||||
*
|
||||
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return int PK ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
|
||||
if (empty($this->courseid)) {
|
||||
print_error('cannotinsertgrade');
|
||||
|
|
|
@ -442,12 +442,12 @@ class grade_grade extends grade_object {
|
|||
public function set_overridden($state, $refresh = true) {
|
||||
if (empty($this->overridden) and $state) {
|
||||
$this->overridden = time();
|
||||
$this->update();
|
||||
$this->update(null, true);
|
||||
return true;
|
||||
|
||||
} else if (!empty($this->overridden) and !$state) {
|
||||
$this->overridden = 0;
|
||||
$this->update();
|
||||
$this->update(null, true);
|
||||
|
||||
if ($refresh) {
|
||||
//refresh when unlocking
|
||||
|
@ -1025,12 +1025,13 @@ class grade_grade extends grade_object {
|
|||
* Insert the grade_grade instance into the database.
|
||||
*
|
||||
* @param string $source From where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return int The new grade_grade ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
// TODO: dategraded hack - do not update times, they are used for submission and grading (MDL-31379)
|
||||
//$this->timecreated = $this->timemodified = time();
|
||||
return parent::insert($source);
|
||||
return parent::insert($source, $isbulkupdate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1038,14 +1039,15 @@ class grade_grade extends grade_object {
|
|||
* the reason is we need to compare the db value with computed number to skip updates if possible.
|
||||
*
|
||||
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source=null, $isbulkupdate = false) {
|
||||
$this->rawgrade = grade_floatval($this->rawgrade);
|
||||
$this->finalgrade = grade_floatval($this->finalgrade);
|
||||
$this->rawgrademin = grade_floatval($this->rawgrademin);
|
||||
$this->rawgrademax = grade_floatval($this->rawgrademax);
|
||||
return parent::update($source);
|
||||
return parent::update($source, $isbulkupdate);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1138,8 +1140,9 @@ class grade_grade extends grade_object {
|
|||
* has changed, and clear up a possible score cache.
|
||||
*
|
||||
* @param bool $deleted True if grade was actually deleted
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
*/
|
||||
protected function notify_changed($deleted) {
|
||||
protected function notify_changed($deleted, $isbulkupdate = false) {
|
||||
global $CFG;
|
||||
|
||||
// Condition code may cache the grades for conditional availability of
|
||||
|
@ -1200,7 +1203,7 @@ class grade_grade extends grade_object {
|
|||
}
|
||||
|
||||
// Pass information on to completion system
|
||||
$completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
|
||||
$completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted, $isbulkupdate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -282,9 +282,10 @@ class grade_item extends grade_object {
|
|||
* the reason is we need to compare the db value with computed number to skip regrading if possible.
|
||||
*
|
||||
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source = null, $isbulkupdate = false) {
|
||||
// reset caches
|
||||
$this->dependson_cache = null;
|
||||
|
||||
|
@ -309,7 +310,7 @@ class grade_item extends grade_object {
|
|||
$this->aggregationcoef = grade_floatval($this->aggregationcoef);
|
||||
$this->aggregationcoef2 = grade_floatval($this->aggregationcoef2);
|
||||
|
||||
$result = parent::update($source);
|
||||
$result = parent::update($source, $isbulkupdate);
|
||||
|
||||
if ($result) {
|
||||
$event = \core\event\grade_item_updated::create_from_grade_item($this);
|
||||
|
@ -499,9 +500,10 @@ class grade_item extends grade_object {
|
|||
* In addition to perform parent::insert(), calls force_regrading() method too.
|
||||
*
|
||||
* @param string $source From where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param string $isbulkupdate If bulk grade update is happening.
|
||||
* @return int PK ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
global $CFG, $DB;
|
||||
|
||||
if (empty($this->courseid)) {
|
||||
|
@ -540,7 +542,7 @@ class grade_item extends grade_object {
|
|||
|
||||
$this->timecreated = $this->timemodified = time();
|
||||
|
||||
if (parent::insert($source)) {
|
||||
if (parent::insert($source, $isbulkupdate)) {
|
||||
// force regrading of items if needed
|
||||
$this->force_regrading();
|
||||
|
||||
|
@ -1790,12 +1792,11 @@ class grade_item extends grade_object {
|
|||
* @param int $feedbackformat A format like FORMAT_PLAIN or FORMAT_HTML
|
||||
* @param int $usermodified The ID of the user making the modification
|
||||
* @param int $timemodified Optional parameter to set the time modified, if not present current time.
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update_final_grade($userid, $finalgrade = false,
|
||||
$source = null, $feedback = false,
|
||||
$feedbackformat = FORMAT_MOODLE,
|
||||
$usermodified = null, $timemodified = null) {
|
||||
public function update_final_grade($userid, $finalgrade = false, $source = null, $feedback = false,
|
||||
$feedbackformat = FORMAT_MOODLE, $usermodified = null, $timemodified = null, $isbulkupdate = false) {
|
||||
global $USER, $CFG;
|
||||
|
||||
$result = true;
|
||||
|
@ -1863,7 +1864,7 @@ class grade_item extends grade_object {
|
|||
if (empty($grade->id)) {
|
||||
$grade->timecreated = null; // Hack alert - date submitted - no submission yet.
|
||||
$grade->timemodified = $timemodified ?? time(); // Hack alert - date graded.
|
||||
$result = (bool)$grade->insert($source);
|
||||
$result = (bool)$grade->insert($source, $isbulkupdate);
|
||||
|
||||
// If the grade insert was successful and the final grade was not null then trigger a user_graded event.
|
||||
if ($result && !is_null($grade->finalgrade)) {
|
||||
|
@ -1887,7 +1888,7 @@ class grade_item extends grade_object {
|
|||
}
|
||||
|
||||
$grade->timemodified = $timemodified ?? time(); // Hack alert - date graded.
|
||||
$result = $grade->update($source);
|
||||
$result = $grade->update($source, $isbulkupdate);
|
||||
|
||||
// If the grade update was successful and the actual grade has changed then trigger a user_graded event.
|
||||
if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
|
||||
|
@ -1949,11 +1950,12 @@ class grade_item extends grade_object {
|
|||
* 'filearea' => 'mod_xyz_feedback',
|
||||
* 'itemid' => 2
|
||||
* ];
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update_raw_grade($userid, $rawgrade = false, $source = null, $feedback = false,
|
||||
$feedbackformat = FORMAT_MOODLE, $usermodified = null, $dategraded = null, $datesubmitted=null,
|
||||
$grade = null, array $feedbackfiles = []) {
|
||||
$grade = null, array $feedbackfiles = [], $isbulkupdate = false) {
|
||||
global $USER;
|
||||
|
||||
$result = true;
|
||||
|
@ -2053,7 +2055,7 @@ class grade_item extends grade_object {
|
|||
|
||||
$gradechanged = false;
|
||||
if (empty($grade->id)) {
|
||||
$result = (bool)$grade->insert($source);
|
||||
$result = (bool)$grade->insert($source, $isbulkupdate);
|
||||
|
||||
// If the grade insert was successful and the final grade was not null then trigger a user_graded event.
|
||||
if ($result && !is_null($grade->finalgrade)) {
|
||||
|
@ -2080,7 +2082,7 @@ class grade_item extends grade_object {
|
|||
// No changes.
|
||||
return $result;
|
||||
}
|
||||
$result = $grade->update($source);
|
||||
$result = $grade->update($source, $isbulkupdate);
|
||||
|
||||
// If the grade update was successful and the actual grade has changed then trigger a user_graded event.
|
||||
if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
|
||||
|
|
|
@ -238,9 +238,10 @@ abstract class grade_object {
|
|||
* Updates this object in the Database, based on its object variables. ID must be set.
|
||||
*
|
||||
* @param string $source from where was the object updated (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source = null, $isbulkupdate = false) {
|
||||
global $USER, $CFG, $DB;
|
||||
|
||||
if (empty($this->id)) {
|
||||
|
@ -263,7 +264,7 @@ abstract class grade_object {
|
|||
$historyid = $DB->insert_record($this->table.'_history', $data);
|
||||
}
|
||||
|
||||
$this->notify_changed(false);
|
||||
$this->notify_changed(false, $isbulkupdate);
|
||||
|
||||
$this->update_feedback_files($historyid);
|
||||
|
||||
|
@ -334,9 +335,10 @@ abstract class grade_object {
|
|||
* in object properties.
|
||||
*
|
||||
* @param string $source From where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param string $isbulkupdate If bulk grade update is happening.
|
||||
* @return int The new grade object ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
global $USER, $CFG, $DB;
|
||||
|
||||
if (!empty($this->id)) {
|
||||
|
@ -364,7 +366,7 @@ abstract class grade_object {
|
|||
$historyid = $DB->insert_record($this->table.'_history', $data);
|
||||
}
|
||||
|
||||
$this->notify_changed(false);
|
||||
$this->notify_changed(false, $isbulkupdate);
|
||||
|
||||
$this->add_feedback_files($historyid);
|
||||
|
||||
|
|
|
@ -123,9 +123,10 @@ class grade_outcome extends grade_object {
|
|||
* in object properties.
|
||||
*
|
||||
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return int PK ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
global $DB;
|
||||
|
||||
$this->timecreated = $this->timemodified = time();
|
||||
|
@ -145,9 +146,10 @@ class grade_outcome extends grade_object {
|
|||
* In addition to update() it also updates grade_outcomes_courses if needed
|
||||
*
|
||||
* @param string $source from where was the object inserted
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source = null, $isbulkupdate = false) {
|
||||
$this->timemodified = time();
|
||||
|
||||
if ($result = parent::update($source)) {
|
||||
|
|
|
@ -114,9 +114,10 @@ class grade_scale extends grade_object {
|
|||
* in object properties.
|
||||
*
|
||||
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return int PK ID if successful, false otherwise
|
||||
*/
|
||||
public function insert($source=null) {
|
||||
public function insert($source = null, $isbulkupdate = false) {
|
||||
$this->timecreated = time();
|
||||
$this->timemodified = time();
|
||||
|
||||
|
@ -145,9 +146,10 @@ class grade_scale extends grade_object {
|
|||
* In addition to update() it also updates grade_outcomes_courses if needed
|
||||
*
|
||||
* @param string $source from where was the object inserted
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return bool success
|
||||
*/
|
||||
public function update($source=null) {
|
||||
public function update($source = null, $isbulkupdate = false) {
|
||||
$this->timemodified = time();
|
||||
|
||||
$result = parent::update($source);
|
||||
|
|
|
@ -58,9 +58,11 @@ require_once($CFG->libdir . '/grade/grade_outcome.php');
|
|||
* @param int $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user
|
||||
* @param mixed $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only
|
||||
* @param mixed $itemdetails Object or array describing the grading item, NULL if no change
|
||||
* @param bool $isbulkupdate If bulk grade update is happening.
|
||||
* @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
|
||||
*/
|
||||
function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) {
|
||||
function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades = null,
|
||||
$itemdetails = null, $isbulkupdate = false) {
|
||||
global $USER, $CFG, $DB;
|
||||
|
||||
// only following grade_item properties can be changed in this function
|
||||
|
@ -76,22 +78,20 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
return GRADE_UPDATE_FAILED;
|
||||
}
|
||||
|
||||
if (!$grade_items = grade_item::fetch_all($params)) {
|
||||
if (!$gradeitems = grade_item::fetch_all($params)) {
|
||||
// create a new one
|
||||
$grade_item = false;
|
||||
|
||||
} else if (count($grade_items) == 1){
|
||||
$grade_item = reset($grade_items);
|
||||
unset($grade_items); //release memory
|
||||
|
||||
$gradeitem = false;
|
||||
} else if (count($gradeitems) == 1) {
|
||||
$gradeitem = reset($gradeitems);
|
||||
unset($gradeitems); // Release memory.
|
||||
} else {
|
||||
debugging('Found more than one grade item');
|
||||
return GRADE_UPDATE_MULTIPLE;
|
||||
}
|
||||
|
||||
if (!empty($itemdetails['deleted'])) {
|
||||
if ($grade_item) {
|
||||
if ($grade_item->delete($source)) {
|
||||
if ($gradeitem) {
|
||||
if ($gradeitem->delete($source)) {
|
||||
return GRADE_UPDATE_OK;
|
||||
} else {
|
||||
return GRADE_UPDATE_FAILED;
|
||||
|
@ -102,7 +102,7 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
|
||||
/// Create or update the grade_item if needed
|
||||
|
||||
if (!$grade_item) {
|
||||
if (!$gradeitem) {
|
||||
if ($itemdetails) {
|
||||
$itemdetails = (array)$itemdetails;
|
||||
|
||||
|
@ -126,11 +126,11 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
$params[$k] = $v;
|
||||
}
|
||||
}
|
||||
$grade_item = new grade_item($params);
|
||||
$grade_item->insert();
|
||||
$gradeitem = new grade_item($params);
|
||||
$gradeitem->insert(null, $isbulkupdate);
|
||||
|
||||
} else {
|
||||
if ($grade_item->is_locked()) {
|
||||
if ($gradeitem->is_locked()) {
|
||||
// no notice() here, test returned value instead!
|
||||
return GRADE_UPDATE_ITEM_LOCKED;
|
||||
}
|
||||
|
@ -144,33 +144,33 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
continue;
|
||||
}
|
||||
if (in_array($k, $floats)) {
|
||||
if (grade_floats_different($grade_item->{$k}, $v)) {
|
||||
$grade_item->{$k} = $v;
|
||||
if (grade_floats_different($gradeitem->{$k}, $v)) {
|
||||
$gradeitem->{$k} = $v;
|
||||
$update = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
if ($grade_item->{$k} != $v) {
|
||||
$grade_item->{$k} = $v;
|
||||
if ($gradeitem->{$k} != $v) {
|
||||
$gradeitem->{$k} = $v;
|
||||
$update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($update) {
|
||||
$grade_item->update();
|
||||
$gradeitem->update(null, $isbulkupdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// reset grades if requested
|
||||
if (!empty($itemdetails['reset'])) {
|
||||
$grade_item->delete_all_grades('reset');
|
||||
$gradeitem->delete_all_grades('reset');
|
||||
return GRADE_UPDATE_OK;
|
||||
}
|
||||
|
||||
/// Some extra checks
|
||||
// do we use grading?
|
||||
if ($grade_item->gradetype == GRADE_TYPE_NONE) {
|
||||
if ($gradeitem->gradetype == GRADE_TYPE_NONE) {
|
||||
return GRADE_UPDATE_OK;
|
||||
}
|
||||
|
||||
|
@ -208,12 +208,12 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
$count = count($grades);
|
||||
if ($count > 0 and $count < 200) {
|
||||
list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid');
|
||||
$params['gid'] = $grade_item->id;
|
||||
$params['gid'] = $gradeitem->id;
|
||||
$sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid $uids";
|
||||
|
||||
} else {
|
||||
$sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid";
|
||||
$params = array('gid'=>$grade_item->id);
|
||||
$params = array('gid' => $gradeitem->id);
|
||||
}
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
|
@ -221,7 +221,7 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
$failed = false;
|
||||
|
||||
while (count($grades) > 0) {
|
||||
$grade_grade = null;
|
||||
$gradegrade = null;
|
||||
$grade = null;
|
||||
|
||||
foreach ($rs as $gd) {
|
||||
|
@ -233,21 +233,21 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
}
|
||||
// existing grade requested
|
||||
$grade = $grades[$userid];
|
||||
$grade_grade = new grade_grade($gd, false);
|
||||
$gradegrade = new grade_grade($gd, false);
|
||||
unset($grades[$userid]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_null($grade_grade)) {
|
||||
if (is_null($gradegrade)) {
|
||||
if (count($grades) == 0) {
|
||||
// no more grades to process
|
||||
// No more grades to process.
|
||||
break;
|
||||
}
|
||||
|
||||
$grade = reset($grades);
|
||||
$userid = $grade['userid'];
|
||||
$grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false);
|
||||
$grade_grade->load_optional_fields(); // add feedback and info too
|
||||
$gradegrade = new grade_grade(array('itemid' => $gradeitem->id, 'userid' => $userid), false);
|
||||
$gradegrade->load_optional_fields(); // add feedback and info too
|
||||
unset($grades[$userid]);
|
||||
}
|
||||
|
||||
|
@ -288,8 +288,8 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
|
|||
}
|
||||
|
||||
// update or insert the grade
|
||||
if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified,
|
||||
$dategraded, $datesubmitted, $grade_grade, $feedbackfiles)) {
|
||||
if (!$gradeitem->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified,
|
||||
$dategraded, $datesubmitted, $gradegrade, $feedbackfiles, $isbulkupdate)) {
|
||||
$failed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -826,6 +826,38 @@ class core_completionlib_testcase extends advanced_testcase {
|
|||
$d3->overrideby = null;
|
||||
$DB->insert_record('course_modules_completion', $d3);
|
||||
$c->internal_set_data($cm, $data);
|
||||
|
||||
// 4) Test instant course completions.
|
||||
$dataactivity = $this->getDataGenerator()->create_module('data', array('course' => $this->course->id),
|
||||
array('completion' => 1));
|
||||
$cm = get_coursemodule_from_instance('data', $dataactivity->id);
|
||||
$c = new completion_info($this->course);
|
||||
$cmdata = get_coursemodule_from_id('data', $dataactivity->cmid);
|
||||
|
||||
// Add activity completion criteria.
|
||||
$criteriadata = new stdClass();
|
||||
$criteriadata->id = $this->course->id;
|
||||
$criteriadata->criteria_activity = array();
|
||||
// Some activities.
|
||||
$criteriadata->criteria_activity[$cmdata->id] = 1;
|
||||
$class = 'completion_criteria_activity';
|
||||
$criterion = new $class();
|
||||
$criterion->update_config($criteriadata);
|
||||
|
||||
$actual = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($actual);
|
||||
|
||||
$data->coursemoduleid = $cm->id;
|
||||
$c->internal_set_data($cm, $data);
|
||||
$actual = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($actual));
|
||||
$this->assertEquals($this->user->id, reset($actual)->userid);
|
||||
|
||||
$data->userid = $newuser2->id;
|
||||
$c->internal_set_data($cm, $data, true);
|
||||
$actual = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($actual));
|
||||
$this->assertEquals($this->user->id, reset($actual)->userid);
|
||||
}
|
||||
|
||||
public function test_get_progress_all_few() {
|
||||
|
@ -1350,6 +1382,302 @@ class core_completionlib_testcase extends advanced_testcase {
|
|||
// The implicitly created grade_item does not have grade to pass defined so it is not distinguished.
|
||||
$this->assertEquals(COMPLETION_COMPLETE, $completioninfo->get_grade_completion($cm, $this->user->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for aggregate_completions().
|
||||
*/
|
||||
public function test_aggregate_completions() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$time = time();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$students[] = $this->getDataGenerator()->create_user();
|
||||
}
|
||||
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
foreach ($students as $student) {
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
}
|
||||
|
||||
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
|
||||
array('completion' => 1));
|
||||
$cmdata = get_coursemodule_from_id('data', $data->cmid);
|
||||
|
||||
// Add activity completion criteria.
|
||||
$criteriadata = new stdClass();
|
||||
$criteriadata->id = $course->id;
|
||||
$criteriadata->criteria_activity = array();
|
||||
// Some activities.
|
||||
$criteriadata->criteria_activity[$cmdata->id] = 1;
|
||||
$class = 'completion_criteria_activity';
|
||||
$criterion = new $class();
|
||||
$criterion->update_config($criteriadata);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Mark activity complete for both students.
|
||||
$cm = get_coursemodule_from_instance('data', $data->id);
|
||||
$completioncriteria = $DB->get_record('course_completion_criteria', []);
|
||||
foreach ($students as $student) {
|
||||
$cmcompletionrecords[] = (object)[
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $student->id,
|
||||
'completionstate' => 1,
|
||||
'viewed' => 0,
|
||||
'overrideby' => null,
|
||||
'timemodified' => 0,
|
||||
];
|
||||
|
||||
$usercompletions[] = (object)[
|
||||
'criteriaid' => $completioncriteria->id,
|
||||
'userid' => $student->id,
|
||||
'timecompleted' => $time,
|
||||
];
|
||||
|
||||
$cc = array(
|
||||
'course' => $course->id,
|
||||
'userid' => $student->id
|
||||
);
|
||||
$ccompletion = new completion_completion($cc);
|
||||
$completion[] = $ccompletion->mark_inprogress($time);
|
||||
}
|
||||
$DB->insert_records('course_modules_completion', $cmcompletionrecords);
|
||||
$DB->insert_records('course_completion_crit_compl', $usercompletions);
|
||||
|
||||
// MDL-33320: for instant completions we need aggregate to work in a single run.
|
||||
$DB->set_field('course_completions', 'reaggregate', $time - 2);
|
||||
|
||||
foreach ($students as $student) {
|
||||
$result = $DB->get_record('course_completions', ['userid' => $student->id, 'reaggregate' => 0]);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
aggregate_completions($completion[0]);
|
||||
|
||||
$result1 = $DB->get_record('course_completions', ['userid' => $students[0]->id, 'reaggregate' => 0]);
|
||||
$result2 = $DB->get_record('course_completions', ['userid' => $students[1]->id, 'reaggregate' => 0]);
|
||||
$result3 = $DB->get_record('course_completions', ['userid' => $students[2]->id, 'reaggregate' => 0]);
|
||||
|
||||
$this->assertIsObject($result1);
|
||||
$this->assertFalse($result2);
|
||||
$this->assertFalse($result3);
|
||||
|
||||
aggregate_completions(0);
|
||||
|
||||
foreach ($students as $student) {
|
||||
$result = $DB->get_record('course_completions', ['userid' => $student->id, 'reaggregate' => 0]);
|
||||
$this->assertIsObject($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for completion_completion::_save().
|
||||
*/
|
||||
public function test_save() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$cc = array(
|
||||
'course' => $course->id,
|
||||
'userid' => $student->id
|
||||
);
|
||||
$ccompletion = new completion_completion($cc);
|
||||
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($completions);
|
||||
|
||||
// We're testing a private method, so we need to setup reflector magic.
|
||||
$method = new ReflectionMethod($ccompletion, '_save');
|
||||
$method->setAccessible(true); // Allow accessing of private method.
|
||||
$completionid = $method->invoke($ccompletion);
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(count($completions), 1);
|
||||
$this->assertEquals(reset($completions)->id, $completionid);
|
||||
|
||||
$ccompletion->id = 0;
|
||||
$method = new ReflectionMethod($ccompletion, '_save');
|
||||
$method->setAccessible(true); // Allow accessing of private method.
|
||||
$completionid = $method->invoke($ccompletion);
|
||||
$this->assertDebuggingCalled('Can not update data object, no id!');
|
||||
$this->assertNull($completionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for completion_completion::mark_enrolled().
|
||||
*/
|
||||
public function test_mark_enrolled() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$cc = array(
|
||||
'course' => $course->id,
|
||||
'userid' => $student->id
|
||||
);
|
||||
$ccompletion = new completion_completion($cc);
|
||||
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($completions);
|
||||
|
||||
$completionid = $ccompletion->mark_enrolled();
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(count($completions), 1);
|
||||
$this->assertEquals(reset($completions)->id, $completionid);
|
||||
|
||||
$ccompletion->id = 0;
|
||||
$completionid = $ccompletion->mark_enrolled();
|
||||
$this->assertDebuggingCalled('Can not update data object, no id!');
|
||||
$this->assertNull($completionid);
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for completion_completion::mark_inprogress().
|
||||
*/
|
||||
public function test_mark_inprogress() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$cc = array(
|
||||
'course' => $course->id,
|
||||
'userid' => $student->id
|
||||
);
|
||||
$ccompletion = new completion_completion($cc);
|
||||
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($completions);
|
||||
|
||||
$completionid = $ccompletion->mark_inprogress();
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
$this->assertEquals(reset($completions)->id, $completionid);
|
||||
|
||||
$ccompletion->id = 0;
|
||||
$completionid = $ccompletion->mark_inprogress();
|
||||
$this->assertDebuggingCalled('Can not update data object, no id!');
|
||||
$this->assertNull($completionid);
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for completion_completion::mark_complete().
|
||||
*/
|
||||
public function test_mark_complete() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$cc = array(
|
||||
'course' => $course->id,
|
||||
'userid' => $student->id
|
||||
);
|
||||
$ccompletion = new completion_completion($cc);
|
||||
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($completions);
|
||||
|
||||
$completionid = $ccompletion->mark_complete();
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
$this->assertEquals(reset($completions)->id, $completionid);
|
||||
|
||||
$ccompletion->id = 0;
|
||||
$completionid = $ccompletion->mark_complete();
|
||||
$this->assertNull($completionid);
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for completion_criteria_completion::mark_complete().
|
||||
*/
|
||||
public function test_criteria_mark_complete() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$record = [
|
||||
'course' => $course->id,
|
||||
'criteriaid' => 1,
|
||||
'userid' => $student->id,
|
||||
'timecompleted' => time()
|
||||
];
|
||||
$completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
|
||||
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEmpty($completions);
|
||||
|
||||
$completionid = $completion->mark_complete($record['timecompleted']);
|
||||
$completions = $DB->get_records('course_completions');
|
||||
$this->assertEquals(1, count($completions));
|
||||
$this->assertEquals(reset($completions)->id, $completionid);
|
||||
}
|
||||
}
|
||||
|
||||
class core_completionlib_fake_recordset implements Iterator {
|
||||
|
|
2
lib/tests/fixtures/upload_grades.csv
vendored
Normal file
2
lib/tests/fixtures/upload_grades.csv
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Email address, Test assignment name
|
||||
student1@example.com, 10
|
|
|
@ -26,6 +26,36 @@ information provided here is intended especially for developers.
|
|||
should no longer be used.
|
||||
* The completion_info function print_help_icon() which has been deprecated since Moodle 2.0 should no longer be used.
|
||||
* @babel/polyfill has been removed in favour of corejs@3
|
||||
* A new parameter $isbulkupdate has been added to the following functions:
|
||||
- grade_category::update()
|
||||
- grade_category::insert()
|
||||
- grade_grade::update()
|
||||
- grade_grade::insert()
|
||||
- grade_grade::notify_changed()
|
||||
- grade_item::insert()
|
||||
- grade_item::update()
|
||||
- grade_item::update_final_grade()
|
||||
- grade_item::update_raw_grade()
|
||||
- grade_object::update()
|
||||
- grade_object::insert()
|
||||
- grade_outcome::update()
|
||||
- grade_outcome::insert()
|
||||
- grade_scale::update()
|
||||
- grade_scale::insert()
|
||||
- grade_update()
|
||||
- completion_info::inform_grade_changed()
|
||||
- completion_info::update_state()
|
||||
- completion_info::internal_set_data()
|
||||
All functions except completion_info::internal_set_data() are only passing this parameter from very beginning of
|
||||
workflow (like grade report page where bulk grade update is possible) so this parameter is used in
|
||||
completion_info::internal_set_data() to decide if we need to mark completions instantly without waiting for cron.
|
||||
* Following methods now return an int instead of bool:
|
||||
- completion_completion::_save()
|
||||
- completion_completion::mark_enrolled()
|
||||
- completion_completion::mark_inprogress()
|
||||
- completion_completion::mark_complete()
|
||||
which is needed to store id of completion record on successful update which is later beeing used by
|
||||
completion_info::internal_set_data() to reaggregate completions that have been marked for instant course completion.
|
||||
|
||||
=== 3.11 ===
|
||||
* PHPUnit has been upgraded to 9.5 (see MDL-71036 for details).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue