Merge branch 'MDL-38487_23' of git://github.com/timhunt/moodle into MOODLE_23_STABLE
[moodle.git] / blocks / quiz_results / block_quiz_results.php
blob7ea73f0cf5430d67ff7c0e35bd8895fa16e9d21e
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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/>.
17 /**
18 * Classes to enforce the various access rules that can apply to a quiz.
20 * @package block
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');
32 /**
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.
38 * @package block
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 {
51 function init() {
52 $this->title = get_string('pluginname', 'block_quiz_results');
55 function applicable_formats() {
56 return array('course' => true, 'mod-quiz' => true);
59 /**
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)) {
66 return 0;
68 $parentcontext = get_context_instance_by_id($this->instance->parentcontextid);
69 if ($parentcontext->contextlevel != CONTEXT_MODULE) {
70 return 0;
72 $cm = get_coursemodule_from_id('quiz', $parentcontext->instanceid);
73 if (!$cm) {
74 return 0;
76 return $cm->instance;
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;
103 $quizid = $quiz->id;
104 $courseid = $this->page->course->id;
105 $inquiz = true;
106 } else if (!empty($this->config->quizid)) {
107 $quizid = $this->config->quizid;
108 $quiz = $DB->get_record('quiz', array('id' => $quizid));
109 if (empty($quiz)) {
110 $this->content->text = get_string('error_emptyquizrecord', 'block_quiz_results');
111 return $this->content;
113 $courseid = $quiz->course;
114 $inquiz = false;
115 } else {
116 $quizid = 0;
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)) {
133 // No grades, sorry
134 // The block will hide itself in this case
135 return $this->content;
138 $groupmode = NOGROUPS;
139 $best = array();
140 $worst = array();
142 if (!empty($this->config->nameformat)) {
143 $nameformat = $this->config->nameformat;
144 } else {
145 $nameformat = B_QUIZRESULTS_NAME_FORMAT_FULL;
148 if (!empty($this->config->usegroups)) {
149 if ($inquiz) {
150 $cm = $this->page->cm;
151 $context = $this->page->context;
152 } else {
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) {
165 case VISIBLEGROUPS:
166 // Display group-mode results
167 $groups = groups_get_all_groups($courseid);
169 if(empty($groups)) {
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
176 $userids = array();
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
188 FROM {groups} g
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],
198 'number' => 1,
199 'group' => $usergroup->name);
200 } else {
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);
232 // Ready for output!
233 $gradeformat = intval(empty($this->config->gradeformat) ? B_QUIZRESULTS_GRADE_FORMAT_PCT : $this->config->gradeformat);
235 if (!$inquiz) {
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.'&amp;group=';
243 } else if (has_capability('moodle/course:viewparticipants', $context)) {
244 $grouplink = $CFG->wwwroot.'/user/index.php?id='.$courseid.'&amp;group=';
245 } else {
246 $grouplink = '';
250 $rank = 0;
251 if(!empty($best)) {
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');
260 break;
261 default:
262 case B_QUIZRESULTS_NAME_FORMAT_FULL:
263 if ($grouplink) {
264 $thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';
265 } else {
266 $thisname = $groupgrades[$groupid]['group'];
268 break;
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;
274 break;
275 case B_QUIZRESULTS_GRADE_FORMAT_ABS:
276 $this->content->text .= quiz_format_grade($quiz, $averagegrade);
277 break;
278 default:
279 case B_QUIZRESULTS_GRADE_FORMAT_PCT:
280 $this->content->text .= round((float)$averagegrade / (float)$quiz->grade * 100).'%';
281 break;
283 $this->content->text .= '</td></tr>';
285 $this->content->text .= '</tbody></table>';
288 $rank = 0;
289 if(!empty($worst)) {
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');
299 break;
300 default:
301 case B_QUIZRESULTS_NAME_FORMAT_FULL:
302 $thisname = '<a href="'.$CFG->wwwroot.'/course/group.php?group='.$groupid.'&amp;id='.$courseid.'">'.$groupgrades[$groupid]['group'].'</a>';
303 break;
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;
309 break;
310 case B_QUIZRESULTS_GRADE_FORMAT_ABS:
311 $this->content->text .= quiz_format_grade($quiz, $averagegrade);
312 break;
313 default:
314 case B_QUIZRESULTS_GRADE_FORMAT_PCT:
315 $this->content->text .= round((float)$averagegrade / (float)$quiz->grade * 100).'%';
316 break;
318 $this->content->text .= '</td></tr>';
320 $this->content->text .= '</tbody></table>';
322 break;
325 case SEPARATEGROUPS:
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.
328 if (!isloggedin()) {
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,
343 $params);
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.
353 default:
354 case NOGROUPS:
355 // Single user mode
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');
383 // Ready for output!
385 $gradeformat = intval(empty($this->config->gradeformat) ? B_QUIZRESULTS_GRADE_FORMAT_PCT : $this->config->gradeformat);
387 if(!$inquiz) {
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>';
392 $rank = 0;
393 if(!empty($best)) {
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;
401 break;
402 case B_QUIZRESULTS_NAME_FORMAT_ANON:
403 $thisname = get_string('user');
404 break;
405 default:
406 case B_QUIZRESULTS_NAME_FORMAT_FULL:
407 $thisname = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userid.'&amp;course='.$courseid.'">'.fullname($users[$userid]).'</a>';
408 break;
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;
414 break;
415 case B_QUIZRESULTS_GRADE_FORMAT_ABS:
416 $this->content->text .= quiz_format_grade($quiz, $grades[$gradeid]->grade);
417 break;
418 default:
419 case B_QUIZRESULTS_GRADE_FORMAT_PCT:
420 if ($quiz->grade) {
421 $this->content->text .= round((float)$grades[$gradeid]->grade / (float)$quiz->grade * 100).'%';
422 } else {
423 $this->content->text .= '--%';
425 break;
427 $this->content->text .= '</td></tr>';
429 $this->content->text .= '</tbody></table>';
432 $rank = 0;
433 if(!empty($worst)) {
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;
442 break;
443 case B_QUIZRESULTS_NAME_FORMAT_ANON:
444 $thisname = get_string('user');
445 break;
446 default:
447 case B_QUIZRESULTS_NAME_FORMAT_FULL:
448 $thisname = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userid.'&amp;course='.$courseid.'">'.fullname($users[$userid]).'</a>';
449 break;
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;
455 break;
456 case B_QUIZRESULTS_GRADE_FORMAT_ABS:
457 $this->content->text .= quiz_format_grade($quiz, $grades[$gradeid]->grade);
458 break;
459 default:
460 case B_QUIZRESULTS_GRADE_FORMAT_PCT:
461 $this->content->text .= round((float)$grades[$gradeid]->grade / (float)$quiz->grade * 100).'%';
462 break;
464 $this->content->text .= '</td></tr>';
466 $this->content->text .= '</tbody></table>';
468 break;
471 return $this->content;
474 function instance_allow_multiple() {
475 return true;