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 core_h5p\local\library\autoloader;
18
use core_h5p\core;
19
use core_h5p\player;
20
use core_h5p\factory;
21
use core_xapi\local\statement\item_activity;
22
 
23
/**
24
 * Generator for the core_h5p subsystem.
25
 *
26
 * @package    core_h5p
27
 * @category   test
28
 * @copyright  2019 Victor Deniz <victor@moodle.com>
29
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class core_h5p_generator extends \component_generator_base {
32
 
33
    /** Url pointing to webservice plugin file. */
34
    public const WSPLUGINFILE = 0;
35
    /** Url pointing to token plugin file. */
36
    public const TOKENPLUGINFILE = 1;
37
    /** Url pointing to plugin file. */
38
    public const PLUGINFILE = 2;
39
 
40
    /**
41
     * Convenience function to create a file.
42
     *
43
     * @param  string $file path to a file.
44
     * @param  string $content file content.
45
     */
46
    public function create_file(string $file, string $content=''): void {
47
        $handle = fopen($file, 'w+');
48
        // File content is not relevant.
49
        if (empty($content)) {
50
            $content = hash("md5", $file);
51
        }
52
        fwrite($handle, $content);
53
        fclose($handle);
54
    }
55
 
56
    /**
57
     * Creates the file record. Currently used for the cache tests.
58
     *
59
     * @param string $type    Either 'scripts' or 'styles'.
60
     * @param string $path    Path to the file in the file system.
61
     * @param string $version Not really needed at the moment.
62
     */
63
    protected function add_libfile_to_array(string $type, string $path, string $version, &$files): void {
64
        $files[$type][] = (object)[
65
            'path' => $path,
66
            'version' => "?ver=$version"
67
        ];
68
    }
69
 
70
    /**
71
     * Create the necessary files and return an array structure for a library.
72
     *
73
     * @param  string $uploaddirectory Base directory for the library.
74
     * @param  int    $libraryid       Library id.
75
     * @param  string $machinename     Name for this library.
76
     * @param  int    $majorversion    Major version (any number will do).
77
     * @param  int    $minorversion    Minor version (any number will do).
78
     * @param  array  $langs           Languages to be included into the library.
79
     * @return array A list of library data and files that the core API will understand.
80
     */
81
    public function create_library(string $uploaddirectory, int $libraryid, string $machinename, int $majorversion,
82
            int $minorversion, ?array $langs = []): array {
83
        // Array $files used in the cache tests.
84
        $files = ['scripts' => [], 'styles' => [], 'language' => []];
85
 
86
        check_dir_exists($uploaddirectory . '/' . 'scripts');
87
        check_dir_exists($uploaddirectory . '/' . 'styles');
88
        if (!empty($langs)) {
89
            check_dir_exists($uploaddirectory . '/' . 'language');
90
        }
91
 
92
        $jsonfile = $uploaddirectory . '/' . 'library.json';
93
        $jsfile = $uploaddirectory . '/' . 'scripts/testlib.min.js';
94
        $cssfile = $uploaddirectory . '/' . 'styles/testlib.min.css';
95
        $this->create_file($jsonfile);
96
        $this->create_file($jsfile);
97
        $this->create_file($cssfile);
98
        foreach ($langs as $lang => $value) {
99
            $jsonfile = $uploaddirectory . '/' . 'language/' . $lang . '.json';
100
            $this->create_file($jsonfile, $value);
101
        }
102
 
103
        $lib = [
104
            'title' => 'Test lib',
105
            'description' => 'Test library description',
106
            'majorVersion' => $majorversion,
107
            'minorVersion' => $minorversion,
108
            'patchVersion' => 2,
109
            'machineName' => $machinename,
110
            'preloadedJs' => [
111
                [
112
                    'path' => 'scripts' . '/' . 'testlib.min.js'
113
                ]
114
            ],
115
            'preloadedCss' => [
116
                [
117
                    'path' => 'styles' . '/' . 'testlib.min.css'
118
                ]
119
            ],
120
            'uploadDirectory' => $uploaddirectory,
121
            'libraryId' => $libraryid
122
        ];
123
 
124
        $version = "{$majorversion}.{$minorversion}.2";
125
        $libname = "{$machinename}-{$majorversion}.{$minorversion}";
126
        $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'scripts' . '/' . 'testlib.min.js';
127
        $this->add_libfile_to_array('scripts', $path, $version, $files);
128
        $path = '/' . 'libraries' . '/' . $libraryid .'/' . $libname . '/' . 'styles' . '/' . 'testlib.min.css';
129
        $this->add_libfile_to_array('styles', $path, $version, $files);
130
        foreach ($langs as $lang => $notused) {
131
            $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'language' . '/' . $lang . '.json';
132
            $this->add_libfile_to_array('language', $path, $version, $files);
133
        }
134
 
135
        return [$lib, $files];
136
    }
