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
namespace core_h5p;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once("$CFG->libdir/filelib.php");
22
 
23
use Moodle\H5PCore;
24
use Moodle\H5PFrameworkInterface;
25
use Moodle\H5PHubEndpoints;
26
use stdClass;
27
use moodle_url;
28
use core_h5p\local\library\autoloader;
29
 
30
// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod
31
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase
32
 
33
/**
34
 * H5P core class, containing functions and storage shared by the other H5P classes.
35
 *
36
 * @package    core_h5p
37
 * @copyright  2019 Sara Arjona <sara@moodle.com>
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class core extends H5PCore {
41
 
42
    /** @var array The array containing all the present libraries */
43
    protected $libraries;
44
 
45
    /**
46
     * Constructor for core_h5p/core.
47
     *
48
     * @param H5PFrameworkInterface $framework The frameworks implementation of the H5PFrameworkInterface
49
     * @param string|H5PFileStorage $path The H5P file storage directory or class
50
     * @param string $url The URL to the file storage directory
51
     * @param string $language The language code. Defaults to english
52
     * @param boolean $export Whether export is enabled
53
     */
54
    public function __construct(H5PFrameworkInterface $framework, $path, string $url, string $language = 'en',
55
            bool $export = false) {
56
 
57
        parent::__construct($framework, $path, $url, $language, $export);
58
 
59
        // Aggregate the assets by default.
60
        $this->aggregateAssets = true;
61
    }
62
 
63
    /**
64
     * Get the path to the dependency.
65
     *
66
     * @param array $dependency An array containing the information of the requested dependency library
67
     * @return string The path to the dependency library
68
     */
69
    protected function getDependencyPath(array $dependency): string {
70
        $library = $this->find_library($dependency);
71
 
72
        return "libraries/{$library->id}/" . H5PCore::libraryToFolderName($dependency);
73
    }
74
 
75
    /**
76
     * Get the paths to the content dependencies.
77
     *
78
     * @param int $id The H5P content ID
79
     * @return array An array containing the path of each content dependency
80
     */
81
    public function get_dependency_roots(int $id): array {
82
        $roots = [];
83
        $dependencies = $this->h5pF->loadContentDependencies($id);
84
        $context = \context_system::instance();
85
        foreach ($dependencies as $dependency) {
86
            $library = $this->find_library($dependency);
87
            $roots[self::libraryToFolderName($dependency)] = (moodle_url::make_pluginfile_url(
88
                $context->id,
89
                'core_h5p',
90
                'libraries',
91
                $library->id,
92
                "/" . self::libraryToFolderName($dependency),
93
                ''
94
            ))->out(false);
95
        }
96
 
97
        return $roots;
98
    }
99
 
100
    /**
101
     * Get a particular dependency library.
102
     *
103
     * @param array $dependency An array containing information of the dependency library
104
     * @return stdClass|null The library object if the library dependency exists, null otherwise
105
     */
106
    protected function find_library(array $dependency): ?\stdClass {
107
        global $DB;
108
        if (null === $this->libraries) {
109
            $this->libraries = $DB->get_records('h5p_libraries');
110
        }
111
 
112
        $major = $dependency['majorVersion'];
113
        $minor = $dependency['minorVersion'];
114
        $patch = $dependency['patchVersion'];
115
 
116
        foreach ($this->libraries as $library) {
117
            if ($library->machinename !== $dependency['machineName']) {
118
                continue;
119
            }
120
 
121
            if ($library->majorversion != $major) {
122
                continue;
123
            }
124
            if ($library->minorversion != $minor) {
125
                continue;
126
            }
127
            if ($library->patchversion != $patch) {
128
                continue;
129
            }
130
 
131
            return $library;
132
        }
133
 
134
        return null;
135
    }
136
 
137
    /**
138
     * Get the list of JS scripts to include on the page.
139
     *
140
     * @return array The array containg urls of the core JavaScript files
141
     */
142
    public static function get_scripts(): array {
143
        global $PAGE;
144
 
145
        $jsrev = $PAGE->requires->get_jsrev();
146
        $urls = [];
147
        foreach (self::$scripts as $script) {
148
            $urls[] = autoloader::get_h5p_core_library_url($script, [
149
                'ver' => $jsrev,
150
            ]);
151
        }
152
        $urls[] = new moodle_url("/h5p/js/h5p_overrides.js", [
153
            'ver' => $jsrev,
154
        ]);
155
 
156
        return $urls;
157
    }
158
 
159
    /**
160
     * Fetch and install the latest H5P content types libraries from the official H5P repository.
161
     * If the latest version of a content type library is present in the system, nothing is done for that content type.
162
     *
163
     * @return stdClass
164
     */
