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
/**
18
 * Data generator.
19
 *
20
 * @package    mod_h5pactivity
21
 * @copyright 2020 Ferran Recio <ferran@moodle.com>
22
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
use mod_h5pactivity\local\manager;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
 
30
/**
31
 * h5pactivity module data generator class.
32
 *
33
 * @package    mod_h5pactivity
34
 * @copyright 2020 Ferran Recio <ferran@moodle.com>
35
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class mod_h5pactivity_generator extends testing_module_generator {
38
 
39
    /**
40
     * Creates new h5pactivity module instance. By default it contains a short
41
     * text file.
42
     *
43
     * @param array|stdClass $record data for module being generated. Requires 'course' key
44
     *     (an id or the full object). Also can have any fields from add module form.
45
     * @param null|array $options general options for course module. Since 2.6 it is
46
     *     possible to omit this argument by merging options into $record
47
     * @return stdClass record from module-defined table with additional field
48
     *     cmid (corresponding id in course_modules table)
49
     */
50
    public function create_instance($record = null, array $options = null): stdClass {
51
        global $CFG, $USER;
52
        // Ensure the record can be modified without affecting calling code.
53
        $record = (object)(array)$record;
54
 
55
        // Fill in optional values if not specified.
56
        if (!isset($record->packagefilepath)) {
57
            $record->packagefilepath = $CFG->dirroot.'/h5p/tests/fixtures/h5ptest.zip';
58
        } else if (strpos($record->packagefilepath, $CFG->dirroot) !== 0) {
59
            $record->packagefilepath = "{$CFG->dirroot}/{$record->packagefilepath}";
60
        }
61
        if (!isset($record->grade)) {
62
            $record->grade = 100;
63
        }
64
        if (!isset($record->displayoptions)) {
65
            $factory = new \core_h5p\factory();
66
            $core = $factory->get_core();
67
            $config = \core_h5p\helper::decode_display_options($core);
68
            $record->displayoptions = \core_h5p\helper::get_display_options($core, $config);
69
        }
70
        if (!isset($record->enabletracking)) {
71
            $record->enabletracking = 1;
72
        }
73
        if (!isset($record->grademethod)) {
74
            $record->grademethod = manager::GRADEHIGHESTATTEMPT;
75
        }
76
        if (!isset($record->reviewmode)) {
77
            $record->reviewmode = manager::REVIEWCOMPLETION;
78
        }
11 efrain 79
        $globaluser = $USER;
80
        if (!empty($record->username)) {
81
            $user = core_user::get_user_by_username($record->username);
82
            $this->set_user($user);
83
        }
1 efrain 84
        // The 'packagefile' value corresponds to the draft file area ID. If not specified, create from packagefilepath.
85
        if (empty($record->packagefile)) {
86
            if (!isloggedin() || isguestuser()) {
87
                throw new coding_exception('H5P activity generator requires a current user');
88
            }
89
            if (!file_exists($record->packagefilepath)) {
90
                throw new coding_exception("File {$record->packagefilepath} does not exist");
91
            }
11 efrain 92
 
1 efrain 93
            $usercontext = context_user::instance($USER->id);
94
 
95
            // Pick a random context id for specified user.
96
            $record->packagefile = file_get_unused_draft_itemid();
97
 
98
            // Add actual file there.
11 efrain 99
            $filerecord = [
100
                'component' => 'user',
101
                'filearea' => 'draft',
102
                'contextid' => $usercontext->id,
103
                'itemid' => $record->packagefile,
104
                'filename' => basename($record->packagefilepath),
105
                'filepath' => '/',
106
                'userid' => $USER->id,
107
            ];
1 efrain 108
            $fs = get_file_storage();
109
            $fs->create_file_from_pathname($filerecord, $record->packagefilepath);
110
        }
11 efrain 111
        $instance = parent::create_instance($record, (array)$options);
112
        $this->set_user($globaluser);
113
        return $instance;
1 efrain 114
    }
115
 
116
    /**
117
     * Creata a fake attempt
118
     * @param stdClass $instance object returned from create_instance() call
119
     * @param stdClass|array $record
120
     * @return stdClass generated object
121
     * @throws coding_exception if function is not implemented by module
122
     */
