Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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.
13
//
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/>.
16
 
17
use mod_quiz\local\reports\attempts_report_table;
18
use mod_quiz\quiz_attempt;
19
 
20
/**
21
 * This is a table subclass for displaying the quiz grades report.
22
 *
23
 * @copyright 2008 Jamie Pratt
24
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
class quiz_overview_table extends attempts_report_table {
27
 
28
    /** @var array used to store information about which questoins have been regraded. */
29
    protected $regradedqs = [];
30
 
31
    /**
32
     * Constructor
33
     * @param stdClass $quiz
34
     * @param context $context
35
     * @param string $qmsubselect
36
     * @param quiz_overview_options $options
37
     * @param \core\dml\sql_join $groupstudentsjoins
38
     * @param \core\dml\sql_join $studentsjoins
39
     * @param array $questions
40
     * @param moodle_url $reporturl
41
     */
42
    public function __construct($quiz, $context, $qmsubselect,
43
            quiz_overview_options $options, \core\dml\sql_join $groupstudentsjoins,
44
            \core\dml\sql_join $studentsjoins, $questions, $reporturl) {
45
        parent::__construct('mod-quiz-report-overview-report', $quiz , $context,
46
                $qmsubselect, $options, $groupstudentsjoins, $studentsjoins, $questions, $reporturl);
47
    }
48
 
