Merge branch 'MDL-42965_master' of https://github.com/totara/openbadges
authorSam Hemelryk <sam@moodle.com>
Mon, 16 Dec 2013 00:47:57 +0000 (13:47 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 16 Dec 2013 00:47:57 +0000 (13:47 +1300)
badges/criteria/award_criteria.php
badges/criteria/award_criteria_activity.php
badges/criteria/award_criteria_course.php
badges/criteria/award_criteria_courseset.php
badges/criteria/award_criteria_manual.php
badges/criteria/award_criteria_overall.php
badges/criteria/award_criteria_profile.php
badges/tests/badgeslib_test.php
badges/upgrade.txt [new file with mode: 0644]
lib/badgeslib.php

index 96526c4..4287cdd 100644 (file)
@@ -236,9 +236,20 @@ abstract class award_criteria {
      * 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
index ff9b92e..8f4d4b0 100644 (file)
@@ -37,13 +37,20 @@ class award_criteria_activity extends award_criteria {
     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;
     }
 
     /**
@@ -95,17 +102,6 @@ class award_criteria_activity extends award_criteria {
         }
     }
 
-    /**
-     * 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
      *
@@ -184,14 +180,17 @@ class award_criteria_activity extends award_criteria {
      * 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;
         }
 
@@ -217,7 +216,7 @@ class award_criteria_activity extends award_criteria {
                 } else {
                     return false;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if (in_array($data->completionstate, $completionstates) && $check_date) {
                     return true;
                 } else {
@@ -229,4 +228,44 @@ class award_criteria_activity extends award_criteria {
 
         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);
+        }
+    }
 }
index c6089a2..295d927 100644 (file)
@@ -38,9 +38,23 @@ class award_criteria_course extends award_criteria {
     /* @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
      *
@@ -151,18 +165,22 @@ class award_criteria_course extends award_criteria {
      * 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;
 
@@ -171,7 +189,7 @@ class award_criteria_course extends award_criteria {
                 $check_grade = ($grade->grade >= $param['grade']);
             }
 
-            if (isset($param['bydate'])) {
+            if (!$filtered && isset($param['bydate'])) {
                 $cparams = array(
                         'userid' => $userid,
                         'course' => $course->id,
@@ -188,4 +206,27 @@ class award_criteria_course extends award_criteria {
 
         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);
+    }
+}
index a20e70b..5aabfe8 100644 (file)
@@ -202,12 +202,17 @@ class award_criteria_courseset extends award_criteria {
     /**
      * 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;
@@ -217,7 +222,7 @@ class award_criteria_courseset extends award_criteria {
                 $check_grade = ($grade->grade >= $param['grade']);
             }
 
-            if (isset($param['bydate'])) {
+            if (!$filtered && isset($param['bydate'])) {
                 $cparams = array(
                         'userid' => $userid,
                         'course' => $course->id,
@@ -235,7 +240,7 @@ class award_criteria_courseset extends award_criteria {
                 } 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 {
@@ -247,4 +252,39 @@ class award_criteria_courseset extends award_criteria {
 
         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);
+        }
+    }
 }
index 672c9f8..616ba97 100644 (file)
@@ -142,11 +142,19 @@ class award_criteria_manual extends award_criteria {
      * 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));
@@ -157,7 +165,7 @@ class award_criteria_manual extends award_criteria {
                     $overall = true;
                     continue;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if (!$crit) {
                     $overall = false;
                     continue;
@@ -169,6 +177,41 @@ class award_criteria_manual extends award_criteria {
         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
      *
index f04e3a4..32e8711 100644 (file)
@@ -86,9 +86,12 @@ class award_criteria_overall extends award_criteria {
      * 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
@@ -114,7 +117,7 @@ class award_criteria_overall extends award_criteria {
                     $overall = true;
                     continue;
                 }
-            } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+            } else {
                 if ($crit->datemet === null) {
                     $overall = false;
                     continue;
@@ -127,6 +130,16 @@ class award_criteria_overall extends award_criteria {
         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
      *
index 9940e90..ec871ab 100644 (file)
@@ -156,35 +156,84 @@ class award_criteria_profile extends award_criteria {
      * 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);
+    }
 }
index b70d377..97b3271 100644 (file)
@@ -292,9 +292,10 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
         $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());
diff --git a/badges/upgrade.txt b/badges/upgrade.txt
new file mode 100644 (file)
index 0000000..1a48ffd
--- /dev/null
@@ -0,0 +1,12 @@
+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.
index 57c86fa..8d1bf9e 100644 (file)
@@ -429,51 +429,63 @@ class badge {
         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++;
             }
         }