137
 
138
    /**
139
     * Save the library files on the filesystem.
140
     *
141
     * @param stdClss $lib The library data
142
     */
143
    private function save_library(stdClass $lib) {
144
        // Get a temp path.
145
        $filestorage = new \core_h5p\file_storage();
146
        $temppath = $filestorage->getTmpPath();
147
 
148
        // Create and save the library files on the filesystem.
149
        $basedirectorymain = $temppath . '/' . $lib->machinename . '-' .
150
            $lib->majorversion . '.' . $lib->minorversion;
151
 
152
        list($library, $libraryfiles) = $this->create_library($basedirectorymain, $lib->id, $lib->machinename,
153
            $lib->majorversion, $lib->minorversion);
154
 
155
        $filestorage->saveLibrary($library);
156
    }
157
 
158
    /**
159
     * Populate H5P database tables with relevant data to simulate the process of adding H5P content.
160
     *
161
     * @param bool $createlibraryfiles Whether to create and store library files on the filesystem
162
     * @param array|null $filerecord The file associated to the H5P entry.
163
     * @return stdClass An object representing the added H5P records
164
     */
165
    public function generate_h5p_data(bool $createlibraryfiles = false, ?array $filerecord = null): stdClass {
166
        // Create libraries.
167
        $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0, 1, '', null,
168
            'http://tutorial.org', 'http://example.org');
169
        $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0, 1, '', null, null,  'http://example.org');
170
        $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1, 1, '', null, 'http://tutorial.org');
171
        $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2, 1, '', null, null, null, true, 0);
172
        $lib4 = $libraries[] = $this->create_library_record('Library4', 'Lib4', 1, 1);
173
        $lib5 = $libraries[] = $this->create_library_record('Library5', 'Lib5', 1, 3);
174
 
175
        if ($createlibraryfiles) {
176
            foreach ($libraries as $lib) {
177
                // Create and save the library files on the filesystem.
178
                $this->save_library($lib);
179
            }
180
        }
181
 
182
        // Create h5p content.
183
        $h5p = $this->create_h5p_record($mainlib->id, null, null, $filerecord);
184
        // Create h5p content library dependencies.
185
        $this->create_contents_libraries_record($h5p, $mainlib->id);
186
        $this->create_contents_libraries_record($h5p, $lib1->id);
187
        $this->create_contents_libraries_record($h5p, $lib2->id);
188
        $this->create_contents_libraries_record($h5p, $lib3->id);
189
        $this->create_contents_libraries_record($h5p, $lib4->id);
190
        // Create library dependencies for $mainlib.
191
        $this->create_library_dependency_record($mainlib->id, $lib1->id);
192
        $this->create_library_dependency_record($mainlib->id, $lib2->id);
193
        $this->create_library_dependency_record($mainlib->id, $lib3->id);
194
        // Create library dependencies for $lib1.
195
        $this->create_library_dependency_record($lib1->id, $lib2->id);
196
        $this->create_library_dependency_record($lib1->id, $lib3->id);
197
        $this->create_library_dependency_record($lib1->id, $lib4->id);
198
        // Create library dependencies for $lib3.
199
        $this->create_library_dependency_record($lib3->id, $lib5->id);
200
 
201
        return (object) [
202
            'h5pcontent' => (object) array(
203
                'h5pid' => $h5p,
204
                'contentdependencies' => array($mainlib, $lib1, $lib2, $lib3, $lib4)
205
            ),
206
            'mainlib' => (object) array(
207
                'data' => $mainlib,
208
                'dependencies' => array($lib1, $lib2, $lib3)
209
            ),
210
            'lib1' => (object) array(
211
                'data' => $lib1,
212
                'dependencies' => array($lib2, $lib3, $lib4)
213
            ),
214
            'lib2' => (object) array(
215
                'data' => $lib2,
216
                'dependencies' => array()
217
            ),
218
            'lib3' => (object) array(
219
                'data' => $lib3,
220
                'dependencies' => array($lib5)
221
            ),
222
            'lib4' => (object) array(
223
                'data' => $lib4,
224
                'dependencies' => array()
225
            ),
226
            'lib5' => (object) array(
227
                'data' => $lib5,
228
                'dependencies' => array()
229
            ),
230
        ];
