* Review this criteria and decide if the user has completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- abstract public function review($userid);
+ abstract public function review($userid, $filtered = false);
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ abstract public function get_completed_criteria_sql();
/**
* Mark this criteria as complete for a user
public $criteriatype = BADGE_CRITERIA_TYPE_ACTIVITY;
private $courseid;
+ private $coursestartdate;
public $required_param = 'module';
public $optional_params = array('bydate');
public function __construct($record) {
+ global $DB;
parent::__construct($record);
- $this->courseid = self::get_course();
+
+ $course = $DB->get_record_sql('SELECT b.courseid, c.startdate
+ FROM {badge} b INNER JOIN {course} c ON b.courseid = c.id
+ WHERE b.id = :badgeid ', array('badgeid' => $this->badgeid));
+ $this->courseid = $course->courseid;
+ $this->coursestartdate = $course->startdate;
}
/**
}
}
- /**
- * Return course ID for activities
- *
- * @return int
- */
- private function get_course() {
- global $DB;
- $courseid = $DB->get_field('badge', 'courseid', array('id' => $this->badgeid));
- return $courseid;
- }
-
/**
* Add appropriate new criteria options to the form
*
* Review this criteria and decide if it has been completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
- global $DB;
+ public function review($userid, $filtered = false) {
$completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS);
- $course = $DB->get_record('course', array('id' => $this->courseid));
+ $course = new stdClass();
+ $course->id = $this->courseid;
- if ($course->startdate > time()) {
+ if ($this->coursestartdate > time()) {
return false;
}
} else {
return false;
}
- } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ } else {
if (in_array($data->completionstate, $completionstates) && $check_date) {
return true;
} else {
return $overall;
}
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ $join = '';
+ $where = '';
+ $params = array();
+
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ foreach ($this->params as $param) {
+ $moduledata[] = " cmc.coursemoduleid = :completedmodule{$param['module']} ";
+ $params["completedmodule{$param['module']}"] = $param['module'];
+ }
+ if (!empty($moduledata)) {
+ $extraon = implode(' OR ', $moduledata);
+ $join = " JOIN {course_modules_completion} cmc ON cmc.userid = u.id AND
+ ( cmc.completionstate = :completionpass OR cmc.completionstate = :completioncomplete ) AND ({$extraon})";
+ $params["completionpass"] = COMPLETION_COMPLETE_PASS;
+ $params["completioncomplete"] = COMPLETION_COMPLETE;
+ }
+ 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 = :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["completioncomplete{$param['module']}"] = COMPLETION_COMPLETE;
+ }
+ return array($join, $where, $params);
+ }
+ }
}
/* @var int Criteria [BADGE_CRITERIA_TYPE_COURSE] */
public $criteriatype = BADGE_CRITERIA_TYPE_COURSE;
+ private $courseid;
+ private $coursestartdate;
+
public $required_param = 'course';
public $optional_params = array('grade', 'bydate');
+ public function __construct($record) {
+ global $DB;
+ parent::__construct($record);
+
+ $course = $DB->get_record_sql('SELECT b.courseid, c.startdate
+ FROM {badge} b INNER JOIN {course} c ON b.courseid = c.id
+ WHERE b.id = :badgeid ', array('badgeid' => $this->badgeid));
+ $this->courseid = $course->courseid;
+ $this->coursestartdate = $course->startdate;
+ }
+
/**
* Add appropriate form elements to the criteria form
*
* Review this criteria and decide if it has been completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
- global $DB;
- foreach ($this->params as $param) {
- $course = $DB->get_record('course', array('id' => $param['course']));
+ public function review($userid, $filtered = false) {
+ $course = new stdClass();
+ $course->id = $this->courseid;
- if ($course->startdate > time()) {
- return false;
- }
+ if ($this->coursestartdate > time()) {
+ return false;
+ }
- $info = new completion_info($course);
+ $info = new completion_info($course);
+
+ foreach ($this->params as $param) {
$check_grade = true;
$check_date = true;
$check_grade = ($grade->grade >= $param['grade']);
}
- if (isset($param['bydate'])) {
+ if (!$filtered && isset($param['bydate'])) {
$cparams = array(
'userid' => $userid,
'course' => $course->id,
return false;
}
-}
\ No newline at end of file
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ // We have only one criterion here, so taking the first one.
+ $coursecriteria = reset($this->params);
+
+ $join = " LEFT JOIN {course_completions} cc ON cc.userid = u.id AND cc.timecompleted > 0";
+ $where = ' AND cc.course = :courseid ';
+ $params['courseid'] = $this->courseid;
+
+ // Add by date parameter.
+ if (isset($param['bydate'])) {
+ $where .= ' AND cc.timecompleted <= :completebydate';
+ $params['completebydate'] = $coursecriteria['bydate'];
+ }
+
+ return array($join, $where, $params);
+ }
+}
/**
* Review this criteria and decide if it has been completed
*
+ * @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
- global $DB;
+ public function review($userid, $filtered = false) {
foreach ($this->params as $param) {
- $course = $DB->get_record('course', array('id' => $param['course']));
+ $course = new stdClass();
+ $course->id = $param['course'];
+
$info = new completion_info($course);
$check_grade = true;
$check_date = true;
$check_grade = ($grade->grade >= $param['grade']);
}
- if (isset($param['bydate'])) {
+ if (!$filtered && isset($param['bydate'])) {
$cparams = array(
'userid' => $userid,
'course' => $course->id,
} else {
return false;
}
- } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ } else {
if ($info->is_course_complete($userid) && $check_grade && $check_date) {
return true;
} else {
return $overall;
}
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ $join = '';
+ $where = '';
+ $params = array();
+
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ foreach ($this->params as $param) {
+ $coursedata[] = " cc.course = :completedcourse{$param['course']} ";
+ $params["completedcourse{$param['course']}"] = $param['course'];
+ }
+ if (!empty($coursedata)) {
+ $extraon = implode(' OR ', $coursedata);
+ $join = " JOIN {course_completions} cc ON cc.userid = u.id AND
+ cc.timecompleted > 0 AND ({$extraon})";
+ }
+ return array($join, $where, $params);
+ } else {
+ foreach ($this->params as $param) {
+ $join .= " LEFT JOIN {course_completions} cc{$param['course']} ON
+ cc{$param['course']}.userid = u.id AND
+ cc{$param['course']}.course = :completedcourse{$param['course']} AND
+ cc{$param['course']}.timecompleted > 0 ";
+ $where .= " AND cc{$param['course']}.course IS NOT NULL ";
+ $params["completedcourse{$param['course']}"] = $param['course'];
+ }
+ return array($join, $where, $params);
+ }
+ }
}
* Review this criteria and decide if it has been completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
+ public function review($userid, $filtered = false) {
global $DB;
+ // Users were already filtered by criteria completion.
+ if ($filtered) {
+ return true;
+ }
+
$overall = false;
foreach ($this->params as $param) {
$crit = $DB->get_record('badge_manual_award', array('issuerrole' => $param['role'], 'recipientid' => $userid, 'badgeid' => $this->badgeid));
$overall = true;
continue;
}
- } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ } else {
if (!$crit) {
$overall = false;
continue;
return $overall;
}
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ $join = '';
+ $where = '';
+ $params = array();
+
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ foreach ($this->params as $param) {
+ $roledata[] = " bma.issuerrole = :issuerrole{$param['role']} ";
+ $params["issuerrole{$param['role']}"] = $param['role'];
+ }
+ if (!empty($roledata)) {
+ $extraon = implode(' OR ', $roledata);
+ $join = " JOIN {badge_manual_award} bma ON bma.recipientid = u.id
+ AND bma.badgeid = :badgeid{$this->badgeid} AND ({$extraon})";
+ $params["badgeid{$this->badgeid}"] = $this->badgeid;
+ }
+ return array($join, $where, $params);
+ } else {
+ foreach ($this->params as $param) {
+ $join .= " LEFT JOIN {badge_manual_award} bma{$param['role']} ON
+ bma{$param['role']}.recipientid = u.id AND
+ bma{$param['role']}.issuerrole = :issuerrole{$param['role']} ";
+ $where .= " AND bma{$param['role']}.issuerrole IS NOT NULL ";
+ $params["issuerrole{$param['role']}"] = $param['role'];
+ }
+ return array($join, $where, $params);
+ }
+ }
+
/**
* Delete this criterion
*
* Overall criteria review should be called only from other criteria handlers.
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
+ public function review($userid, $filtered = false) {
global $DB;
$sql = "SELECT bc.*, bcm.critid, bcm.userid, bcm.datemet
$overall = true;
continue;
}
- } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ } else {
if ($crit->datemet === null) {
$overall = false;
continue;
return $overall;
}
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ return array('', '', array());
+ }
+
/**
* Add appropriate criteria elements to the form
*
* Review this criteria and decide if it has been completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
* @return bool Whether criteria is complete
*/
- public function review($userid) {
+ public function review($userid, $filtered = false) {
global $DB;
- $overall = false;
+ // Users were already filtered by criteria completion, no checks required.
+ if ($filtered) {
+ return true;
+ }
+
+ $join = '';
+ $where = '';
+ $sqlparams = array();
+ $rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
+
foreach ($this->params as $param) {
if (is_numeric($param['field'])) {
- $crit = $DB->get_field('user_info_data', 'data', array('userid' => $userid, 'fieldid' => $param['field']));
+ $infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
+ $sqlparams["fieldid{$param['field']}"] = $param['field'];
} else {
- $crit = $DB->get_field('user', $param['field'], array('id' => $userid));
+ $userdata[] = " u.{$param['field']} != '' ";
}
+ }
- if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
- if (!$crit) {
- return false;
- } else {
- $overall = true;
- continue;
- }
- } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
- if (!$crit) {
- $overall = false;
- continue;
- } else {
- return true;
- }
- }
+ // Add user custom field parameters if there are any.
+ if (!empty($infodata)) {
+ $extraon = implode($rule, $infodata);
+ $join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
+ }
+
+ // Add user table field parameters if there are any.
+ if (!empty($userdata)) {
+ $extraon = implode($rule, $userdata);
+ $where = " AND ({$extraon})";
}
+
+ $sqlparams['userid'] = $userid;
+ $sql = "SELECT u.* FROM {user} u " . $join . " WHERE u.id = :userid " . $where;
+ $overall = $DB->record_exists_sql($sql, $sqlparams);
+
return $overall;
}
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ $join = '';
+ $where = '';
+ $params = array();
+ $rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
+
+ foreach ($this->params as $param) {
+ if (is_numeric($param['field'])) {
+ $infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
+ $params["fieldid{$param['field']}"] = $param['field'];
+ } else {
+ $userdata[] = " u.{$param['field']} != '' ";
+ }
+ }
+
+ // Add user custom fields if there are any.
+ if (!empty($infodata)) {
+ $extraon = implode($rule, $infodata);
+ $join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
+ }
+
+ // Add user table fields if there are any.
+ if (!empty($userdata)) {
+ $extraon = implode($rule, $userdata);
+ $where = " AND ({$extraon})";
+ }
+ return array($join, $where, $params);
+ }
}
$criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
$criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
$criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
- $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address'));
+ $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address', 'field_aim' => 'aim'));
$this->user->address = 'Test address';
+ $this->user->aim = '999999999';
$sink = $this->redirectEmails();
user_update_user($this->user, false);
$this->assertCount(1, $sink->get_messages());
--- /dev/null
+This files describes API changes in /badges/*,
+information provided here is intended especially for developers.
+
+=== 2.7 ===
+
+* get_completed_criteria_sql() - This method was added to award_criteria class and must be overriden
+ in all criteria classes. This method returns an array consisting of SQL JOIN statement, WHERE conditions,
+ and any parameters that might be required. The results are used in lib/badgeslib.php in review_all_criteria()
+ to reduce to the minimum the number of users to review and award badges.
+
+* New optional parameter $filtered in review() allows to indicate that some expensive checks can be skipped
+ if the list of users has been initially filtered based on met criteria.
core_php_time_limit::raise();
raise_memory_limit(MEMORY_HUGE);
- // For site level badges, get all active site users who can earn this badge and haven't got it yet.
- if ($this->type == BADGE_TYPE_SITE) {
- $sql = 'SELECT DISTINCT u.id, bi.badgeid
+ foreach ($this->criteria as $crit) {
+ // Overall criterion is decided when other criteria are reviewed.
+ if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
+ continue;
+ }
+
+ list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
+ // For site level badges, get all active site users who can earn this badge and haven't got it yet.
+ if ($this->type == BADGE_TYPE_SITE) {
+ $sql = "SELECT DISTINCT u.id, bi.badgeid
FROM {user} u
+ {$extrajoin}
LEFT JOIN {badge_issued} bi
ON u.id = bi.userid AND bi.badgeid = :badgeid
- WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0';
- $toearn = $DB->get_fieldset_sql($sql, array('badgeid' => $this->id, 'guestid' => $CFG->siteguest));
- } else {
- // For course level badges, get users who can earn this badge in the course.
- // These are all enrolled users with capability moodle/badges:earnbadge.
- $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
- $users = get_enrolled_users($this->get_context(), 'moodle/badges:earnbadge', 0, 'u.id');
- $toearn = array_diff(array_keys($users), $earned);
- }
+ WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere;
+ $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
+ $toearn = $DB->get_fieldset_sql($sql, $params);
+ } else {
+ // For course level badges, get all users who already earned the badge in this course.
+ // Then find the ones who are enrolled in the course and don't have a badge yet.
+ $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
+ $wheresql = '';
+ $earnedparams = array();
+ if (!empty($earned)) {
+ list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
+ $wheresql = ' WHERE u.id ' . $earnedsql;
+ }
+ list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
+ $sql = "SELECT u.id
+ FROM {user} u
+ {$extrajoin}
+ JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
+ $params = array_merge($enrolledparams, $earnedparams, $extraparams);
+ $toearn = $DB->get_fieldset_sql($sql, $params);
+ }
- foreach ($toearn as $uid) {
- $toreview = false;
- foreach ($this->criteria as $crit) {
- if ($crit->criteriatype != BADGE_CRITERIA_TYPE_OVERALL) {
- if ($crit->review($uid)) {
- $crit->mark_complete($uid);
- if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
- $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
- $this->issue($uid);
- $awards++;
- break;
- } else {
- $toreview = true;
- continue;
- }
+ foreach ($toearn as $uid) {
+ $reviewoverall = false;
+ if ($crit->review($uid, true)) {
+ $crit->mark_complete($uid);
+ if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+ $this->issue($uid);
+ $awards++;
} else {
- if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
- continue;
- } else {
- break;
- }
+ $reviewoverall = true;
}
+ } else {
+ // Will be reviewed some other time.
+ $reviewoverall = false;
+ }
+ // Review overall if it is required.
+ if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
+ $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
+ $this->issue($uid);
+ $awards++;
}
- }
- // Review overall if it is required.
- if ($toreview && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
- $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
- $this->issue($uid);
- $awards++;
}
}