49
    public function build_table() {
50
        global $DB;
51
 
52
        if (!$this->rawdata) {
53
            return;
54
        }
55
 
56
        $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetime'));
57
        parent::build_table();
58
 
59
        // End of adding the data from attempts. Now add averages at bottom.
60
        $this->add_separator();
61
 
62
        if (!empty($this->groupstudentsjoins->joins)) {
63
            $hasgroupstudents = $DB->record_exists_sql("
64
                    SELECT 1
65
                      FROM {user} u
66
                    {$this->groupstudentsjoins->joins}
67
                     WHERE {$this->groupstudentsjoins->wheres}
68
                    ", $this->groupstudentsjoins->params);
69
            if ($hasgroupstudents) {
70
                $this->add_average_row(get_string('groupavg', 'grades'), $this->groupstudentsjoins);
71
            }
72
        }
73
 
74
        if (!empty($this->studentsjoins->joins)) {
75
            $hasstudents = $DB->record_exists_sql("
76
                    SELECT 1
77
                      FROM {user} u
78
                    {$this->studentsjoins->joins}
79
                     WHERE {$this->studentsjoins->wheres}
80
                    " , $this->studentsjoins->params);
81
            if ($hasstudents) {
82
                $this->add_average_row(get_string('overallaverage', 'grades'), $this->studentsjoins);
83
            }
84
        }
85
    }
86
 
87
    /**
88
     * Calculate the average overall and question scores for a set of attempts at the quiz.
89
     *
90
     * @param string $label the title ot use for this row.
91
     * @param \core\dml\sql_join $usersjoins to indicate a set of users.
92
     * @return array of table cells that make up the average row.
93
     */
94
    public function compute_average_row($label, \core\dml\sql_join $usersjoins) {
95
        global $DB;
96
 
97
        list($fields, $from, $where, $params) = $this->base_sql($usersjoins);
98
        $record = $DB->get_record_sql("
99
                SELECT AVG(quizaouter.sumgrades) AS grade, COUNT(quizaouter.sumgrades) AS numaveraged
100
                  FROM {quiz_attempts} quizaouter
101
                  JOIN (
102
                       SELECT DISTINCT quiza.id
103
                         FROM $from
104
                        WHERE $where
105
                       ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id
106
                ", $params);
107
        $record->grade = quiz_rescale_grade($record->grade, $this->quiz, false);
108
        if ($this->is_downloading()) {
109
            $namekey = 'lastname';
110
        } else {
111
            $namekey = 'fullname';
112
        }
113
        $averagerow = [
114
            $namekey       => $label,
115
            'sumgrades'    => $this->format_average($record),
116
            'feedbacktext' => strip_tags(quiz_report_feedback_for_grade(
117
                                         $record->grade, $this->quiz->id, $this->context))
118
        ];
119
 
120
        if ($this->options->slotmarks) {
121
            $dm = new question_engine_data_mapper();
122
            $qubaids = new qubaid_join("{quiz_attempts} quizaouter
123
                  JOIN (
124
                       SELECT DISTINCT quiza.id
125
                         FROM $from
126
                        WHERE $where
127
                       ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id",
128
                    'quizaouter.uniqueid', '1 = 1', $params);
129
            $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions));
130
 
131
            $averagerow += $this->format_average_grade_for_questions($avggradebyq);
132
        }
133
 
134
        return $averagerow;
135
    }
136
 
137
    /**
138
     * Add an average grade row for a set of users.
139
     *
140
     * @param string $label the title ot use for this row.
141
     * @param \core\dml\sql_join $usersjoins (joins, wheres, params) for the users to average over.
142
     */
143
    protected function add_average_row($label, \core\dml\sql_join $usersjoins) {
144
        $averagerow = $this->compute_average_row($label, $usersjoins);
145
        $this->add_data_keyed($averagerow);
146
    }
147
 
148
    /**
149
     * Helper userd by {@link add_average_row()}.
150
     * @param array $gradeaverages the raw grades.
151
     * @return array the (partial) row of data.
152
     */
153
    protected function format_average_grade_for_questions($gradeaverages) {
154
        $row = [];
155
 
156
        if (!$gradeaverages) {
157
            $gradeaverages = [];
158
        }
159
 
160
        foreach ($this->questions as $question) {
161
            if (isset($gradeaverages[$question->slot]) && $question->maxmark > 0) {
162
                $record = $gradeaverages[$question->slot];
163
                $record->grade = quiz_rescale_grade(
164
                        $record->averagefraction * $question->maxmark, $this->quiz, false);
165
 
166
            } else {
167
                $record = new stdClass();
168
                $record->grade = null;
169
                $record->numaveraged = 0;
170
            }
171
 
172
            $row['qsgrade' . $question->slot] = $this->format_average($record, true);
173
        }
174
 
175
        return $row;
176
    }
177
 
178
    /**
179
     * Format an entry in an average row.
180
     * @param stdClass $record with fields grade and numaveraged.
181
     * @param bool $question true if this is a question score, false if it is an overall score.
182
     * @return string HTML fragment for an average score (with number of things included in the average).
183
     */
184
    protected function format_average($record, $question = false) {
185
        if (is_null($record->grade)) {
186
            $average = '-';
187
        } else if ($question) {
188
            $average = quiz_format_question_grade($this->quiz, $record->grade);
189
        } else {
190
            $average = quiz_format_grade($this->quiz, $record->grade);
191
        }
192
 
193
        if ($this->download) {
194
            return $average;
195
        } else if (is_null($record->numaveraged) || $record->numaveraged == 0) {
196
            return html_writer::tag('span', html_writer::tag('span',
197
                    $average, ['class' => 'average']), ['class' => 'avgcell']);
198
        } else {
199
            return html_writer::tag('span', html_writer::tag('span',
200
                    $average, ['class' => 'average']) . ' ' . html_writer::tag('span',
201
                    '(' . $record->numaveraged . ')', ['class' => 'count']),
202
                    ['class' => 'avgcell']);
203
        }
204
    }
205
 
206
    protected function submit_buttons() {
1441 ariadna 207
        global $PAGE, $OUTPUT;
208
        parent::submit_buttons();
1 efrain 209
        if (has_capability('mod/quiz:regrade', $this->context)) {
1441 ariadna 210
            $questionslots = [];
211
            foreach ($this->questions as $slot => $question) {
212
                $questionslots[] = [
213
                    'slot' => $slot,
214
                    'name' => get_string('questionno', 'question', $question->number),
215
                ];
216
            }
217
            echo html_writer::empty_tag('input', [
218
                'type' => 'button',
219
                'id' => 'regradeattempts',
220
                'class' => 'btn btn-secondary me-1',
221
                'name' => 'regradeattempts',
222
                'value' => get_string('regrade_attempts', 'quiz_overview'),
223
                'data-slots' => json_encode($questionslots),
224
                // There is currently not a good way to render a help icon in a template, so put the required HTML here.
225
                'data-help-icon' => $OUTPUT->help_icon('regrade', 'quiz_overview'),
226
            ]);
227
            $PAGE->requires->js_call_amd('quiz_overview/regrade_modal', 'init');
1 efrain 228
        }
229
    }
230
 
231
    public function col_sumgrades($attempt) {
232
        if ($attempt->state != quiz_attempt::FINISHED) {
233
            return '-';
234
        }
235
 
236
        $grade = quiz_rescale_grade($attempt->sumgrades, $this->quiz);
237
        if ($this->is_downloading()) {
238
            return $grade;
239
        }
240
 
241
        if (isset($this->regradedqs[$attempt->usageid])) {
242
            $newsumgrade = 0;
243
            $oldsumgrade = 0;
244
            foreach ($this->questions as $question) {
245
                if (isset($this->regradedqs[$attempt->usageid][$question->slot])) {
246
                    $newsumgrade += $this->regradedqs[$attempt->usageid]
247
                            [$question->slot]->newfraction * $question->maxmark;
248
                    $oldsumgrade += $this->regradedqs[$attempt->usageid]
249
                            [$question->slot]->oldfraction * $question->maxmark;
250
                } else {
251
                    $newsumgrade += $this->lateststeps[$attempt->usageid]
252
                            [$question->slot]->fraction * $question->maxmark;
253
                    $oldsumgrade += $this->lateststeps[$attempt->usageid]
254
                            [$question->slot]->fraction * $question->maxmark;
255
                }
256
            }
257
            $newsumgrade = quiz_rescale_grade($newsumgrade, $this->quiz);
258
            $oldsumgrade = quiz_rescale_grade($oldsumgrade, $this->quiz);
259
            $grade = html_writer::tag('del', $oldsumgrade) . '/' .
260
                    html_writer::empty_tag('br') . $newsumgrade;
261
        }
262
        return html_writer::link(new moodle_url('/mod/quiz/review.php',
263
                ['attempt' => $attempt->attempt]), $grade,
264
                ['title' => get_string('reviewattempt', 'quiz')]);
265
    }
266
 
267
    /**
268
     * @param string $colname the name of the column.
269
     * @param stdClass $attempt the row of data - see the SQL in display() in
270
     * mod/quiz/report/overview/report.php to see what fields are present,
271
     * and what they are called.
272
     * @return string the contents of the cell.
273
     */
274
    public function other_cols($colname, $attempt) {
275
        if (!preg_match('/^qsgrade(\d+)$/', $colname, $matches)) {
276
            return parent::other_cols($colname, $attempt);
277
        }
278
        $slot = $matches[1];
279
 
280
        $question = $this->questions[$slot];
281
        if (!isset($this->lateststeps[$attempt->usageid][$slot])) {
282
            return '-';
283
        }
284
 
285
        $stepdata = $this->lateststeps[$attempt->usageid][$slot];
286
        $state = question_state::get($stepdata->state);
287
 
288
        if ($question->maxmark == 0) {
289
            $grade = '-';
290
        } else if (is_null($stepdata->fraction)) {
291
            if ($state == question_state::$needsgrading) {
292
                $grade = get_string('requiresgrading', 'question');
293
            } else {
294
                $grade = '-';
295
            }
296
        } else {
297
            $grade = quiz_rescale_grade(
298
                    $stepdata->fraction * $question->maxmark, $this->quiz, 'question');
299
        }
300
 
301
        if ($this->is_downloading()) {
302
            return $grade;
303
        }
304
 
305
        if (isset($this->regradedqs[$attempt->usageid][$slot])) {
306
            $gradefromdb = $grade;
307
            $newgrade = quiz_rescale_grade(
308
                    $this->regradedqs[$attempt->usageid][$slot]->newfraction * $question->maxmark,
309
                    $this->quiz, 'question');
310
            $oldgrade = quiz_rescale_grade(
311
                    $this->regradedqs[$attempt->usageid][$slot]->oldfraction * $question->maxmark,
312
                    $this->quiz, 'question');
313
 
314
            $grade = html_writer::tag('del', $oldgrade) . '/' .
315
                    html_writer::empty_tag('br') . $newgrade;
316
        }
317
 
318
        return $this->make_review_link($grade, $attempt, $slot);
319
    }
320
 
321
    public function col_regraded($attempt) {
322
        if ($attempt->regraded == '') {
323
            return '';
324
        } else if ($attempt->regraded == 0) {
325
            return get_string('needed', 'quiz_overview');
326
        } else if ($attempt->regraded == 1) {
327
            return get_string('done', 'quiz_overview');
328
        }
329
    }
330
 
331
    protected function update_sql_after_count($fields, $from, $where, $params) {
332
        $fields .= ", COALESCE((
333
                                SELECT MAX(qqr.regraded)
334
                                  FROM {quiz_overview_regrades} qqr
335
                                 WHERE qqr.questionusageid = quiza.uniqueid
336
                          ), -1) AS regraded";
337
        if ($this->options->onlyregraded) {
338
            $where .= " AND COALESCE((
339
                                    SELECT MAX(qqr.regraded)
340
                                      FROM {quiz_overview_regrades} qqr
341
                                     WHERE qqr.questionusageid = quiza.uniqueid
342
                                ), -1) <> -1";
343
        }