231
    }
232
 
233
    /**
234
     * Create a record in the h5p_libraries database table.
235
     *
236
     * @param string $machinename The library machine name
237
     * @param string $title The library's name
238
     * @param int $majorversion The library's major version
239
     * @param int $minorversion The library's minor version
240
     * @param int $patchversion The library's patch version
241
     * @param string $semantics Json describing the content structure for the library
242
     * @param string $addto The plugin configuration data
243
     * @param string $tutorial The tutorial URL
244
     * @param string $examlpe The example URL
245
     * @param bool $enabled Whether the library is enabled or not
246
     * @param int $runnable Whether the library is runnable (1) or not (0)
247
     * @return stdClass An object representing the added library record
248
     */
249
    public function create_library_record(string $machinename, string $title, int $majorversion = 1,
250
            int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null,
251
            string $tutorial = null, string $example = null, bool $enabled = true, int $runnable = 1): stdClass {
252
        global $DB;
253
 
254
        $content = [
255
            'machinename' => $machinename,
256
            'title' => $title,
257
            'majorversion' => $majorversion,
258
            'minorversion' => $minorversion,
259
            'patchversion' => $patchversion,
260
            'runnable' => $runnable,
261
            'fullscreen' => 1,
262
            'preloadedjs' => 'js/example.js',
263
            'preloadedcss' => 'css/example.css',
264
            'droplibrarycss' => '',
265
            'semantics' => $semantics,
266
            'addto' => $addto,
267
            'tutorial' => $tutorial,
268
            'example' => $example,
269
            'enabled' => $enabled,
270
        ];
271
 
272
        $libraryid = $DB->insert_record('h5p_libraries', $content);
273
 
274
        return $DB->get_record('h5p_libraries', ['id' => $libraryid]);
275
    }
276
 
277
    /**
278
     * Create a record in the h5p database table.
279
     *
280
     * @param int $mainlibid The ID of the content's main library
281
     * @param string $jsoncontent The content in json format
282
     * @param string $filtered The filtered content parameters
283
     * @param array|null $filerecord The file associated to the H5P entry.
284
     * @return int The ID of the added record
285
     */
286
    public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null,
287
            ?array $filerecord = null): int {
288
        global $DB;
289
 
290
        if (!$jsoncontent) {
291
            $jsoncontent = json_encode(
292
                array(
293
                    'text' => '<p>Dummy text<\/p>\n',
294
                    'questions' => '<p>Test question<\/p>\n'
295
                )
296
            );
297
        }
298
 
299
        if (!$filtered) {
300
            $filtered = json_encode(
301
                array(
302
                    'text' => 'Dummy text',
303
                    'questions' => 'Test question'
304
                )
305
            );
306
        }
307
 
308
        // Load the H5P file into DB.
309
        $pathnamehash = sha1('pathname');
310
        $contenthash = sha1('content');
311
        if ($filerecord) {
312
            $fs = get_file_storage();
313
            if (!$fs->get_file(
314
                    $filerecord['contextid'],
315
                    $filerecord['component'],
316
                    $filerecord['filearea'],
317
                    $filerecord['itemid'],
318
                    $filerecord['filepath'],
319
                    $filerecord['filename'])) {
320
                $file = $fs->create_file_from_string($filerecord, $jsoncontent);
321
                $pathnamehash = $file->get_pathnamehash();
322
                $contenthash = $file->get_contenthash();
323
                if (array_key_exists('addxapistate', $filerecord) && $filerecord['addxapistate']) {
324
                    // Save some xAPI state associated to this H5P content.
325
                    $params = [
326
                        'component' => $filerecord['component'],
327
                        'activity' => item_activity::create_from_id($filerecord['contextid']),
328
                    ];
329
                    global $CFG;
330
                    require_once($CFG->dirroot.'/lib/xapi/tests/helper.php');
331
                    \core_xapi\test_helper::create_state($params, true);
332
                }
333
            }
334
        }
335
 
336
        return $DB->insert_record(
337
            'h5p',
338
            [
339
                'jsoncontent' => $jsoncontent,
340
                'displayoptions' => 8,
341
                'mainlibraryid' => $mainlibid,
342
                'timecreated' => time(),
343
                'timemodified' => time(),
344
                'filtered' => $filtered,
345
                'pathnamehash' => $pathnamehash,
346
                'contenthash' => $contenthash,
347
            ]
348
        );
349
    }
