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
/**
18
 * The mod_hvp file storage
19
 *
20
 * @package    mod_hvp
21
 * @copyright  2016 Joubel AS
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace mod_hvp;
26
defined('MOODLE_INTERNAL') || die();
27
 
28
/**
29
 * The mod_hvp file storage class.
30
 *
31
 * @package    mod_hvp
32
 * @since      Moodle 2.7
33
 * @copyright  2016 Joubel AS
34
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class results {
37
 
38
    // Type specific inputs.
39
    protected $contentid;
40
 
41
    // Generic result inputs.
42
    protected $offset, $limit, $orderby, $orderdir, $filters;
43
 
44
    /**
45
     * Start handling results by filtering input parameters.
46
     */
47
    public function __construct() {
48
        $this->filter_input();
49
    }
50
 
51
    /**
52
     * Filter and load input parameters
53
     *
54
     * @throws \coding_exception
55
     */
56
    protected function filter_input() {
57
        // Type specifc.
58
        $this->contentid = optional_param('content_id', 0, PARAM_INT);
59
 
60
        // Used to handle pagination.
61
        $this->offset = optional_param('offset', 0, PARAM_INT);
62
 
63
        // Max number of items to display on one page.
64
        $this->limit = optional_param('limit', 20, PARAM_INT);
65
        if ($this->limit > 100) {
66
            // Avoid wrong usage.
67
            throw new \coding_exception('limit to high');
68
        }
69
 
70
        // Field to order by.
71
        $this->orderby = optional_param('sortBy', 0, PARAM_INT);
72
 
73
        // Direction to order in.
74
        $this->orderdir = optional_param('sortDir', 0, PARAM_INT);
75
 
76
        // List of fields to filter results on.
77
        $this->filters = optional_param_array('filters', array(), PARAM_RAW_TRIMMED);
78
    }
79
 
80
    /**
81
     * Print results data
82
     */
83
    public function print_results() {
84
        global $USER;
85
 
86
        $cm = get_coursemodule_from_instance('hvp', $this->contentid);
87
        if (!$cm) {
88
            \H5PCore::ajaxError('No such content');
89
            http_response_code(404);
90
            return;
91
        }
92
 
93
        // Check permission.
94
        $context = \context_module::instance($cm->id);
95
        $viewownresults = has_capability('mod/hvp:viewresults', $context);
96
        $viewallresults = has_capability('mod/hvp:viewallresults', $context);
97
        if (!$viewownresults && !$viewallresults) {
98
            \H5PCore::ajaxError(get_string('nopermissiontoviewresult', 'hvp'));
99
            http_response_code(403);
100
            return;
101
        }
102
 
103
        // Only get own results if can't view all.
104
        $uid = $viewallresults ? null : (int)$USER->id;
105
        $results = $this->get_results($uid);
106
        $rows = $this->get_human_readable_results($results, $cm->course);
107
 
108
        header('Cache-Control: no-cache');
109
        header('Content-type: application/json');
110
        print json_encode(array(
111
            'num' => $this->get_results_num(),
112
            'rows' => $rows
113
        ));
114
    }
115
 
116
    /**
117
     * Constructs human readable results
118
     *
119
     * @param $results
120
     * @param $course
121
     *
122
     * @return array
123
     */
124
    private function get_human_readable_results($results, $course) {
125
        // Make data readable for humans.
126
        $rows = array();
127
        foreach ($results as $result) {
128
            $userlink = \html_writer::link(
129
                new \moodle_url('/user/view.php', array(
130
                    'id' => $result->id,
131
                    'course' => $course
132
                )),
133
                \fullname($result)
134
            );
135
 
136
            $reviewlink = '—';
137
 
138
            // Check if result has xAPI data.
139
            if ($result->xapiid) {
140
                $reviewlink = \html_writer::link(
141
                    new \moodle_url('/mod/hvp/review.php',
142
                        array(
143
                            'id' => $this->contentid,
144
                            'course' => $course,
145
                            'user' => $result->id
146
                        )
147
                    ),
148
                    get_string('viewreportlabel', 'hvp')
149
                );
150
            } else if ($result->rawgrade !== null) {
151
                $reviewlink = get_string('reportnotsupported', 'hvp');
152
            }
153
 
154
            $rows[] = array(
155
                $userlink,
156
                $result->rawgrade === null ? '—' : (int) $result->rawgrade,
157
                $result->rawgrade === null ? '—' : (int) $result->rawgrademax,
158
                empty($result->timemodified) ? '—' : date('Y/m/d – H:i', $result->timemodified),
159
                $reviewlink
160
            );
161
        }
162
 
163
        return $rows;
164
    }