165
    public function fetch_latest_content_types(): ?\stdClass {
166
 
167
        $contenttypes = $this->get_latest_content_types();
168
        if (!empty($contenttypes->error)) {
169
            return $contenttypes;
170
        }
171
 
172
        $typesinstalled = [];
173
 
174
        $factory = new factory();
175
        $framework = $factory->get_framework();
176
 
177
        foreach ($contenttypes->contentTypes as $type) {
178
            // Don't fetch content types if any of the versions is disabled.
179
            $librarydata = (object) ['machinename' => $type->id];
180
            if (!api::is_library_enabled($librarydata)) {
181
                continue;
182
            }
183
            // Don't fetch content types that require a higher H5P core API version.
184
            if (!$this->is_required_core_api($type->coreApiVersionNeeded)) {
185
                continue;
186
            }
187
 
188
            $library = [
189
                'machineName' => $type->id,
190
                'majorVersion' => $type->version->major,
191
                'minorVersion' => $type->version->minor,
192
                'patchVersion' => $type->version->patch,
193
            ];
194
            // Add example and tutorial to the library, to store this information too.
195
            if (isset($type->example)) {
196
                $library['example'] = $type->example;
197
            }
198
            if (isset($type->tutorial)) {
199
                $library['tutorial'] = $type->tutorial;
200
            }
201
 
202
            $shoulddownload = true;
203
            if ($framework->getLibraryId($type->id, $type->version->major, $type->version->minor)) {
204
                if (!$framework->isPatchedLibrary($library)) {
205
                    $shoulddownload = false;
206
                }
207
            }
208
 
209
            if ($shoulddownload) {
210
                $installed['id'] = $this->fetch_content_type($library);
211
                if ($installed['id']) {
212
                    $installed['name'] = H5PCore::libraryToString($library);
213
                    $typesinstalled[] = $installed;
214
                }
215
            }
216
        }
217
 
218
        $result = new stdClass();
219
        $result->error = '';
220
        $result->typesinstalled = $typesinstalled;
221
 
222
        return $result;
223
    }
224
 
225
    /**
226
     * Given an H5P content type machine name, fetch and install the required library from the official H5P repository.
227
     *
228
     * @param array $library Library machineName, majorversion and minorversion.
229
     * @return int|null Returns the id of the content type library installed, null otherwise.
230
     */
231
    public function fetch_content_type(array $library): ?int {
232
        global $DB;
233
 
234
        $factory = new factory();
235
 
236
        $fs = get_file_storage();
237
 
238
        // Delete any existing file, if it was not deleted during a previous download.
239
        $existing = $fs->get_file(
240
            (\context_system::instance())->id,
241
            'core_h5p',
242
            'library_sources',
243
            0,
244
            '/',
245
            $library['machineName']
246
        );
247
        if ($existing) {
248
            $existing->delete();
249
        }
250
 
251
        // Download the latest content type from the H5P official repository.
252
        $file = $fs->create_file_from_url(
253
            (object) [
254
                'component' => 'core_h5p',
255
                'filearea' => 'library_sources',
256
                'itemid' => 0,
257
                'contextid' => (\context_system::instance())->id,
258
                'filepath' => '/',
259
                'filename' => $library['machineName'],
260
            ],
261
            $this->get_api_endpoint($library['machineName']),
262
            null,
263
            true
264
        );
265
 
266
        if (!$file) {
267
            return null;
268
        }
269
 
270
        helper::save_h5p($factory, $file, (object) [], false, true);
271
 
272
        $file->delete();
273
 
274
        $librarykey = static::libraryToString($library);
275
 
276
        if (is_null($factory->get_storage()->h5pC->librariesJsonData)) {
277
            // There was an error fetching the content type.
278
            debugging('Error fetching content type: ' . $librarykey);
279
            return null;
280
        }
281
 
282
        $libraryjson = $factory->get_storage()->h5pC->librariesJsonData[$librarykey];
283
        if (is_null($libraryjson) || !array_key_exists('libraryId', $libraryjson)) {
284
            // There was an error fetching the content type.
285
            debugging('Error fetching content type: ' . $librarykey);
286
            return null;
287
        }
288
 
289
        $libraryid = $libraryjson['libraryId'];
290
 
291
        // Update example and tutorial (if any of them are defined in $library).
292
        $params = ['id' => $libraryid];
293
        if (array_key_exists('example', $library)) {
294
            $params['example'] = $library['example'];
295
        }
296
        if (array_key_exists('tutorial', $library)) {
297
            $params['tutorial'] = $library['tutorial'];
298
        }
299
        if (count($params) > 1) {
300
            $DB->update_record('h5p_libraries', $params);
301
        }
302
 
303
        return $libraryid;
304
    }
305
 
306
    /**
307
     * Get H5P endpoints.
308
     *
309
     * If $endpoint = 'content' and $library is null, moodle_url is the endpoint of the latest version of the H5P content
310
     * types; however, if $library is the machine name of a content type, moodle_url is the endpoint to download the content type.
311
     * The SITES endpoint ($endpoint = 'site') may be use to get a site UUID or send site data.
312
     *
313
     * @param string|null $library The machineName of the library whose endpoint is requested.
314
     * @param string $endpoint The endpoint required. Valid values: "site", "content".
315
     * @return moodle_url The endpoint moodle_url object.
316
     */
