2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Classes to enforce the various access rules that can apply to a quiz.
21 * @subpackage quiz_results
22 * @copyright 2009 Tim Hunt
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') ||
die();
29 require_once($CFG->dirroot
. '/mod/quiz/lib.php');
33 * Block quiz_results class definition.
35 * This block can be added to a course page or a quiz page to display of list of
36 * the best/worst students/groups in a particular quiz.
39 * @subpackage quiz_results
40 * @copyright 2009 Tim Hunt
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 define('B_QUIZRESULTS_NAME_FORMAT_FULL', 1);
44 define('B_QUIZRESULTS_NAME_FORMAT_ID', 2);
45 define('B_QUIZRESULTS_NAME_FORMAT_ANON', 3);
46 define('B_QUIZRESULTS_GRADE_FORMAT_PCT', 1);
47 define('B_QUIZRESULTS_GRADE_FORMAT_FRA', 2);
48 define('B_QUIZRESULTS_GRADE_FORMAT_ABS', 3);
50 class block_quiz_results
extends block_base
{
52 $this->title
= get_string('pluginname', 'block_quiz_results');
55 function applicable_formats() {
56 return array('course' => true, 'mod-quiz' => true);
60 * If this block belongs to a quiz context, then return that quiz's id.
61 * Otherwise, return 0.
62 * @return integer the quiz id.
64 public function get_owning_quiz() {
65 if (empty($this->instance
->parentcontextid
)) {
68 $parentcontext = get_context_instance_by_id($this->instance
->parentcontextid
);
69 if ($parentcontext->contextlevel
!= CONTEXT_MODULE
) {
72 $cm = get_coursemodule_from_id('quiz', $parentcontext->instanceid
);
79 function instance_config_save($data, $nolongerused = false) {
80 if (empty($data->quizid
)) {
81 $data->quizid
= $this->get_owning_quiz();
83 parent
::instance_config_save($data);
86 function get_content() {
87 global $USER, $CFG, $DB;
89 if ($this->content
!== NULL) {
90 return $this->content
;
93 $this->content
= new stdClass
;
94 $this->content
->text
= '';
95 $this->content
->footer
= '';
97 if (empty($this->instance
)) {
98 return $this->content
;
101 if ($this->page
->activityname
== 'quiz' && $this->page
->context
->id
== $this->instance
->parentcontextid
) {
102 $quiz = $this->page
->activityrecord
;
104 $courseid = $this->page
->course
->id
;
106 } else if (!empty($this->config
->quizid
)) {
107 $quizid = $this->config
->quizid
;
108 $quiz = $DB->get_record('quiz', array('id' => $quizid));
110 $this->content
->text
= get_string('error_emptyquizrecord', 'block_quiz_results');
111 return $this->content
;
113 $courseid = $quiz->course
;
119 if (empty($quizid)) {
120 $this->content
->text
= get_string('error_emptyquizid', 'block_quiz_results');
121 return $this->content
;
124 if (empty($this->config
->showbest
) && empty($this->config
->showworst
)) {
125 $this->content
->text
= get_string('configuredtoshownothing', 'block_quiz_results');
126 return $this->content
;
129 // Get the grades for this quiz
130 $grades = $DB->get_records('quiz_grades', array('quiz' => $quizid), 'grade, timemodified DESC');
132 if (empty($grades)) {
134 // The block will hide itself in this case
135 return $this->content
;
138 $groupmode = NOGROUPS
;
142 if (!empty($this->config
->nameformat
)) {
143 $nameformat = $this->config
->nameformat
;
145 $nameformat = B_QUIZRESULTS_NAME_FORMAT_FULL
;
148 if (!empty($this->config
->usegroups
)) {
150 $cm = $this->page
->cm
;
151 $context = $this->page
->context
;
153 $cm = get_coursemodule_from_instance('quiz', $quizid, $courseid);
154 $context = get_context_instance(CONTEXT_MODULE
, $cm->id
);
156 $groupmode = groups_get_activity_groupmode($cm);
158 if ($groupmode == SEPARATEGROUPS
&& has_capability('moodle/site:accessallgroups', $context)) {
159 // We 'll make an exception in this case
160 $groupmode = VISIBLEGROUPS
;
164 switch ($groupmode) {
166 // Display group-mode results
167 $groups = groups_get_all_groups($courseid);
170 // No groups exist, sorry
171 $this->content
->text
= get_string('error_nogroupsexist', 'block_quiz_results');
172 return $this->content
;
175 // Find out all the userids which have a submitted grade
177 $gradeforuser = array();
178 foreach ($grades as $grade) {
179 $userids[] = $grade->userid
;
180 $gradeforuser[$grade->userid
] = (float)$grade->grade
;
183 // Now find which groups these users belong in
184 list($usertest, $params) = $DB->get_in_or_equal($userids);
185 $params[] = $courseid;
186 $usergroups = $DB->get_records_sql('
187 SELECT gm.id, gm.userid, gm.groupid, g.name
189 LEFT JOIN {groups_members} gm ON g.id = gm.groupid
190 WHERE gm.userid ' . $usertest . ' AND g.courseid = ?', $params);
192 // Now, iterate the grades again and sum them up for each group
193 $groupgrades = array();
194 foreach ($usergroups as $usergroup) {
195 if (!isset($groupgrades[$usergroup->groupid
])) {
196 $groupgrades[$usergroup->groupid
] = array(
197 'sum' => (float)$gradeforuser[$usergroup->userid
],
199 'group' => $usergroup->name
);
201 $groupgrades[$usergroup->groupid
]['sum'] +
= $gradeforuser[$usergroup->userid
];
202 $groupgrades[$usergroup->groupid
]['number'] +
= 1;
206 foreach($groupgrades as $groupid => $groupgrade) {
207 $groupgrades[$groupid]['average'] = $groupgrades[$groupid]['sum'] / $groupgrades[$groupid]['number'];
210 // Sort groupgrades according to average grade, ascending
211 uasort($groupgrades, create_function('$a, $b', 'if($a["average"] == $b["average"]) return 0; return ($a["average"] > $b["average"] ? 1 : -1);'));
213 // How many groups do we have with graded member submissions to show?
214 $numbest = empty($this->config
->showbest
) ?
0 : min($this->config
->showbest
, count($groupgrades));
215 $numworst = empty($this->config
->showworst
) ?
0 : min($this->config
->showworst
, count($groupgrades) - $numbest);
217 // Collect all the group results we are going to use in $best and $worst
218 $remaining = $numbest;
219 $groupgrade = end($groupgrades);
220 while ($remaining--) {
221 $best[key($groupgrades)] = $groupgrade['average'];
222 $groupgrade = prev($groupgrades);
225 $remaining = $numworst;
226 $groupgrade = reset($groupgrades);
227 while ($remaining--) {
228 $worst[key($groupgrades)] = $groupgrade['average'];
229 $groupgrade = next($groupgrades);
233 $gradeformat = intval(empty($this->config
->gradeformat
) ? B_QUIZRESULTS_GRADE_FORMAT_PCT
: $this->config
->gradeformat
);
236 // Don't show header and link to the quiz if we ARE at the quiz...
237 $this->content
->text
.= '<h1><a href="'.$CFG->wwwroot
.'/mod/quiz/view.php?q='.$quizid.'">'.$quiz->name
.'</a></h1>';
240 if ($nameformat = B_QUIZRESULTS_NAME_FORMAT_FULL
) {
241 if (has_capability('moodle/course:managegroups', $context)) {
242 $grouplink = $CFG->wwwroot
.'/group/overview.php?id='.$courseid.'&group=';
243 } else if (has_capability('moodle/course:viewparticipants', $context)) {
244 $grouplink = $CFG->wwwroot
.'/user/index.php?id='.$courseid.'&group=';
252 $this->content
->text
.= '<table class="grades"><caption>';
253 $this->content
->text
.= ($numbest == 1?
get_string('bestgroupgrade', 'block_quiz_results'):get_string('bestgroupgrades', 'block_quiz_results', $numbest));
254 $this->content
->text
.= '</caption><colgroup class="number" /><colgroup class="name" /><colgroup class="grade" /><tbody>';
255 foreach($best as $groupid => $averagegrade) {
256 switch($nameformat) {
257 case B_QUIZRESULTS_NAME_FORMAT_ANON
:
258 case B_QUIZRESULTS_NAME_FORMAT_ID
:
259 $thisname = get_string('group');
262 case B_QUIZRESULTS_NAME_FORMAT_FULL
:
264 $thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';
266 $thisname = $groupgrades[$groupid]['group'];
270 $this->content
->text
.= '<tr><td>'.(++
$rank).'.</td><td>'.$thisname.'</td><td>';
271 switch($gradeformat) {
272 case B_QUIZRESULTS_GRADE_FORMAT_FRA
:
273 $this->content
->text
.= quiz_format_grade($quiz, $averagegrade).'/'.$quiz->grade
;
275 case B_QUIZRESULTS_GRADE_FORMAT_ABS
:
276 $this->content
->text
.= quiz_format_grade($quiz, $averagegrade);
279 case B_QUIZRESULTS_GRADE_FORMAT_PCT
:
280 $this->content
->text
.= round((float)$averagegrade / (float)$quiz->grade
* 100).'%';
283 $this->content
->text
.= '</td></tr>';
285 $this->content
->text
.= '</tbody></table>';
290 $worst = array_reverse($worst, true);
291 $this->content
->text
.= '<table class="grades"><caption>';
292 $this->content
->text
.= ($numworst == 1?
get_string('worstgroupgrade', 'block_quiz_results'):get_string('worstgroupgrades', 'block_quiz_results', $numworst));
293 $this->content
->text
.= '</caption><colgroup class="number" /><colgroup class="name" /><colgroup class="grade" /><tbody>';
294 foreach($worst as $groupid => $averagegrade) {
295 switch($nameformat) {
296 case B_QUIZRESULTS_NAME_FORMAT_ANON
:
297 case B_QUIZRESULTS_NAME_FORMAT_ID
:
298 $thisname = get_string('group');
301 case B_QUIZRESULTS_NAME_FORMAT_FULL
:
302 $thisname = '<a href="'.$CFG->wwwroot
.'/course/group.php?group='.$groupid.'&id='.$courseid.'">'.$groupgrades[$groupid]['group'].'</a>';
305 $this->content
->text
.= '<tr><td>'.(++
$rank).'.</td><td>'.$thisname.'</td><td>';
306 switch($gradeformat) {
307 case B_QUIZRESULTS_GRADE_FORMAT_FRA
:
308 $this->content
->text
.= quiz_format_grade($quiz, $averagegrade).'/'.$quiz->grade
;
310 case B_QUIZRESULTS_GRADE_FORMAT_ABS
:
311 $this->content
->text
.= quiz_format_grade($quiz, $averagegrade);
314 case B_QUIZRESULTS_GRADE_FORMAT_PCT
:
315 $this->content
->text
.= round((float)$averagegrade / (float)$quiz->grade
* 100).'%';
318 $this->content
->text
.= '</td></tr>';
320 $this->content
->text
.= '</tbody></table>';
326 // This is going to be just like no-groups mode, only we 'll filter
327 // out the grades from people not in our group.
329 // Not logged in, so show nothing
330 return $this->content
;
333 $mygroups = groups_get_all_groups($courseid, $USER->id
);
334 if(empty($mygroups)) {
335 // Not member of a group, show nothing
336 return $this->content
;
339 // Get users from the same groups as me.
340 list($grouptest, $params) = $DB->get_in_or_equal(array_keys($mygroups));
341 $mygroupsusers = $DB->get_records_sql_menu(
342 'SELECT DISTINCT userid, 1 FROM {groups_members} WHERE groupid ' . $grouptest,
345 // Filter out the grades belonging to other users, and proceed as if there were no groups
346 foreach ($grades as $key => $grade) {
347 if (!isset($mygroupsusers[$grade->userid
])) {
348 unset($grades[$key]);
352 // No break, fall through to the default case now we have filtered the $grades array.
356 $numbest = empty($this->config
->showbest
) ?
0 : min($this->config
->showbest
, count($grades));
357 $numworst = empty($this->config
->showworst
) ?
0 : min($this->config
->showworst
, count($grades) - $numbest);
359 // Collect all the usernames we are going to need
360 $remaining = $numbest;
361 $grade = end($grades);
362 while($remaining--) {
363 $best[$grade->userid
] = $grade->id
;
364 $grade = prev($grades);
367 $remaining = $numworst;
368 $grade = reset($grades);
369 while($remaining--) {
370 $worst[$grade->userid
] = $grade->id
;
371 $grade = next($grades);
374 if(empty($best) && empty($worst)) {
375 // Nothing to show, for some reason...
376 return $this->content
;
379 // Now grab all the users from the database
380 $userids = array_merge(array_keys($best), array_keys($worst));
381 $users = $DB->get_records_list('user', 'id', $userids, '', 'id, firstname, lastname, idnumber');
385 $gradeformat = intval(empty($this->config
->gradeformat
) ? B_QUIZRESULTS_GRADE_FORMAT_PCT
: $this->config
->gradeformat
);
388 // Don't show header and link to the quiz if we ARE at the quiz...
389 $this->content
->text
.= '<h1><a href="'.$CFG->wwwroot
.'/mod/quiz/view.php?q='.$quizid.'">'.$quiz->name
.'</a></h1>';
394 $this->content
->text
.= '<table class="grades"><caption>';
395 $this->content
->text
.= ($numbest == 1?
get_string('bestgrade', 'block_quiz_results'):get_string('bestgrades', 'block_quiz_results', $numbest));
396 $this->content
->text
.= '</caption><colgroup class="number" /><colgroup class="name" /><colgroup class="grade" /><tbody>';
397 foreach($best as $userid => $gradeid) {
398 switch($nameformat) {
399 case B_QUIZRESULTS_NAME_FORMAT_ID
:
400 $thisname = get_string('user').' '.$users[$userid]->idnumber
;
402 case B_QUIZRESULTS_NAME_FORMAT_ANON
:
403 $thisname = get_string('user');
406 case B_QUIZRESULTS_NAME_FORMAT_FULL
:
407 $thisname = '<a href="'.$CFG->wwwroot
.'/user/view.php?id='.$userid.'&course='.$courseid.'">'.fullname($users[$userid]).'</a>';
410 $this->content
->text
.= '<tr><td>'.(++
$rank).'.</td><td>'.$thisname.'</td><td>';
411 switch($gradeformat) {
412 case B_QUIZRESULTS_GRADE_FORMAT_FRA
:
413 $this->content
->text
.= quiz_format_grade($quiz, $grades[$gradeid]->grade
).'/'.$quiz->grade
;
415 case B_QUIZRESULTS_GRADE_FORMAT_ABS
:
416 $this->content
->text
.= quiz_format_grade($quiz, $grades[$gradeid]->grade
);
419 case B_QUIZRESULTS_GRADE_FORMAT_PCT
:
421 $this->content
->text
.= round((float)$grades[$gradeid]->grade
/ (float)$quiz->grade
* 100).'%';
423 $this->content
->text
.= '--%';
427 $this->content
->text
.= '</td></tr>';
429 $this->content
->text
.= '</tbody></table>';
434 $worst = array_reverse($worst, true);
435 $this->content
->text
.= '<table class="grades"><caption>';
436 $this->content
->text
.= ($numworst == 1?
get_string('worstgrade', 'block_quiz_results'):get_string('worstgrades', 'block_quiz_results', $numworst));
437 $this->content
->text
.= '</caption><colgroup class="number" /><colgroup class="name" /><colgroup class="grade" /><tbody>';
438 foreach($worst as $userid => $gradeid) {
439 switch($nameformat) {
440 case B_QUIZRESULTS_NAME_FORMAT_ID
:
441 $thisname = get_string('user').' '.$users[$userid]->idnumber
;
443 case B_QUIZRESULTS_NAME_FORMAT_ANON
:
444 $thisname = get_string('user');
447 case B_QUIZRESULTS_NAME_FORMAT_FULL
:
448 $thisname = '<a href="'.$CFG->wwwroot
.'/user/view.php?id='.$userid.'&course='.$courseid.'">'.fullname($users[$userid]).'</a>';
451 $this->content
->text
.= '<tr><td>'.(++
$rank).'.</td><td>'.$thisname.'</td><td>';
452 switch($gradeformat) {
453 case B_QUIZRESULTS_GRADE_FORMAT_FRA
:
454 $this->content
->text
.= quiz_format_grade($quiz, $grades[$gradeid]->grade
).'/'.$quiz->grade
;
456 case B_QUIZRESULTS_GRADE_FORMAT_ABS
:
457 $this->content
->text
.= quiz_format_grade($quiz, $grades[$gradeid]->grade
);
460 case B_QUIZRESULTS_GRADE_FORMAT_PCT
:
461 $this->content
->text
.= round((float)$grades[$gradeid]->grade
/ (float)$quiz->grade
* 100).'%';
464 $this->content
->text
.= '</td></tr>';
466 $this->content
->text
.= '</tbody></table>';
471 return $this->content
;
474 function instance_allow_multiple() {