123
    public function create_content($instance, $record = []) {
124
        global $DB, $USER;
125
 
126
        $currenttime = time();
127
        $cmid = $record['cmid'];
128
        $userid = $record['userid'] ?? $USER->id;
129
        $conditions = ['h5pactivityid' => $instance->id, 'userid' => $userid];
130
        $attemptnum = $DB->count_records('h5pactivity_attempts', $conditions) + 1;
131
        $attempt = (object)[
132
                'h5pactivityid' => $instance->id,
133
                'userid' => $userid,
134
                'timecreated' => $currenttime,
135
                'timemodified' => $currenttime,
136
                'attempt' => $attemptnum,
137
                'rawscore' => 3,
138
                'maxscore' => 5,
139
                'completion' => 1,
140
                'success' => 1,
141
                'scaled' => 0.6,
142
            ];
143
        $attempt->id = $DB->insert_record('h5pactivity_attempts', $attempt);
144
 
145
        // Create 3 diferent tracking results.
146
        $result = (object)[
147
                'attemptid' => $attempt->id,
148
                'subcontent' => '',
149
                'timecreated' => $currenttime,
150
                'interactiontype' => 'compound',
151
                'description' => 'description for '.$userid,
152
                'correctpattern' => '',
153
                'response' => '',
154
                'additionals' => '{"extensions":{"http:\/\/h5p.org\/x-api\/h5p-local-content-id":'.
155
                        $cmid.'},"contextExtensions":{}}',
156
                'rawscore' => 3,
157
                'maxscore' => 5,
158
                'completion' => 1,
159
                'success' => 1,
160
                'scaled' => 0.6,
161
            ];
162
        $DB->insert_record('h5pactivity_attempts_results', $result);
163
 
164
        $result->subcontent = 'bd03477a-90a1-486d-890b-0657d6e80ffd';
165
        $result->interactiontype = 'compound';
166
        $result->response = '0[,]5[,]2[,]3';
167
        $result->additionals = '{"choices":[{"id":"0","description":{"en-US":"Blueberry\n"}},'.
168
                '{"id":"1","description":{"en-US":"Raspberry\n"}},{"id":"5","description":'.
169
                '{"en-US":"Strawberry\n"}},{"id":"2","description":{"en-US":"Cloudberry\n"}},'.
170
                '{"id":"3","description":{"en-US":"Halle Berry\n"}},'.
171
                '{"id":"4","description":{"en-US":"Cocktail cherry\n"}}],'.
172
                '"extensions":{"http:\/\/h5p.org\/x-api\/h5p-local-content-id":'.$cmid.
173
                ',"http:\/\/h5p.org\/x-api\/h5p-subContentId":"'.$result->interactiontype.
174
                '"},"contextExtensions":{}}';
175
        $result->rawscore = 1;
176
        $result->scaled = 0.2;
177
        $DB->insert_record('h5pactivity_attempts_results', $result);
178
 
179
        $result->subcontent = '14fcc986-728b-47f3-915b-'.$userid;
180
        $result->interactiontype = 'matching';
181
        $result->correctpattern = '["0[.]1[,]1[.]0[,]2[.]2"]';
182
        $result->response = '1[.]0[,]0[.]1[,]2[.]2';
183
        $result->additionals = '{"source":[{"id":"0","description":{"en-US":"A berry"}}'.
184
                ',{"id":"1","description":{"en-US":"An orange berry"}},'.
185
                '{"id":"2","description":{"en-US":"A red berry"}}],'.
186
                '"target":[{"id":"0","description":{"en-US":"Cloudberry"}},'.
187
                '{"id":"1","description":{"en-US":"Blueberry"}},'.
188
                '{"id":"2","description":{"en-US":"Redcurrant\n"}}],'.
189
                '"contextExtensions":{}}';
190
        $result->rawscore = 2;
191
        $result->scaled = 0.4;
192
        $DB->insert_record('h5pactivity_attempts_results', $result);
193
 
194
        return $attempt;
195
    }
196
 
197
    /**
198
     * Create a H5P attempt.
199
     *
200
     * This method is user by behat generator.
201
     *
202
     * @param array $data the attempts data array
203
     */