165
 
166
    /**
167
     * Builds the SQL query required to retrieve results for the given
168
     * interactive content.
169
     *
170
     * @param int $uid Only get results for uid
171
     *
172
     * @throws \coding_exception
173
     * @return array
174
     */
175
    protected function get_results($uid=null) {
176
        // Add extra fields, joins and where for the different result lists.
177
        if ($this->contentid !== 0) {
178
            list($fields, $join, $where, $order, $args) = $this->get_content_sql($uid);
179
        } else {
180
            throw new \coding_exception('missing content_id');
181
        }
182
 
183
        // Xapi join.
184
        $where[] = "x.content_id = ?";
185
        $args[] = $this->contentid;
186
 
187
        // Build where statement.
188
        $where[] = "i.itemtype = 'mod'";
189
        $where[] = "i.itemmodule = 'hvp'";
190
        $where[] = "x.parent_id IS NULL";
191
        $where = 'WHERE ' . implode(' AND ', $where);
192
 
193
        // Order results by the select column and direction.
194
        $order[] = 'g.rawgrade';
195
        $order[] = 'g.rawgrademax';
196
        $order[] = 'g.timemodified';
197
        $orderby = $this->get_order_sql($order);
198
 
199
        // Join on xAPI results.
200
        $join .= ' LEFT JOIN {hvp_xapi_results} x ON g.userid = x.user_id';
201
        $groupby = ' GROUP BY i.id, g.id, u.id, i.iteminstance, x.id';
202
 
203
        // Get from statement.
204
        $from = $this->get_from_sql();
205
 
206
        // Execute query and get results.
207
        return $this->get_sql_results("
208
                SELECT u.id,
209
                       i.id AS gradeitemid,
210
                       g.id AS gradeid,
211
                       {$fields}
212
                       g.rawgrade,
213
                       g.rawgrademax,
214
                       g.timemodified,
215
                       x.id as xapiid
216
                  {$from}
217
                  {$join}
218
                  {$where}
219
                  {$groupby}
220
                  {$orderby}
221
                ", $args,
222
                $this->offset,
223
                $this->limit);
224
    }
225
 
226
    /**
227
     * Build and execute the query needed to tell the number of total results.
228
     * This is used to create pagination.
229
     *
230
     * @return int
231
     */
232
    protected function get_results_num() {
233
        global $DB;
234
 
235
        list(, $join, $where, , $args) = $this->get_content_sql();
236
        $where[] = "i.itemtype = 'mod'";
237
        $where[] = "i.itemmodule = 'hvp'";
238
        $where = 'WHERE ' . implode(' AND ', $where);
239
        $from = $this->get_from_sql();
240
 
241
        return (int) $DB->get_field_sql("SELECT COUNT(i.id) {$from} {$join} {$where}", $args);
242
    }
243
 
244
    /**
245
     * Builds the order part of the SQL query.
246
     *
247
     * @param array $fields Fields allowed to order by
248
     * @throws \coding_exception
249
     * @return string
250
     */
