mirror of
https://github.com/moodle/moodle.git
synced 2025-03-11 03:15:24 +01:00
MDL-63120 core_badges: Avoid multiple joins in sql statement
This commit is contained in:
parent
2e1c6fd43e
commit
a076a80dec
@ -236,6 +236,7 @@ class award_criteria_activity extends award_criteria {
|
||||
* @return array list($join, $where, $params)
|
||||
*/
|
||||
public function get_completed_criteria_sql() {
|
||||
global $DB;
|
||||
$join = '';
|
||||
$where = '';
|
||||
$params = array();
|
||||
@ -257,18 +258,41 @@ class award_criteria_activity extends award_criteria {
|
||||
}
|
||||
return array($join, $where, $params);
|
||||
} else {
|
||||
foreach ($this->params as $param) {
|
||||
$join .= " LEFT JOIN {course_modules_completion} cmc{$param['module']} ON
|
||||
cmc{$param['module']}.userid = u.id AND
|
||||
cmc{$param['module']}.coursemoduleid = :completedmodule{$param['module']} AND
|
||||
( cmc{$param['module']}.completionstate = :completionpass{$param['module']} OR
|
||||
cmc{$param['module']}.completionstate = :completionfail{$param['module']} OR
|
||||
cmc{$param['module']}.completionstate = :completioncomplete{$param['module']} )";
|
||||
$where .= " AND cmc{$param['module']}.coursemoduleid IS NOT NULL ";
|
||||
$params["completedmodule{$param['module']}"] = $param['module'];
|
||||
$params["completionpass{$param['module']}"] = COMPLETION_COMPLETE_PASS;
|
||||
$params["completionfail{$param['module']}"] = COMPLETION_COMPLETE_FAIL;
|
||||
$params["completioncomplete{$param['module']}"] = COMPLETION_COMPLETE;
|
||||
// Get all cmids of modules related to the criteria.
|
||||
$cmids = array_map(fn ($x) => $x['module'], $this->params);
|
||||
list($cmcmodulessql, $paramscmc) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
|
||||
|
||||
// Create a sql query to get all users who have worked on these course modules.
|
||||
$sql = "SELECT DISTINCT userid FROM {course_modules_completion} cmc "
|
||||
. "WHERE coursemoduleid " . $cmcmodulessql . " AND "
|
||||
. "( cmc.completionstate IN ( :completionpass, :completionfail, :completioncomplete ) )";
|
||||
$paramscmcs = [
|
||||
'completionpass' => COMPLETION_COMPLETE_PASS,
|
||||
'completionfail' => COMPLETION_COMPLETE_FAIL,
|
||||
'completioncomplete' => COMPLETION_COMPLETE
|
||||
];
|
||||
$paramscmc = array_merge($paramscmc, $paramscmcs);
|
||||
$userids = $DB->get_records_sql($sql, $paramscmc);
|
||||
|
||||
// Now check each user if the user has a completion of each module.
|
||||
$useridsbadgeable = array_keys(array_filter(
|
||||
$userids,
|
||||
function ($user) use ($cmcmodulessql, $paramscmc, $cmids) {
|
||||
global $DB;
|
||||
$params = array_merge($paramscmc, ['userid' => $user->userid]);
|
||||
$select = "coursemoduleid " . $cmcmodulessql . " AND userid = :userid";
|
||||
$cmidsuser = $DB->get_fieldset_select('course_modules_completion', 'coursemoduleid', $select, $params);
|
||||
return empty(array_diff($cmidsuser, $cmids));
|
||||
}
|
||||
));
|
||||
|
||||
// Finally create a where statement (if neccessary) with all userids who are allowed to get the badge.
|
||||
// This list also includes all users who have previously received the badge. These are filtered out in the badge.php.
|
||||
$join = "";
|
||||
$where = "";
|
||||
if (!empty($useridsbadgeable)) {
|
||||
list($wherepart, $params) = $DB->get_in_or_equal($useridsbadgeable, SQL_PARAMS_NAMED);
|
||||
$where = " AND u.id " . $wherepart;
|
||||
}
|
||||
return array($join, $where, $params);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ require_once($CFG->libdir . '/badgeslib.php');
|
||||
require_once($CFG->dirroot . '/badges/lib.php');
|
||||
|
||||
use core_badges\helper;
|
||||
use core\task\manager;
|
||||
|
||||
class badgeslib_test extends advanced_testcase {
|
||||
protected $badgeid;
|
||||
@ -511,6 +512,88 @@ class badgeslib_test extends advanced_testcase {
|
||||
$this->assertEquals(badge_message_from_template($message, $params), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for working around the 61 tables join limit of mysql in award_criteria_activity in combination with the scheduled task.
|
||||
* @return void
|
||||
* @throws dml_transaction_exception
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
* @throws ddl_exception
|
||||
* @throws AssertionFailedError
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ExpectationFailedException
|
||||
* @covers \core_badges\badge->review_all_criteria()
|
||||
*/
|
||||
public function test_badge_activity_criteria_with_a_huge_number_of_coursemodules() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
|
||||
|
||||
// Messaging is not compatible with transactions.
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Create more than 61 modules to potentially trigger an mysql db error.
|
||||
$assigncount = 75;
|
||||
$assigns = [];
|
||||
for ($i = 1; $i <= $assigncount; $i++) {
|
||||
$assigns[] = $this->getDataGenerator()->create_module('assign', ['course' => $this->course->id], ['completion' => 1]);
|
||||
}
|
||||
$assigncmids = array_flip(array_map(fn ($assign) => $assign->cmid, $assigns));
|
||||
$criteriaactivityarray = array_fill_keys(array_keys($assigncmids), 1);
|
||||
|
||||
// Set completion criteria.
|
||||
$criteriadata = (object) [
|
||||
'id' => $this->course->id,
|
||||
'criteria_activity' => $criteriaactivityarray,
|
||||
];
|
||||
$criterion = new completion_criteria_activity();
|
||||
$criterion->update_config($criteriadata);
|
||||
|
||||
$badge = new badge($this->coursebadge);
|
||||
|
||||
$criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
|
||||
$criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
|
||||
$criteriaactivity = award_criteria::build(['criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id]);
|
||||
|
||||
$modulescrit = ['agg' => BADGE_CRITERIA_AGGREGATION_ALL];
|
||||
foreach ($assigns as $assign) {
|
||||
$modulescrit['module_' . $assign->cmid] = $assign->cmid;
|
||||
}
|
||||
$criteriaactivity->save($modulescrit);
|
||||
|
||||
// Take one assign to complete it later.
|
||||
$assingtemp = array_shift($assigns);
|
||||
|
||||
// Mark the user to complete the modules.
|
||||
foreach ($assigns as $assign) {
|
||||
$cmassign = get_coursemodule_from_id('assign', $assign->cmid);
|
||||
$completion = new \completion_info($this->course);
|
||||
$completion->update_state($cmassign, COMPLETION_COMPLETE, $this->user->id);
|
||||
}
|
||||
|
||||
// Run the scheduled task to issue the badge. But the badge should not be issued.
|
||||
ob_start();
|
||||
$task = manager::get_scheduled_task('core\task\badges_cron_task');
|
||||
$task->execute();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
|
||||
// Now complete the last uncompleted module.
|
||||
$cmassign = get_coursemodule_from_id('assign', $assingtemp->cmid);
|
||||
$completion = new \completion_info($this->course);
|
||||
$completion->update_state($cmassign, COMPLETION_COMPLETE, $this->user->id);
|
||||
|
||||
// Run the scheduled task to issue the badge. Now the badge schould be issued.
|
||||
ob_start();
|
||||
$task = manager::get_scheduled_task('core\task\badges_cron_task');
|
||||
$task->execute();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertDebuggingCalled('Error baking badge image!');
|
||||
$this->assertTrue($badge->is_issued($this->user->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test badges observer when course module completion event id fired.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user