204
    public function create_attempt(array $data): void {
205
        global $DB;
206
 
207
        if (!isset($data['h5pactivityid'])) {
208
            throw new coding_exception('Must specify h5pactivityid when creating a H5P attempt.');
209
        }
210
 
211
        if (!isset($data['userid'])) {
212
            throw new coding_exception('Must specify userid when creating a H5P attempt.');
213
        }
214
 
215
        // Defaults.
216
        $data['attempt'] = $data['attempt'] ?? 1;
217
        $data['rawscore'] = $data['rawscore'] ?? 0;
218
        $data['maxscore'] = $data['maxscore'] ?? 0;
219
        $data['duration'] = $data['duration'] ?? 0;
220
        $data['completion'] = $data['completion'] ?? 1;
221
        $data['success'] = $data['success'] ?? 0;
222
 
223
        $data['attemptid'] = $this->get_attempt_object($data);
224
 
225
        // Check interaction type and create a valid record for it.
226
        $data['interactiontype'] = $data['interactiontype'] ?? 'compound';
227
        $method = 'get_attempt_result_' . str_replace('-', '', $data['interactiontype']);
228
        if (!method_exists($this, $method)) {
229
            throw new Exception("Cannot create a {$data['interactiontype']} interaction statement");
230
        }
231
 
232
        $this->insert_statement($data, $this->$method($data));
233
 
234
        // If the activity has tracking enabled, try to recalculate grades.
235
        $activity = $DB->get_record('h5pactivity', ['id' => $data['h5pactivityid']]);
236
        if ($activity->enabletracking) {
237
            h5pactivity_update_grades($activity, $data['userid']);
238
        }
239
    }
240
 
241
    /**
242
     * Get or create an H5P attempt using the data array.
243
     *
244
     * @param array $attemptinfo the generator provided data
245
     * @return int the attempt id
246
     */
247
    private function get_attempt_object($attemptinfo): int {
248
        global $DB;
249
        $result = $DB->get_record('h5pactivity_attempts', [
250
            'userid' => $attemptinfo['userid'],
251
            'h5pactivityid' => $attemptinfo['h5pactivityid'],
252
            'attempt' => $attemptinfo['attempt'],
253
        ]);
254
        if ($result) {
255
            return $result->id;
256
        }
257
        return $this->new_user_attempt($attemptinfo);
258
    }
259
 
260
    /**
261
     * Creates a user attempt.
262
     *
263
     * @param array $attemptinfo the current attempt information.
264
     * @return int the h5pactivity_attempt ID
265
     */
266
    private function new_user_attempt(array $attemptinfo): int {
267
        global $DB;
268
        $record = (object)[
269
            'h5pactivityid' => $attemptinfo['h5pactivityid'],
270
            'userid' => $attemptinfo['userid'],
271
            'timecreated' => time(),
272
            'timemodified' => time(),
273
            'attempt' => $attemptinfo['attempt'],
274
            'rawscore' => $attemptinfo['rawscore'],
275
            'maxscore' => $attemptinfo['maxscore'],
276
            'duration' => $attemptinfo['duration'],
277
            'completion' => $attemptinfo['completion'],
278
            'success' => $attemptinfo['success'],
279
        ];
280
        if (empty($record->maxscore)) {
281
            $record->scaled = 0;
282
        } else {
283
            $record->scaled = $record->rawscore / $record->maxscore;
284
        }
285
        return $DB->insert_record('h5pactivity_attempts', $record);
286
    }
287
 
288
    /**
289
     * Insert a new statement into an attempt.
290
     *
291
     * If the interaction type is "compound" it will also update the attempt general result.
292
     *
293
     * @param array $attemptinfo the current attempt information
294
     * @param array $statement the statement tracking information
295
     * @return int the h5pactivity_attempt_result ID
296
     */
297
    private function insert_statement(array $attemptinfo, array $statement): int {
298
        global $DB;
299
        $record = $statement + [
300
            'attemptid' => $attemptinfo['attemptid'],
301
            'interactiontype' => $attemptinfo['interactiontype'] ?? 'compound',
302
            'timecreated' => time(),
303
            'rawscore' => $attemptinfo['rawscore'],
304
            'maxscore' => $attemptinfo['maxscore'],
305
            'duration' => $attemptinfo['duration'],
306
            'completion' => $attemptinfo['completion'],
307
            'success' => $attemptinfo['success'],
308
        ];
309
        $result = $DB->insert_record('h5pactivity_attempts_results', $record);
310
        if ($record['interactiontype'] == 'compound') {
311
            $attempt = (object)[
312
                'id' => $attemptinfo['attemptid'],
313
                'rawscore' => $record['rawscore'],
314
                'maxscore' => $record['maxscore'],
315
                'duration' => $record['duration'],
316
                'completion' => $record['completion'],
317
                'success' => $record['success'],
318
            ];
319
            $DB->update_record('h5pactivity_attempts', $attempt);
320
        }
321
        return $result;
322
    }
323
 
324
    /**
325
     * Generates a valid compound tracking result.
326
     *
327
     * @param array $attemptinfo the current attempt information.
328
     * @return array with the required statement data
329
     */