344
        return [$fields, $from, $where, $params];
345
    }
346
 
347
    protected function requires_latest_steps_loaded() {
348
        return $this->options->slotmarks;
349
    }
350
 
351
    protected function is_latest_step_column($column) {
352
        if (preg_match('/^qsgrade([0-9]+)/', $column, $matches)) {
353
            return $matches[1];
354
        }
355
        return false;
356
    }
357
 
358
    protected function get_required_latest_state_fields($slot, $alias) {
359
        return "$alias.fraction * $alias.maxmark AS qsgrade$slot";
360
    }
361
 
362
    public function query_db($pagesize, $useinitialsbar = true) {
363
        parent::query_db($pagesize, $useinitialsbar);
364
 
365
        if ($this->options->slotmarks && has_capability('mod/quiz:regrade', $this->context)) {
366
            $this->regradedqs = $this->get_regraded_questions();
367
        }
368
    }
369
 
370
    /**
371
     * Get all the questions in all the attempts being displayed that need regrading.
372
     * @return array A two dimensional array $questionusageid => $slot => $regradeinfo.
373
     */
374
    protected function get_regraded_questions() {
375
        global $DB;
376
 
377
        $qubaids = $this->get_qubaids_condition();
378
        $regradedqs = $DB->get_records_select('quiz_overview_regrades',
379
                'questionusageid ' . $qubaids->usage_id_in(), $qubaids->usage_id_in_params());
380
        return quiz_report_index_by_keys($regradedqs, ['questionusageid', 'slot']);
381
    }
382
}