Proyectos de Subversion Moodle

Rev

| 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() {
207
        if (has_capability('mod/quiz:regrade', $this->context)) {
208
            $regradebuttonparams = [
209
                'type'  => 'submit',
210
                'class' => 'btn btn-secondary mr-1',
211
                'name'  => 'regrade',
212
                'value' => get_string('regradeselected', 'quiz_overview'),
213
                'data-action' => 'toggle',
214
                'data-togglegroup' => $this->togglegroup,
215
                'data-toggle' => 'action',
216
                'disabled' => true
217
            ];
218
            echo html_writer::empty_tag('input', $regradebuttonparams);
219
        }
220
        parent::submit_buttons();
221
    }
222
 
223
    public function col_sumgrades($attempt) {
224
        if ($attempt->state != quiz_attempt::FINISHED) {
225
            return '-';
226
        }
227
 
228
        $grade = quiz_rescale_grade($attempt->sumgrades, $this->quiz);
229
        if ($this->is_downloading()) {
230
            return $grade;
231
        }
232
 
233
        if (isset($this->regradedqs[$attempt->usageid])) {
234
            $newsumgrade = 0;
235
            $oldsumgrade = 0;
236
            foreach ($this->questions as $question) {
237
                if (isset($this->regradedqs[$attempt->usageid][$question->slot])) {
238
                    $newsumgrade += $this->regradedqs[$attempt->usageid]
239
                            [$question->slot]->newfraction * $question->maxmark;
240
                    $oldsumgrade += $this->regradedqs[$attempt->usageid]
241
                            [$question->slot]->oldfraction * $question->maxmark;
242
                } else {
243
                    $newsumgrade += $this->lateststeps[$attempt->usageid]
244
                            [$question->slot]->fraction * $question->maxmark;
245
                    $oldsumgrade += $this->lateststeps[$attempt->usageid]
246
                            [$question->slot]->fraction * $question->maxmark;
247
                }
248
            }
249
            $newsumgrade = quiz_rescale_grade($newsumgrade, $this->quiz);
250
            $oldsumgrade = quiz_rescale_grade($oldsumgrade, $this->quiz);
251
            $grade = html_writer::tag('del', $oldsumgrade) . '/' .
252
                    html_writer::empty_tag('br') . $newsumgrade;
253
        }
254
        return html_writer::link(new moodle_url('/mod/quiz/review.php',
255
                ['attempt' => $attempt->attempt]), $grade,
256
                ['title' => get_string('reviewattempt', 'quiz')]);
257
    }
258
 
259
    /**
260
     * @param string $colname the name of the column.
261
     * @param stdClass $attempt the row of data - see the SQL in display() in
262
     * mod/quiz/report/overview/report.php to see what fields are present,
263
     * and what they are called.
264
     * @return string the contents of the cell.
265
     */
266
    public function other_cols($colname, $attempt) {
267
        if (!preg_match('/^qsgrade(\d+)$/', $colname, $matches)) {
268
            return parent::other_cols($colname, $attempt);
269
        }
270
        $slot = $matches[1];
271
 
272
        $question = $this->questions[$slot];
273
        if (!isset($this->lateststeps[$attempt->usageid][$slot])) {
274
            return '-';
275
        }
276
 
277
        $stepdata = $this->lateststeps[$attempt->usageid][$slot];
278
        $state = question_state::get($stepdata->state);
279
 
280
        if ($question->maxmark == 0) {
281
            $grade = '-';
282
        } else if (is_null($stepdata->fraction)) {
283
            if ($state == question_state::$needsgrading) {
284
                $grade = get_string('requiresgrading', 'question');
285
            } else {
286
                $grade = '-';
287
            }
288
        } else {
289
            $grade = quiz_rescale_grade(
290
                    $stepdata->fraction * $question->maxmark, $this->quiz, 'question');
291
        }
292
 
293
        if ($this->is_downloading()) {
294
            return $grade;
295
        }
296
 
297
        if (isset($this->regradedqs[$attempt->usageid][$slot])) {
298
            $gradefromdb = $grade;
299
            $newgrade = quiz_rescale_grade(
300
                    $this->regradedqs[$attempt->usageid][$slot]->newfraction * $question->maxmark,
301
                    $this->quiz, 'question');
302
            $oldgrade = quiz_rescale_grade(
303
                    $this->regradedqs[$attempt->usageid][$slot]->oldfraction * $question->maxmark,
304
                    $this->quiz, 'question');
305
 
306
            $grade = html_writer::tag('del', $oldgrade) . '/' .
307
                    html_writer::empty_tag('br') . $newgrade;
308
        }
309
 
310
        return $this->make_review_link($grade, $attempt, $slot);
311
    }
312
 
313
    public function col_regraded($attempt) {
314
        if ($attempt->regraded == '') {
315
            return '';
316
        } else if ($attempt->regraded == 0) {
317
            return get_string('needed', 'quiz_overview');
318
        } else if ($attempt->regraded == 1) {
319
            return get_string('done', 'quiz_overview');
320
        }
321
    }
322
 
323
    protected function update_sql_after_count($fields, $from, $where, $params) {
324
        $fields .= ", COALESCE((
325
                                SELECT MAX(qqr.regraded)
326
                                  FROM {quiz_overview_regrades} qqr
327
                                 WHERE qqr.questionusageid = quiza.uniqueid
328
                          ), -1) AS regraded";
329
        if ($this->options->onlyregraded) {
330
            $where .= " AND COALESCE((
331
                                    SELECT MAX(qqr.regraded)
332
                                      FROM {quiz_overview_regrades} qqr
333
                                     WHERE qqr.questionusageid = quiza.uniqueid
334
                                ), -1) <> -1";
335
        }
336
        return [$fields, $from, $where, $params];
337
    }
338
 
339
    protected function requires_latest_steps_loaded() {
340
        return $this->options->slotmarks;
341
    }
342
 
343
    protected function is_latest_step_column($column) {
344
        if (preg_match('/^qsgrade([0-9]+)/', $column, $matches)) {
345
            return $matches[1];
346
        }
347
        return false;
348
    }
349
 
350
    protected function get_required_latest_state_fields($slot, $alias) {
351
        return "$alias.fraction * $alias.maxmark AS qsgrade$slot";
352
    }
353
 
354
    public function query_db($pagesize, $useinitialsbar = true) {
355
        parent::query_db($pagesize, $useinitialsbar);
356
 
357
        if ($this->options->slotmarks && has_capability('mod/quiz:regrade', $this->context)) {
358
            $this->regradedqs = $this->get_regraded_questions();
359
        }
360
    }
361
 
362
    /**
363
     * Get all the questions in all the attempts being displayed that need regrading.
364
     * @return array A two dimensional array $questionusageid => $slot => $regradeinfo.
365
     */
366
    protected function get_regraded_questions() {
367
        global $DB;
368
 
369
        $qubaids = $this->get_qubaids_condition();
370
        $regradedqs = $DB->get_records_select('quiz_overview_regrades',
371
                'questionusageid ' . $qubaids->usage_id_in(), $qubaids->usage_id_in_params());
372
        return quiz_report_index_by_keys($regradedqs, ['questionusageid', 'slot']);
373
    }
374
}