350
 
351
    /**
352
     * Create a record in the h5p_contents_libraries database table.
353
     *
354
     * @param string $h5pid The ID of the H5P content
355
     * @param int $libid The ID of the library
356
     * @param string $dependencytype The dependency type
357
     * @return int The ID of the added record
358
     */
359
    public function create_contents_libraries_record(string $h5pid, int $libid,
360
            string $dependencytype = 'preloaded'): int {
361
        global $DB;
362
 
363
        return $DB->insert_record(
364
            'h5p_contents_libraries',
365
            array(
366
                'h5pid' => $h5pid,
367
                'libraryid' => $libid,
368
                'dependencytype' => $dependencytype,
369
                'dropcss' => 0,
370
                'weight' => 1
371
            )
372
        );
373
    }
374
 
375
    /**
376
     * Create a record in the h5p_library_dependencies database table.
377
     *
378
     * @param int $libid The ID of the library
379
     * @param int $requiredlibid The ID of the required library
380
     * @param string $dependencytype The dependency type
381
     * @return int The ID of the added record
382
     */
383
    public function create_library_dependency_record(int $libid, int $requiredlibid,
384
            string $dependencytype = 'preloaded'): int {
385
        global $DB;
386
 
387
        return $DB->insert_record(
388
            'h5p_library_dependencies',
389
            array(
390
                'libraryid' => $libid,
391
                'requiredlibraryid' => $requiredlibid,
392
                'dependencytype' => $dependencytype
393
            )
394
        );
395
    }
396
 
397
    /**
398
     * Create H5P content type records in the h5p_libraries database table.
399
     *
400
     * @param array $typestonotinstall H5P content types that should not be installed
401
     * @param core $core h5p_test_core instance required to use the exttests URL
402
     * @return array Data of the content types not installed.
403
     *
404
     * @throws invalid_response_exception If request to get the latest content types fails (usually due to a transient error)
405
     */
406
    public function create_content_types(array $typestonotinstall, core $core): array {
407
        global $DB;
408
 
409
        autoloader::register();
410
 
411
        // Get info of latest content types versions.
412
        $response = $core->get_latest_content_types();
413
        if (!empty($response->error)) {
414
            throw new invalid_response_exception($response->error);
415
        }
416
 
417
        $installedtypes = 0;
418
 
419
        // Fake installation of all other H5P content types.
420
        foreach ($response->contentTypes as $contenttype) {
421
            // Don't install pending content types.
422
            if (in_array($contenttype->id, $typestonotinstall)) {
423
                continue;
424
            }
425
            $library = [
426
                'machinename' => $contenttype->id,
427
                'majorversion' => $contenttype->version->major,
428
                'minorversion' => $contenttype->version->minor,
429
                'patchversion' => $contenttype->version->patch,
430
                'runnable' => 1,
431
                'coremajor' => $contenttype->coreApiVersionNeeded->major,
432
                'coreminor' => $contenttype->coreApiVersionNeeded->minor
433
            ];
434
            $DB->insert_record('h5p_libraries', (object) $library);
435
            $installedtypes++;
436
        }
437
 
438
        return [$installedtypes, count($typestonotinstall)];
439
    }
440
 
441
    /**
442
     * Add a record on files table for a file that belongs to
443
     *
444
     * @param string $file File name and path inside the filearea.
445
     * @param string $filearea The filearea in which the file is ("editor" or "content").
446
     * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor.
447
     *
448
     * @return stored_file;
449
     * @throws coding_exception
450
     */
451
    public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
452
        global $USER;
453
 
454
        $filepath = '/'.dirname($file).'/';
455
        $filename = basename($file);
456
 
457
        if (($filearea === 'content') && ($contentid == 0)) {
458
            throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
459
        }
460
 
461
        if ($filearea === 'draft') {
462
            $usercontext = \context_user::instance($USER->id);
463
            $context = $usercontext->id;
464
            $component = 'user';
465
            $itemid = 0;
466
        } else {
467
            $systemcontext = context_system::instance();
468
            $context = $systemcontext->id;
469
            $component = \core_h5p\file_storage::COMPONENT;
470
            $itemid = $contentid;
471
        }
472
 
473
        $content = 'fake content';
474
 
475
        $filerecord = array(
476
            'contextid' => $context,
477
            'component' => $component,
478
            'filearea'  => $filearea,
479
            'itemid'    => $itemid,
480
            'filepath'  => $filepath,
481
            'filename'  => $filename,
482
        );