330
    private function get_attempt_result_compound(array $attemptinfo): array {
331
        $additionals = (object)[
332
            "extensions" => (object)[
333
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
334
            ],
335
            "contextExtensions" => (object)[],
336
        ];
337
 
338
        return [
339
            'subcontent' => '',
340
            'description' => '',
341
            'correctpattern' => '',
342
            'response' => '',
343
            'additionals' => json_encode($additionals),
344
        ];
345
    }
346
 
347
    /**
348
     * Generates a valid choice tracking result.
349
     *
350
     * @param array $attemptinfo the current attempt information.
351
     * @return array with the required statement data
352
     */
353
    private function get_attempt_result_choice(array $attemptinfo): array {
354
 
355
        $response = ($attemptinfo['rawscore']) ? '1[,]0' : '2[,]3';
356
 
357
        $additionals = (object)[
358
            "choices" => [
359
                (object)[
360
                    "id" => "3",
361
                    "description" => (object)[
362
                        "en-US" => "Another wrong answer\n",
363
                    ],
364
                ],
365
                (object)[
366
                    "id" => "2",
367
                    "description" => (object)[
368
                        "en-US" => "Wrong answer\n",
369
                    ],
370
                ],
371
                (object)[
372
                    "id" => "1",
373
                    "description" => (object)[
374
                        "en-US" => "This is also a correct answer\n",
375
                    ],
376
                ],
377
                (object)[
378
                    "id" => "0",
379
                    "description" => (object)[
380
                        "en-US" => "This is a correct answer\n",
381
                    ],
382
                ],
383
            ],
384
            "extensions" => (object)[
385
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
386
                "http://h5p.org/x-api/h5p-subContentId" => "4367a919-ec47-43c9-b521-c22d9c0c0d8d",
387
            ],
388
            "contextExtensions" => (object)[],
389
        ];
390
 
391
        return [
392
            'subcontent' => microtime(),
393
            'description' => 'Select the correct answers',
394
            'correctpattern' => '["1[,]0"]',
395
            'response' => $response,
396
            'additionals' => json_encode($additionals),
397
        ];
398
    }
399
 
400
    /**
401
     * Generates a valid matching tracking result.
402
     *
403
     * @param array $attemptinfo the current attempt information.
404
     * @return array with the required statement data
405
     */
406
    private function get_attempt_result_matching(array $attemptinfo): array {
407
 
408
        $response = ($attemptinfo['rawscore']) ? '0[.]0[,]1[.]1' : '1[.]0[,]0[.]1';
409
 
410
        $additionals = (object)[
411
            "source" => [
412
                (object)[
413
                    "id" => "0",
414
                    "description" => (object)[
415
                        "en-US" => "Drop item A\n",
416
                    ],
417
                ],
418
                (object)[
419
                    "id" => "1",
420
                    "description" => (object)[
421
                        "en-US" => "Drop item B\n",
422
                    ],
423
                ],
424
            ],
425
            "target" => [
426
                (object)[
427
                    "id" => "0",
428
                    "description" => (object)[
429
                        "en-US" => "Drop zone A\n",
430
                    ],
431
                ],
432
                (object)[
433
                    "id" => "1",
434
                    "description" => (object)[
435
                        "en-US" => "Drop zone B\n",
436
                    ],
437
                ],
438
            ],
439
            "extensions" => [
440
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
441
                "http://h5p.org/x-api/h5p-subContentId" => "682f1c74-c819-4e9d-8c36-12d9dc5fcdbc",
442
            ],
443
            "contextExtensions" => (object)[],
444
        ];
445
 
446
        return [
447
            'subcontent' => microtime(),
448
            'description' => 'Drag and Drop example 1',
449
            'correctpattern' => '["0[.]0[,]1[.]1"]',
450
            'response' => $response,
451
            'additionals' => json_encode($additionals),
452
        ];
453
    }
454
 
455
    /**
456
     * Generates a valid fill-in tracking result.
457
     *
458
     * @param array $attemptinfo the current attempt information.
459
     * @return array with the required statement data
460
     */