317
    public function get_api_endpoint(?string $library = null, string $endpoint = 'content'): moodle_url {
318
        if ($endpoint == 'site') {
319
            $h5purl = H5PHubEndpoints::createURL(H5PHubEndpoints::SITES );
320
        } else if ($endpoint == 'content') {
321
            $h5purl = H5PHubEndpoints::createURL(H5PHubEndpoints::CONTENT_TYPES ) . $library;
322
        }
323
 
324
        return new moodle_url($h5purl);
325
    }
326
 
327
    /**
328
     * Get the latest version of the H5P content types available in the official repository.
329
     *
330
     * @return stdClass An object with 2 properties:
331
     *     - string error: error message when there is any problem, empty otherwise
332
     *     - array contentTypes: an object for each H5P content type with its information
333
     */
334
    public function get_latest_content_types(): \stdClass {
335
        global $CFG;
336
 
337
        $siteuuid = $this->get_site_uuid() ?? md5($CFG->wwwroot);
338
        $postdata = ['uuid' => $siteuuid];
339
 
340
        // Get the latest content-types json.
341
        $endpoint = $this->get_api_endpoint();
342
        $request = download_file_content($endpoint, null, $postdata, true);
343
 
344
        if (!empty($request->error) || $request->status != '200' || empty($request->results)) {
345
            if (empty($request->error)) {
346
                $request->error = get_string('fetchtypesfailure', 'core_h5p');
347
            }
348
            return $request;
349
        }
350
 
351
        $contenttypes = json_decode($request->results);
352
        $contenttypes->error = '';
353
 
354
        return $contenttypes;
355
    }
356
 
357
    /**
358
     * Get the site UUID. If site UUID is not defined, try to register the site.
359
     *
360
     * return $string The site UUID, null if it is not set.
361
     */
362
    public function get_site_uuid(): ?string {
363
        // Check if the site_uuid is already set.
364
        $siteuuid = get_config('core_h5p', 'site_uuid');
365
 
366
        if (!$siteuuid) {
367
            $siteuuid = $this->register_site();
368
        }
369
 
370
        return $siteuuid;
371
    }
372
 
373
    /**
374
     * Get H5P generated site UUID.
375
     *
376
     * return ?string Returns H5P generated site UUID, null if can't get it.
377
     */
378
    private function register_site(): ?string {
379
        $endpoint = $this->get_api_endpoint(null, 'site');
380
        $siteuuid = download_file_content($endpoint, null, '');
381
 
382
        // Successful UUID retrieval from H5P.
383
        if ($siteuuid) {
384
            $json = json_decode($siteuuid);
385
            if (isset($json->uuid)) {
386
                set_config('site_uuid', $json->uuid, 'core_h5p');
387
                return $json->uuid;
388
            }
389
        }
390
 
391
        return null;
392
    }
393
 
394
    /**
395
     * Checks that the required H5P core API version or higher is installed.
396
     *
397
     * @param stdClass $coreapi Object with properties major and minor for the core API version required.
398
     * @return bool True if the required H5P core API version is installed. False if not.
399
     */
400
    public function is_required_core_api($coreapi): bool {
401
        if (isset($coreapi) && !empty($coreapi)) {
402
            if (($coreapi->major > H5PCore::$coreApi['majorVersion']) ||
403
                (($coreapi->major == H5PCore::$coreApi['majorVersion']) && ($coreapi->minor > H5PCore::$coreApi['minorVersion']))) {
404
                return false;
405
            }
406
        }
407
        return true;
408
    }
409
 
410
    /**
411
     * Get the library string from a DB library record.
412
     *
413
     * @param  stdClass $record The DB library record.
414
     * @param  bool $foldername If true, use hyphen instead of space in returned string.
415
     * @return string The string name on the form {machineName} {majorVersion}.{minorVersion}.
416
     */
417
    public static function record_to_string(stdClass $record, bool $foldername = false): string {
418
        if ($foldername) {
419
            return static::libraryToFolderName([
420
                'machineName' => $record->machinename,
421
                'majorVersion' => $record->majorversion,
422
                'minorVersion' => $record->minorversion,
423
            ]);
424
        } else {
425
            return static::libraryToString([
426
                'machineName' => $record->machinename,
427
                'majorVersion' => $record->majorversion,
428
                'minorVersion' => $record->minorversion,
429
            ]);
430
        }
431
 
432
    }
433
 
434
    /**
435
     * Small helper for getting the library's ID.
436
     * This method is rewritten to use MUC (instead of an static variable which causes some problems with PHPUnit).
437
     *
438
     * @param array $library
439
     * @param string $libString
440
     * @return int Identifier, or FALSE if non-existent
441
     */
442
    public function getLibraryId($library, $libString = null) {
443
        if (!$libString) {
444
            $libString = self::libraryToString($library);
445
        }
446
 
447
        // Check if this information has been saved previously into the cache.
448
        $libcache = \cache::make('core', 'h5p_libraries');
449
        $librarykey = helper::get_cache_librarykey($libString);
450
        $libraryId = $libcache->get($librarykey);
451
        if ($libraryId === false) {
452
            $libraryId = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
453
            $libcache->set($librarykey, $libraryId);
454
        }
455
 
456
        return $libraryId;
457
    }
458
 
459
}