483
 
484
        $fs = new file_storage();
485
        return $fs->create_file_from_string($filerecord, $content);
486
    }
487
 
488
    /**
489
     * Create a fake export H5P deployed file.
490
     *
491
     * @param string $filename Name of the H5P file to deploy.
492
     * @param int $contextid Context id of the H5P activity.
493
     * @param string $component component.
494
     * @param string $filearea file area.
495
     * @param int $typeurl Type of url to create the export url plugin file.
496
     * @return array return deployed file information.
497
     */
498
    public function create_export_file(string $filename, int $contextid,
499
        string $component,
500
        string $filearea,
501
        int $typeurl = self::WSPLUGINFILE): array {
502
        global $CFG;
503
 
504
        // We need the autoloader for H5P player.
505
        autoloader::register();
506
 
507
        $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename;
508
        $filerecord = [
509
            'contextid' => $contextid,
510
            'component' => $component,
511
            'filearea'  => $filearea,
512
            'itemid'    => 0,
513
            'filepath'  => '/',
514
            'filename'  => $filename,
515
        ];
516
        // Load the h5p file into DB.
517
        $fs = get_file_storage();
518
        if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) {
519
            $fs->create_file_from_pathname($filerecord, $path);
520
        }
521
 
522
        // Make the URL to pass to the player.
523
        if ($typeurl == self::WSPLUGINFILE) {
524
            $url = \moodle_url::make_webservice_pluginfile_url(
525
                $filerecord['contextid'],
526
                $filerecord['component'],
527
                $filerecord['filearea'],
528
                $filerecord['itemid'],
529
                $filerecord['filepath'],
530
                $filerecord['filename']
531
            );
532
        } else {
533
            $includetoken = false;
534
            if ($typeurl == self::TOKENPLUGINFILE) {
535
                $includetoken = true;
536
            }
537
            $url = \moodle_url::make_pluginfile_url(
538
                $filerecord['contextid'],
539
                $filerecord['component'],
540
                $filerecord['filearea'],
541
                $filerecord['itemid'],
542
                $filerecord['filepath'],
543
                $filerecord['filename'],
544
                false,
545
                $includetoken
546
            );
547
        }
548
 
549
        $config = new stdClass();
550
        $h5pplayer = new player($url->out(false), $config);
551
        // We need to add assets to page to create the export file.
552
        $h5pplayer->add_assets_to_page();
553
 
554
        // Call the method. We need the id of the new H5P content.
555
        $rc = new \ReflectionClass(player::class);
556
        $rcp = $rc->getProperty('h5pid');
557
        $h5pid = $rcp->getValue($h5pplayer);
558
 
559
        // Get the info export file.
560
        $factory = new factory();
561
        $core = $factory->get_core();
562
        $content = $core->loadContent($h5pid);
563
        $slug = $content['slug'] ? $content['slug'] . '-' : '';
564
        $exportfilename = "{$slug}{$h5pid}.h5p";
565
        $fileh5p = $core->fs->get_export_file($exportfilename);
566
        $deployedfile = [];
567
        $deployedfile['filename'] = $fileh5p->get_filename();
568
        $deployedfile['filepath'] = $fileh5p->get_filepath();
569
        $deployedfile['mimetype'] = $fileh5p->get_mimetype();
570
        $deployedfile['filesize'] = $fileh5p->get_filesize();
571
        $deployedfile['timemodified'] = $fileh5p->get_timemodified();
572
 
573
        // Create the url depending the request was made through typeurl.
574
        if ($typeurl == self::WSPLUGINFILE) {
575
            $url  = \moodle_url::make_webservice_pluginfile_url(
576
                $fileh5p->get_contextid(),
577
                $fileh5p->get_component(),
578
                $fileh5p->get_filearea(),
579
                '',
580
                '',
581
                $fileh5p->get_filename()
582
            );
583
        } else {
584
            $includetoken = false;
585
            if ($typeurl == self::TOKENPLUGINFILE) {
586
                $includetoken = true;
587
            }
588
            $url = \moodle_url::make_pluginfile_url(
589
                $fileh5p->get_contextid(),
590
                $fileh5p->get_component(),
591
                $fileh5p->get_filearea(),
592
                '',
593
                '',
594
                $fileh5p->get_filename(),
595
                false,
596
                $includetoken
597
            );
598
        }
599
        $deployedfile['fileurl'] = $url->out(false);
600
 
601
        return $deployedfile;
602
    }
603
}