461
    private function get_attempt_result_fillin(array $attemptinfo): array {
462
 
463
        $response = ($attemptinfo['rawscore']) ? 'first[,]second' : 'something[,]else';
464
 
465
        $additionals = (object)[
466
            "extensions" => (object)[
467
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
468
                "http://h5p.org/x-api/h5p-subContentId" => "1a3febd5-7edc-4336-8112-12756b945b62",
469
                "https://h5p.org/x-api/case-sensitivity" => true,
470
                "https://h5p.org/x-api/alternatives" => [
471
                    ["first"],
472
                    ["second"],
473
                ],
474
            ],
475
            "contextExtensions" => (object)[
476
                "https://h5p.org/x-api/h5p-reporting-version" => "1.1.0",
477
            ],
478
        ];
479
 
480
        return [
481
            'subcontent' => microtime(),
482
            'description' => '<p>This an example of missing word text.</p>
483
 
484
                    <p>The first answer if "first": the first answer is __________.</p>
485
 
486
                    <p>The second is second is "second": the secons answer is __________</p>',
487
            'correctpattern' => '["{case_matters=true}first[,]second"]',
488
            'response' => $response,
489
            'additionals' => json_encode($additionals),
490
        ];
491
    }
492
 
493
    /**
494
     * Generates a valid true-false tracking result.
495
     *
496
     * @param array $attemptinfo the current attempt information.
497
     * @return array with the required statement data
498
     */
499
    private function get_attempt_result_truefalse(array $attemptinfo): array {
500
 
501
        $response = ($attemptinfo['rawscore']) ? 'true' : 'false';
502
 
503
        $additionals = (object)[
504
            "extensions" => (object)[
505
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
506
                "http://h5p.org/x-api/h5p-subContentId" => "5de9fb1e-aa03-4c9a-8cf0-3870b3f012ca",
507
            ],
508
            "contextExtensions" => (object)[],
509
        ];
510
 
511
        return [
512
            'subcontent' => microtime(),
513
            'description' => 'The correct answer is true.',
514
            'correctpattern' => '["true"]',
515
            'response' => $response,
516
            'additionals' => json_encode($additionals),
517
        ];
518
    }
519
 
520
    /**
521
     * Generates a valid long-fill-in tracking result.
522
     *
523
     * @param array $attemptinfo the current attempt information.
524
     * @return array with the required statement data
525
     */
526
    private function get_attempt_result_longfillin(array $attemptinfo): array {
527
 
528
        $response = ($attemptinfo['rawscore']) ? 'The Hobbit is book' : 'Who cares?';
529
 
530
        $additionals = (object)[
531
            "extensions" => (object)[
532
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
533
                "http://h5p.org/x-api/h5p-subContentId" => "5de9fb1e-aa03-4c9a-8cf0-3870b3f012ca",
534
            ],
535
            "contextExtensions" => (object)[],
536
        ];
537
 
538
        return [
539
            'subcontent' => microtime(),
540
            'description' => '<p>Please describe the novel The Hobbit',
541
            'correctpattern' => '',
542
            'response' => $response,
543
            'additionals' => json_encode($additionals),
544
        ];
545
    }
546
 
547
    /**
548
     * Generates a valid sequencing tracking result.
549
     *
550
     * @param array $attemptinfo the current attempt information.
551
     * @return array with the required statement data
552
     */
553
    private function get_attempt_result_sequencing(array $attemptinfo): array {
554
 
555
        $response = ($attemptinfo['rawscore']) ? 'true' : 'false';
556
 
557
        $additionals = (object)[
558
            "extensions" => (object)[
559
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
560
                "http://h5p.org/x-api/h5p-subContentId" => "5de9fb1e-aa03-4c9a-8cf0-3870b3f012ca",
561
            ],
562
            "contextExtensions" => (object)[],
563
        ];
564
 
565
        return [
566
            'subcontent' => microtime(),
567
            'description' => 'The correct answer is true.',
568
            'correctpattern' => '["{case_matters=true}first[,]second"]',
569
            'response' => $response,
570
            'additionals' => json_encode($additionals),
571
        ];
572
    }
573
 
574
    /**
575
     * Generates a valid other tracking result.
576
     *
577
     * @param array $attemptinfo the current attempt information.
578
     * @return array with the required statement data
579
     */
580
    private function get_attempt_result_other(array $attemptinfo): array {
581
 
582
        $additionals = (object)[
583
            "extensions" => (object)[
584
                "http://h5p.org/x-api/h5p-local-content-id" => 1,
585
            ],
586
            "contextExtensions" => (object)[],
587
        ];
588
 
589
        return [
590
            'subcontent' => microtime(),
591
            'description' => '',
592
            'correctpattern' => '',
593
            'response' => '',
594
            'additionals' => json_encode($additionals),
595
        ];
596
    }
597
}