251
    protected function get_order_sql($fields) {
252
        // Make sure selected order field is valid.
253
        if (!isset($fields[$this->orderby])) {
254
            throw new \coding_exception('invalid order field');
255
        }
256
 
257
        // Find selected sortable field.
258
        $field = $fields[$this->orderby];
259
 
260
        if (is_object($field)) {
261
            // Some fields are reverse sorted by default, e.g. text fields.
262
            // This feels more natural for the humans.
263
            if (!empty($field->reverse)) {
264
                $this->orderdir = !$this->orderdir;
265
            }
266
 
267
            $field = $field->name;
268
        }
269
 
270
        $dir = ($this->orderdir ? 'ASC' : 'DESC');
271
        if ($field === 'u.firstname') {
272
            // Order by all user name fields.
273
            $field = implode(" {$dir}, ", self::get_ordered_user_name_fields());
274
        }
275
 
276
        return "ORDER BY {$field} {$dir}";
277
    }
278
 
279
    /**
280
     * Get from part of the SQL query.
281
     *
282
     * @return string
283
     */
284
    protected function get_from_sql() {
285
        return " FROM {grade_items} i LEFT JOIN {grade_grades} g ON i.id = g.itemid LEFT JOIN {user} u ON u.id = g.userid";
286
    }
287
 
288
    /**
289
     * Get all user name fields in display order.
290
     *
291
     * @param string $prefix Optional table prefix to prepend to all fields
292
     * @return array
293
     */
294
    public static function get_ordered_user_name_fields($prefix = 'u.') {
295
        static $ordered;
296
 
297
        if (empty($ordered)) {
298
            $available = \get_all_user_name_fields();
299
            $displayname = \fullname((object)$available);
300
            if (empty($displayname)) {
301
                $ordered = array("{$prefix}firstname", "{$prefix}lastname");
302
            } else {
303
                // Find fields in order.
304
                foreach ($available as $key => $value) {
305
                    $ordered[] = $prefix . $available[$key];
306
                }
307
            }
308
        }
309
 
310
        return $ordered;
311
    }
312
 
313
    /**
314
     * Get the different parts needed to create the SQL for getting results
315
     * belonging to a specifc content.
316
     * (An alternative to this could be getting all the results for a
317
     * specified user.)
318
     *
319
     * @param int $uid Only get users with this id
320
     * @return array $fields, $join, $where, $order, $args
321
     */
322
    protected function get_content_sql($uid=null) {
323
        global $DB;
324
 
325
        $usernamefields = implode(', ', self::get_ordered_user_name_fields());
326
        $fields = " {$usernamefields}, ";
327
        $join = "";
328
        $where = array("i.iteminstance = ?");
329
        $args = array($this->contentid);
330
 
331
        // Only get entries with own user id.
332
        if (isset($uid)) {
333
            array_push($where, "u.id = ?");
334
            array_push($args, $uid);
335
        }
336
 
337
        if (isset($this->filters[0])) {
338
            $keywordswhere = array();
339
 
340
            // Split up keywords using whitespace and comma.
341
            foreach (preg_split("/[\s,]+/", $this->filters[0]) as $keyword) {
342
                // Search all user name fields.
343
                $usernamewhere = array();
344
                foreach (self::get_ordered_user_name_fields() as $usernamefield) {
345
                    $usernamewhere[] = $DB->sql_like($usernamefield, '?', false);
346
                    $args[] = '%' . $keyword . '%';
347
                }
348
 
349
                // Add user name fields where to keywords where.
350
                if (!empty($usernamewhere)) {
351
                    $keywordswhere[] = '(' . implode(' OR ', $usernamewhere) . ')';
352
                }
353
            }
354
 
355
            // Add keywords where to SQL where.
356
            if (!empty($keywordswhere)) {
357
                $where[] = '(' . implode(' AND ', $keywordswhere) . ')';
358
            }
359
        }
360
        $order = array((object) array(
361
            'name' => 'u.firstname',
362
            'reverse' => true
363
        ));
364
 
365
        return array($fields, $join, $where, $order, $args);
366
    }
367
 
368
    /**
369
     * Execute given query and return any results
370
     *
371
     * @param string $query
372
     * @param array $args Used for placeholders
373
     * @return array
374
     */
375
    protected function get_sql_results($query, $args, $limitfrom = 0, $limitnum = 0) {
376
        global $DB;
377
        return $DB->get_records_sql($query, $args, $limitfrom, $limitnum);
378
    }
379
}