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
 * \mod_hvp\framework class
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
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
global $CFG;
30
require_once(__DIR__ . '/../autoloader.php');
31
require_once($CFG->libdir . '/filelib.php');
32
require_once($CFG->libdir . '/adminlib.php');
33
 
34
/**
35
 * Moodle's implementation of the H5P framework interface.
36
 *
37
 * @package    mod_hvp
38
 * @copyright  2016 Joubel AS
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 *
41
 * @SuppressWarnings(PHPMD)
42
 */
43
class framework implements \H5PFrameworkInterface {
44
 
45
    /**
46
     * Get type of hvp instance
47
     *
48
     * @param string $type Type of hvp instance to get
49
     * @return \H5PContentValidator|\H5PCore|\H5PStorage|\H5PValidator|\mod_hvp\framework|\H5peditor
50
     */
51
    public static function instance($type = null) {
52
        global $CFG;
53
        static $interface, $core, $editor, $editorinterface, $editorajaxinterface;
54
 
55
        if (!isset($interface)) {
56
            $interface = new \mod_hvp\framework();
57
 
58
            // Support alternate file storage class defined in $CFG.
59
            if (!empty($CFG->mod_hvp_file_storage_class)) {
60
                $fsclass = $CFG->mod_hvp_file_storage_class;
61
            } else {
62
                $fsclass = '\mod_hvp\file_storage';
63
            }
64
 
65
            $fs = new $fsclass();
66
 
67
            $context = \context_system::instance();
68
            $root = view_assets::getsiteroot();
69
            $url = "{$root}/pluginfile.php/{$context->id}/mod_hvp";
70
 
71
            $language = self::get_language();
72
 
73
            $export = !(isset($CFG->mod_hvp_export) && $CFG->mod_hvp_export === '0');
74
 
75
            $core = new \H5PCore($interface, $fs, $url, $language, $export);
76
            $core->aggregateAssets = !(isset($CFG->mod_hvp_aggregate_assets) && $CFG->mod_hvp_aggregate_assets === '0');
77
        }
78
 
79
        switch ($type) {
80
            case 'validator':
81
                return new \H5PValidator($interface, $core);
82
            case 'storage':
83
                return new \H5PStorage($interface, $core);
84
            case 'contentvalidator':
85
                return new \H5PContentValidator($interface, $core);
86
            case 'interface':
87
                return $interface;
88
            case 'editor':
89
                if (empty($editorinterface)) {
90
                    $editorinterface = new \mod_hvp\editor_framework();
91
                }
92
 
93
                if (empty($editorajaxinterface)) {
94
                    $editorajaxinterface = new editor_ajax();
95
                }
96
 
97
                if (empty($editor)) {
98
                    $editor = new \H5peditor($core, $editorinterface, $editorajaxinterface);
99
                }
100
                return $editor;
101
            case 'core':
102
            default:
103
                return $core;
104
        }
105
    }
106
 
107
    /**
108
     * Check if the current user has editor access, if not then return the
109
     * given error message.
110
     *
111
     * @param string $error
112
     * @return boolean
113
     */
114
    public static function has_editor_access($error) {
115
        $context = \context::instance_by_id(required_param('contextId', PARAM_RAW));
116
        $cap = ($context->contextlevel === CONTEXT_COURSE ? 'addinstance' : 'manage');
117
 
118
        if (!has_capability("mod/hvp:$cap", $context)) {
119
            \H5PCore::ajaxError(get_string($error, 'hvp'));
120
            http_response_code(403);
121
            return false;
122
        }
123
 
124
        return true;
125
    }
126
 
127
    /**
128
     * Get current H5P language code.
129
     *
130
     * @return string Language Code
131
     */
132
    public static function get_language() {
133
        static $map;
134
 
135
        if (empty($map)) {
136
            // Create mapping for "converting" language codes.
137
            $map = array(
138
                'no' => 'nb'
139
            );
140
        }
141
 
142
        // Get current language in Moodle.
143
        $language = str_replace('_', '-', strtolower(\current_language()));
144
 
145
        // Try to map.
146
        return isset($map[$language]) ? $map[$language] : $language;
147
    }
148
 
149
    /**
150
     * Implements getPlatformInfo
151
     */
152
    // @codingStandardsIgnoreLine
153
    public function getPlatformInfo() {
154
        global $CFG;
155
 
156
        return array(
157
            'name' => 'Moodle',
158
            'version' => $CFG->version,
159
            'h5pVersion' => get_component_version('mod_hvp'),
160
        );
161
    }
162
 
163
    /**
164
     * @inheritdoc
165
     */
166
    // @codingStandardsIgnoreLine
167
    public function fetchExternalData($url, $data = null, $blocking = true, $stream = null, $alldata = false, $headers = array(), $files = array(), $method = 'POST') {
168
        global $CFG;
169
 
170
        if (!empty($files)) {
171
            $curldata = array();
172
            foreach ($data as $key => $value) {
173
                if (empty($value)) {
174
                    continue; // Skip empty values.
175
                }
176
                if (is_array($value)) {
177
                    foreach ($value as $i => $subvalue) {
178
                        $curldata["{$key}[{$i}]"] = $subvalue;
179
                    }
180
                } else {
181
                    $curldata[$key] = $value;
182
                }
183
            }
184
 
185
            foreach ($files as $name => $file) {
186
                if ($file === null) {
187
                    continue;
188
                } else if (is_array($file['name'])) {
189
                    // Array of files uploaded (multiple).
190
                    for ($i = 0; $i < count($file['name']); $i ++) {
191
                        $curldata["{$name}[{$i}]"] = new \CurlFile($file['tmp_name'][$i], $file['type'][$i], $file['name'][$i]);
192
                    }
193
                } else {
194
                    // Single file.
195
                    $curldata[$name] = new \CurlFile($file['tmp_name'], $file['type'], $file['name']);
196
                }
197
            }
198
        } else if (!empty($data)) {
199
            // Application/x-www-form-urlencoded.
200
            $curldata = format_postdata_for_curlcall($data);
201
        }
202
 
203
        $options = array(
204
            'CURLOPT_SSL_VERIFYPEER' => true,
205
            'CURLOPT_CONNECTTIMEOUT' => 20,
206
            'CURLOPT_FOLLOWLOCATION' => 1,
207
            'CURLOPT_MAXREDIRS'      => 5,
208
            'CURLOPT_RETURNTRANSFER' => true,
209
            'CURLOPT_NOBODY'         => false,
210
            'CURLOPT_TIMEOUT'        => 300,
211
        );
212
 
213
        if ($stream !== null) {
214
            // Download file.
215
            @set_time_limit(0);
216
 
217
            // Generate local tmp file path.
218
            $localfolder = make_temp_directory(uniqid('hvp-'));
219
            $localpath = $localfolder . '.h5p';
220
 
221
            // Add folder and file paths to H5P Core.
222
            $interface = self::instance('interface');
223
            $interface->getUploadedH5pFolderPath($localfolder);
224
            $interface->getUploadedH5pPath($localpath);
225
 
226
            $stream = fopen($localpath, 'w');
227
            $options['CURLOPT_FILE'] = $stream;
228
        }
229
 
230
        $curl = new curl();
231
 
232
        // Massage headers to work with curl.
233
        foreach ($headers as $key => $value) {
234
            $curl->setHeader(is_numeric($key) ? $value : "$key: $value");
235
        }
236
 
237
        if (empty($data) || $method === 'GET') {
238
            $response = $curl->get($url, array(), $options);
239
        } else if ($method === 'POST') {
240
            $response = $curl->post($url, $curldata, $options);
241
        } else if ($method === 'PUT') {
242
            $response = $curl->put($url, $curldata, $options);
243
        }
244
 
245
        if ($stream !== null) {
246
            fclose($stream);
247
            @chmod($localpath, $CFG->filepermissions);
248
        }
249
 
250
        $errorno = $curl->get_errno();
251
        // Error handling.
252
        if ($errorno) {
253
            if ($alldata) {
254
                $response = null;
255
            } else {
256
                $this->setErrorMessage($response, 'failed-fetching-external-data');
257
 
258
                return false;
259
            }
260
        }
261
 
262
        if ($alldata) {
263
            $info = $curl->get_info();
264
 
265
            return [
266
                'status'  => intval($info['http_code']),
267
                'data'    => empty($response) ? null : $response,
268
                'headers' => $curl->get_raw_response(),
269
            ];
270
        } else {
271
            return $response;
272
        }
273
    }
274
 
275
    /**
276
     * Implements setLibraryTutorialUrl
277
     *
278
     * Set the tutorial URL for a library. All versions of the library is set
279
     *
280
     * @param string $libraryname
281
     * @param string $url
282
     */
283
    // @codingStandardsIgnoreLine
284
    public function setLibraryTutorialUrl($libraryname, $url) {
285
        global $DB;
286
 
287
        $DB->execute("UPDATE {hvp_libraries} SET tutorial_url = ? WHERE machine_name = ?", array($url, $libraryname));
288
    }
289
 
290
    /**
291
     * Implements setErrorMessage
292
     *
293
     * @param string $message translated error message
294
     * @param string $code
295
     */
296
    // @codingStandardsIgnoreLine
297
    public function setErrorMessage($message, $code = null) {
298
        if ($message !== null) {
299
            self::messages('error', $message, $code);
300
        }
301
    }
302
 
303
    /**
304
     * Implements setInfoMessage
305
     */
306
    // @codingStandardsIgnoreLine
307
    public function setInfoMessage($message) {
308
        if ($message !== null) {
309
            self::messages('info', $message);
310
        }
311
    }
312
 
313
    /**
314
     * Store messages until they can be printed to the current user
315
     *
316
     * @param string $type Type of messages, e.g. 'info' or 'error'
317
     * @param string $newmessage Optional
318
     * @param string $code
319
     * @return array Array of stored messages
320
     */
321
    public static function messages($type, $newmessage = null, $code = null) {
322
        static $m = 'mod_hvp_messages';
323
 
324
        if ($newmessage === null) {
325
            // Return and reset messages.
326
            $messages = isset($_SESSION[$m][$type]) ? $_SESSION[$m][$type] : array();
327
            unset($_SESSION[$m][$type]);
328
            if (empty($_SESSION[$m])) {
329
                unset($_SESSION[$m]);
330
            }
331
            return $messages;
332
        }
333
 
334
        // We expect to get out an array of strings when getting info
335
        // and an array of objects when getting errors for consistency across platforms.
336
        // This implementation should be improved for consistency across the data type returned here.
337
        if ($type === 'error') {
338
            $_SESSION[$m][$type][] = (object)array(
339
                'code' => $code,
340
                'message' => $newmessage
341
            );
342
        } else {
343
            $_SESSION[$m][$type][] = $newmessage;
344
        }
345
    }
346
 
347
    /**
348
     * Simple print of given messages.
349
     *
350
     * @param string $type One of error|info
351
     * @param array $messages
352
     */
353
    // @codingStandardsIgnoreLine
354
    public static function printMessages($type, $messages) {
355
        global $OUTPUT;
356
        foreach ($messages as $message) {
357
            $out = $type === 'error' ? $message->message : $message;
358
            print $OUTPUT->notification($out, ($type === 'error' ? 'notifyproblem' : 'notifymessage'));
359
        }
360
    }
361
 
362
    /**
363
     * Implements getMessages
364
     */
365
    // @codingStandardsIgnoreLine
366
    public function getMessages($type) {
367
        return self::messages($type);
368
    }
369
 
370
    /**
371
     * Implements t
372
     */
373
    public function t($message, $replacements = array()) {
374
        static $translationsmap;
375
 
376
        if (empty($translationsmap)) {
377
            // Create mapping.
378
            // @codingStandardsIgnoreStart
379
            $translationsmap = [
380
                'Your PHP version does not support ZipArchive.' => 'noziparchive',
381
                'The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)' => 'noextension',
382
                'The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)' => 'nounzip',
383
                'Could not parse the main h5p.json file' => 'noparse',
384
                'The main h5p.json file is not valid' => 'nojson',
385
                'Invalid content folder' => 'invalidcontentfolder',
386
                'Could not find or parse the content.json file' => 'nocontent',
387
                'Library directory name must match machineName or machineName-majorVersion.minorVersion (from library.json). (Directory: %directoryName , machineName: %machineName, majorVersion: %majorVersion, minorVersion: %minorVersion)' => 'librarydirectoryerror',
388
                'A valid content folder is missing' => 'missingcontentfolder',
389
                'A valid main h5p.json file is missing' => 'invalidmainjson',
390
                'Missing required library @library' => 'missinglibrary',
391
                "Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this." => 'missinguploadpermissions',
392
                'Invalid library name: %name' => 'invalidlibraryname',
393
                'Could not find library.json file with valid json format for library %name' => 'missinglibraryjson',
394
                'Invalid semantics.json file has been included in the library %name' => 'invalidsemanticsjson',
395
                'Invalid language file %file in library %library' => 'invalidlanguagefile',
396
                'Invalid language file %languageFile has been included in the library %name' => 'invalidlanguagefile2',
397
                'The file "%file" is missing from library: "%name"' => 'missinglibraryfile',
398
                'The system was unable to install the <em>%component</em> component from the package, it requires a newer version of the H5P plugin. This site is currently running version %current, whereas the required version is %required or higher. You should consider upgrading and then try again.' => 'missingcoreversion',
399
                "Invalid data provided for %property in %library. Boolean expected." => 'invalidlibrarydataboolean',
400
                "Invalid data provided for %property in %library" => 'invalidlibrarydata',
401
                "Can't read the property %property in %library" => 'invalidlibraryproperty',
402
                'The required property %property is missing from %library' => 'missinglibraryproperty',
403
                'Illegal option %option in %library' => 'invalidlibraryoption',
404
                'Added %new new H5P library and updated %old old one.' => 'addedandupdatedss',
405
                'Added %new new H5P library and updated %old old ones.' => 'addedandupdatedsp',
406
                'Added %new new H5P libraries and updated %old old one.' => 'addedandupdatedps',
407
                'Added %new new H5P libraries and updated %old old ones.' => 'addedandupdatedpp',
408
                'Added %new new H5P library.' => 'addednewlibrary',
409
                'Added %new new H5P libraries.' => 'addednewlibraries',
410
                'Updated %old H5P library.' =>  'updatedlibrary',
411
                'Updated %old H5P libraries.' => 'updatedlibraries',
412
                'Missing dependency @dep required by @lib.' => 'missingdependency',
413
                'Provided string is not valid according to regexp in semantics. (value: \"%value\", regexp: \"%regexp\")' => 'invalidstring',
414
                'File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.' => 'invalidfile',
415
                'Invalid selected option in multi-select.' => 'invalidmultiselectoption',
416
                'Invalid selected option in select.' => 'invalidselectoption',
417
                'H5P internal error: unknown content type "@type" in semantics. Removing content!' => 'invalidsemanticstype',
418
                'Copyright information' => 'copyrightinfo',
419
                'Title' => 'title',
420
                'Author' => 'author',
421
                'Year(s)' => 'years',
422
                'Year' => 'year',
423
                'Source' => 'source',
424
                'License' => 'license',
425
                'Undisclosed' => 'undisclosed',
426
                'Attribution 4.0' => 'attribution',
427
                'Attribution-ShareAlike 4.0' => 'attributionsa',
428
                'Attribution-NoDerivs 4.0' => 'attributionnd',
429
                'Attribution-NonCommercial 4.0' => 'attributionnc',
430
                'Attribution-NonCommercial-ShareAlike 4.0' => 'attributionncsa',
431
                'Attribution-NonCommercial-NoDerivs 4.0' => 'attributionncnd',
432
                'Attribution' => 'noversionattribution',
433
                'Attribution-ShareAlike' => 'noversionattributionsa',
434
                'Attribution-NoDerivs' => 'noversionattributionnd',
435
                'Attribution-NonCommercial' => 'noversionattributionnc',
436
                'Attribution-NonCommercial-ShareAlike' => 'noversionattributionncsa',
437
                'Attribution-NonCommercial-NoDerivs' => 'noversionattributionncnd',
438
                'General Public License v3' => 'gpl',
439
                'Public Domain' => 'pd',
440
                'Public Domain Dedication and Licence' => 'pddl',
441
                'Public Domain Mark' => 'pdm',
442
                'Public Domain Mark (PDM)' => 'pdm',
443
                'Copyright' => 'copyrightstring',
444
                'Unable to create directory.' => 'unabletocreatedir',
445
                'Unable to get field type.' => 'unabletogetfieldtype',
446
                "File type isn't allowed." => 'filetypenotallowed',
447
                'Invalid field type.' => 'invalidfieldtype',
448
                'Invalid image file format. Use jpg, png or gif.' => 'invalidimageformat',
449
                'File is not an image.' => 'filenotimage',
450
                'Invalid audio file format. Use mp3 or wav.' => 'invalidaudioformat',
451
                'Invalid video file format. Use mp4 or webm.' => 'invalidvideoformat',
452
                'Could not save file.' => 'couldnotsave',
453
                'Could not copy file.' => 'couldnotcopy',
454
                'The mbstring PHP extension is not loaded. H5P need this to function properly' => 'missingmbstring',
455
                'The version of the H5P library %machineName used in this content is not valid. Content contains %contentLibrary, but it should be %semanticsLibrary.' => 'wrongversion',
456
                'The H5P library %library used in the content is not valid' => 'invalidlibrarynamed',
457
                'Your PHP version is outdated. H5P requires version 5.2 to function properly. Version 5.6 or later is recommended.' => 'oldphpversion',
458
                'Your PHP max upload size is quite small. With your current setup, you may not upload files larger than %number MB. This might be a problem when trying to upload H5Ps, images and videos. Please consider to increase it to more than 5MB.' => 'maxuploadsizetoosmall',
459
                'Your PHP max post size is quite small. With your current setup, you may not upload files larger than %number MB. This might be a problem when trying to upload H5Ps, images and videos. Please consider to increase it to more than 5MB' => 'maxpostsizetoosmall',
460
                'Your server does not have SSL enabled. SSL should be enabled to ensure a secure connection with the H5P hub.' => 'sslnotenabled',
461
                'H5P hub communication has been disabled because one or more H5P requirements failed.' => 'hubcommunicationdisabled',
462
                'When you have revised your server setup you may re-enable H5P hub communication in H5P Settings.' => 'reviseserversetupandretry',
463
                'A problem with the server write access was detected. Please make sure that your server can write to your data folder.' => 'nowriteaccess',
464
                'Your PHP max upload size is bigger than your max post size. This is known to cause issues in some installations.' => 'uploadsizelargerthanpostsize',
465
                'Library cache was successfully updated!' => 'ctcachesuccess',
466
                'No content types were received from the H5P Hub. Please try again later.' => 'ctcachenolibraries',
467
                "Couldn't communicate with the H5P Hub. Please try again later." => 'ctcacheconnectionfailed',
468
                'The hub is disabled. You can re-enable it in the H5P settings.' => 'hubisdisabled',
469
                'File not found on server. Check file upload settings.' => 'filenotfoundonserver',
470
                'Invalid security token.' => 'invalidtoken',
471
                'No content type was specified.' => 'nocontenttype',
472
                'The chosen content type is invalid.' => 'invalidcontenttype',
473
                'You do not have permission to install content types. Contact the administrator of your site.' => 'installdenied',
474
                'You do not have permission to install content types.' => 'installdenied',
475
                'Validating h5p package failed.' => 'validatingh5pfailed',
476
                'Failed to download the requested H5P.' => 'failedtodownloadh5p',
477
                'A post message is required to access the given endpoint' => 'postmessagerequired',
478
                'Could not get posted H5P.' => 'invalidh5ppost',
479
                'Site could not be registered with the hub. Please contact your site administrator.' => 'sitecouldnotberegistered',
480
                'The H5P Hub has been disabled until this problem can be resolved. You may still upload libraries through the "H5P Libraries" page.' => 'hubisdisableduploadlibraries',
481
                'Your site was successfully registered with the H5P Hub.' => 'successfullyregisteredwithhub',
482
                'You have been provided a unique key that identifies you with the Hub when receiving new updates. The key is available for viewing in the "H5P Settings" page.' => 'sitekeyregistered',
483
                'Fullscreen' => 'fullscreen',
484
                'Disable fullscreen' => 'disablefullscreen',
485
                'Download' => 'download',
486
                'Rights of use' => 'copyright',
487
                'Embed' => 'embed',
488
                'Size' => 'size',
489
                'Show advanced' => 'showadvanced',
490
                'Hide advanced' => 'hideadvanced',
491
                'Include this script on your website if you want dynamic sizing of the embedded content:' => 'resizescript',
492
                'Close' => 'close',
493
                'Thumbnail' => 'thumbnail',
494
                'No copyright information available for this content.' => 'nocopyright',
495
                'Download this content as a H5P file.' => 'downloadtitle',
496
                'View copyright information for this content.' => 'copyrighttitle',
497
                'View the embed code for this content.' => 'embedtitle',
498
                'Visit H5P.org to check out more cool content.' => 'h5ptitle',
499
                'This content has changed since you last used it.' => 'contentchanged',
500
                "You'll be starting over." => 'startingover',
501
                'by' => 'by',
502
                'Show more' => 'showmore',
503
                'Show less' => 'showless',
504
                'Sublevel' => 'sublevel',
505
                'Confirm action' => 'confirmdialogheader',
506
                'Please confirm that you wish to proceed. This action is not reversible.' => 'confirmdialogbody',
507
                'Cancel' => 'cancellabel',
508
                'Confirm' => 'confirmlabel',
509
                '4.0 International' => 'licenseCC40',
510
                '3.0 Unported' => 'licenseCC30',
511
                '2.5 Generic' => 'licenseCC25',
512
                '2.0 Generic' => 'licenseCC20',
513
                '1.0 Generic' => 'licenseCC10',
514
                'General Public License' => 'licenseGPL',
515
                'Version 3' => 'licenseV3',
516
                'Version 2' => 'licenseV2',
517
                'Version 1' => 'licenseV1',
518
                'CC0 1.0 Universal (CC0 1.0) Public Domain Dedication' => 'licenseCC010',
519
                'CC0 1.0 Universal' => 'licenseCC010U',
520
                'License Version' => 'licenseversion',
521
                'Creative Commons' => 'creativecommons',
522
                'Attribution' => 'ccattribution',
523
                'Attribution (CC BY)' => 'ccattribution',
524
                'Attribution-ShareAlike' => 'ccattributionsa',
525
                'Attribution-ShareAlike (CC BY-SA)' => 'ccattributionsa',
526
                'Attribution-NoDerivs' => 'ccattributionnd',
527
                'Attribution-NoDerivs (CC BY-ND)' => 'ccattributionnd',
528
                'Attribution-NonCommercial' => 'ccattributionnc',
529
                'Attribution-NonCommercial (CC BY-NC)' => 'ccattributionnc',
530
                'Attribution-NonCommercial-ShareAlike' => 'ccattributionncsa',
531
                'Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)' => 'ccattributionncsa',
532
                'Attribution-NonCommercial-NoDerivs' => 'ccattributionncnd',
533
                'Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)' => 'ccattributionncnd',
534
                'Public Domain Dedication' => 'ccpdd',
535
                'Public Domain Dedication (CC0)' => 'ccpdd',
536
                'Years (from)' => 'yearsfrom',
537
                'Years (to)' => 'yearsto',
538
                "Author's name" => 'authorname',
539
                "Author's role" => 'authorrole',
540
                'Editor' => 'editor',
541
                'Licensee' => 'licensee',
542
                'Originator' => 'originator',
543
                'Any additional information about the license' => 'additionallicenseinfo',
544
                'License Extras' => 'licenseextras',
545
                'Changelog' => 'changelog',
546
                'Content Type' => 'contenttype',
547
                'Question' => 'question',
548
                'Date' => 'date',
549
                'Changed by' => 'changedby',
550
                'Description of change' => 'changedescription',
551
                'Photo cropped, text changed, etc.' => 'changeplaceholder',
552
                'Additional Information' => 'additionalinfo',
553
                'Author comments' => 'authorcomments',
554
                'Comments for the editor of the content (This text will not be published as a part of copyright info)' => 'authorcommentsdescription',
555
                'Reuse' => 'reuse',
556
                'Reuse Content' => 'reusecontent',
557
                'Reuse this content.' => 'reusedescription',
558
                'Content is copied to the clipboard' => 'contentcopied',
559
                'Connection lost. Results will be stored and sent when you regain connection.' => 'connectionlost',
560
                'Connection reestablished.' => 'connectionreestablished',
561
                'Attempting to submit stored results.' => 'resubmitscores',
562
                'Your connection to the server was lost' => 'offlinedialogheader',
563
                'We were unable to send information about your completion of this task. Please check your internet connection.' => 'offlinedialogbody',
564
                'Retrying in :num....' => 'offlinedialogretrymessage',
565
                'Retry now' => 'offlinedialogretrybuttonlabel',
566
                'Successfully submitted results.' => 'offlinesuccessfulsubmit',
567
                'Sharing <strong>:title</strong>' => 'maintitle',
568
                'Edit info for <strong>:title</strong>' => 'editinfotitle',
569
                'Back' => 'back',
570
                'Next' => 'next',
571
                'Review info' => 'reviewinfo',
572
                'Share' => 'share',
573
                'Save changes' => 'savechanges',
574
                'Register on the H5P Hub' => 'registeronhub',
575
                'Required Info' => 'requiredinfo',
576
                'Optional Info' => 'optionalinfo',
577
                'Review & Share' => 'reviewandshare',
578
                'Review & Save' => 'reviewandsave',
579
                'Shared' => 'shared',
580
                'Step :step of :total' => 'currentstep',
581
                'All content details can be edited after sharing' => 'sharingnote',
582
                'Select a license for your content' => 'licensedescription',
583
                'Select a license version' => 'licenseversiondescription',
584
                'Disciplines' => 'disciplinelabel',
585
                'You can select multiple disciplines' => 'disciplinedescription',
586
                'You can select up to :numDisciplines disciplines' => 'disciplinelimitreachedmessage',
587
                'Type to search for disciplines' =>'searchplaceholder',
588
                'in' => 'in',
589
                'Dropdown button' => 'dropdownbutton',
590
                'Remove :chip from the list' => 'removechip',
591
                'Add keywords' => 'keywordsplaceholder',
592
                'Keywords' => 'keywords',
593
                'You can add multiple keywords separated by commas. Press "Enter" or "Add" to confirm keywords' => 'keywordsdescription',
594
                'Alt text' => 'alttext',
595
                'Please review the info below before you share' => 'reviewmessage',
596
                'Sub-content (images, questions etc.) will be shared under :license unless otherwise specified in the authoring tool' => 'subcontentwarning',
597
                'Disciplines' => 'disciplines',
598
                'Short description' => 'shortdescription',
599
                'Long description' => 'longdescription',
600
                'Icon' => 'icon',
601
                'Screenshots' => 'screenshots',
602
                'Help me choose a license' => 'helpchoosinglicense',
603
                'Share failed.' => 'sharefailed',
604
                'Editing failed.' => 'editingfailed',
605
                'Something went wrong, please try to share again.' => 'sharetryagain',
606
                'Please wait...' => 'pleasewait',
607
                'Language' => 'language',
608
                'Level' => 'level',
609
                'Short description of your content' => 'shortdescriptionplaceholder',
610
                'Long description of your content' => 'longdescriptionplaceholder',
611
                'Description' => 'description',
612
                '640x480px. If not selected content will use category icon' => 'icondescription',
613
                'Add up to five screenshots of your content' => 'screenshotsdescription',
614
                'Submitted!' => 'submitted',
615
                'Is now submitted to H5P Hub' => 'isnowsubmitted',
616
                'A change has been submited for' => 'changehasbeensubmitted',
617
                'Your content will normally be available in the Hub within one business day.' => 'contentavailable',
618
                'Your content will update soon' => 'contentupdatesoon',
619
                'Content License Info' => 'contentlicensetitle',
620
                'Click on a specific license to get info about proper usage' => 'licensedialogdescription',
621
                'Publisher' => 'publisherfieldtitle',
622
                'This will display as the "Publisher name" on shared content' => 'publisherfielddescription',
623
                'Email Address' => 'emailaddress',
624
                'Publisher description' => 'publisherdescription',
625
                'This will be displayed under "Publisher info" on shared content' => 'publisherdescriptiontext',
626
                'Contact Person' => 'contactperson',
627
                'Phone' => 'phone',
628
                'Address' => 'address',
629
                'City' => 'city',
630
                'Zip' => 'zip',
631
                'Country' => 'country',
632
                'Organization logo or avatar' => 'logouploadtext',
633
                'I accept the <a href=":url" target="_blank">terms of use</a>' => 'acceptterms',
634
                'You have successfully registered an account on the H5P Hub' => 'successfullyregistred',
635
                'You account details can be changed' => 'successfullyregistreddescription',
636
                'here' => 'accountdetailslinktext',
637
                'H5P Hub Registration' => 'registrationtitle',
638
                'An error occurred' => 'registrationfailed',
639
                'We were not able to create an account at this point. Something went wrong. Try again later.' => 'registrationfaileddescription',
640
                ':length is the maximum number of characters' => 'maxlength',
641
                'Keyword already exists!' => 'keywordexists',
642
                'License details' => 'licensedetails',
643
                'Remove' => 'remove',
644
                'Remove image' => 'removeimage',
645
                'Cancel sharing' => 'cancelpublishconfirmationdialogtitle',
646
                'Are you sure you want to cancel the sharing process?' => 'cancelpublishconfirmationdialogdescription',
647
                'No' => 'cancelpublishconfirmationdialogcancelbuttontext',
648
                'Yes' => 'cancelpublishconfirmationdialogconfirmbuttontext',
649
                'Add' => 'add',
650
                'Save account settings' => 'updateregistrationonhub',
651
                'Your H5P Hub account settings have successfully been changed' => 'successfullyupdated',
652
                'One of the files inside the package exceeds the maximum file size allowed. (%file %used > %max)' => 'fileexceedsmaxsize',
653
                'The total size of the unpacked files exceeds the maximum size allowed. (%used > %max)' => 'unpackedfilesexceedsmaxsize',
654
                'Unable to read file from the package: %fileName' => 'couldnotreadfilefromzip',
655
                'Unable to parse JSON from the package: %fileName' => 'couldnotparsejsonfromzip',
656
                'Could not parse post data.' => 'couldnotparsepostdata',
657
                'The mbstring PHP extension is not loaded. H5P needs this to function properly' => 'nombstringexteension',
658
                'Assistive Technologies label' => 'assistivetechnologieslabel',
659
                'Typical age' => 'age',
660
                'The target audience of this content. Possible input formats separated by commas: "1,34-45,-50,59-".' => 'agedescription',
661
                'Invalid input format for Typical age. Possible input formats separated by commas: "1, 34-45, -50, -59-".' => 'invalidage',
662
                'H5P will reach out to the contact person in case there are any issues with the content shared by the publisher. The contact person\'s name or other information will not be published or shared with third parties' => 'contactpersondescription',
663
                'The email address will be used by H5P to reach out to the publisher in case of any issues with the content or in case the publisher needs to recover their account. It will not be published or shared with any third parties' => 'emailaddressdescription',
664
                'Copyrighted material cannot be shared in the H5P Content Hub. If the content is licensed with a OER friendly license like Creative Commons, please choose the appropriate license. If not this content cannot be shared.' => 'copyrightwarning',
665
                'Keywords already exists!' => 'keywordsexists',
666
                'Some of these keywords already exist' => 'somekeywordsexists',
667
            ];
668
            // @codingStandardsIgnoreEnd
669
        }
670
 
671
        // Some strings such as error messages are not translatable, in this case use message
672
        // directly instead of crashing
673
        // @see https://github.com/h5p/h5p-php-library/commit/2bd972168e7b22aaeea2dd13682ced9cf8233452#diff-5ca86cd0514d58be6708beff914aba66R1296.
674
        if (!isset($translationsmap[$message])) {
675
            return $message;
676
        }
677
 
678
        return get_string($translationsmap[$message], 'hvp', $replacements);
679
    }
680
 
681
    /**
682
     * Implements getH5PPath
683
     */
684
    // @codingStandardsIgnoreLine
685
    public function getH5pPath() {
686
        global $CFG;
687
 
688
        return $CFG->dirroot . '/mod/hvp/files';
689
    }
690
 
691
    /**
692
     * Implements getLibraryFileUrl
693
     */
694
    // @codingStandardsIgnoreLine
695
    public function getLibraryFileUrl($libraryfoldername, $fileName) {
696
        $context  = \context_system::instance();
697
        $basepath = view_assets::getsiteroot() . '/';
698
        return "{$basepath}pluginfile.php/{$context->id}/mod_hvp/libraries/{$libraryfoldername}/{$fileName}";
699
    }
700
 
701
    /**
702
     * Implements getUploadedH5PFolderPath
703
     */
704
    // @codingStandardsIgnoreLine
705
    public function getUploadedH5pFolderPath($setpath = null) {
706
        static $path;
707
 
708
        if ($setpath !== null) {
709
            $path = $setpath;
710
        }
711
 
712
        if (!isset($path)) {
713
            throw new \coding_exception('Using getUploadedH5pFolderPath() before path is set');
714
        }
715
 
716
        return $path;
717
    }
718
 
719
    /**
720
     * Implements getUploadedH5PPath
721
     */
722
    // @codingStandardsIgnoreLine
723
    public function getUploadedH5pPath($setpath = null) {
724
        static $path;
725
 
726
        if ($setpath !== null) {
727
            $path = $setpath;
728
        }
729
 
730
        return $path;
731
    }
732
 
733
    /**
734
     * Implements loadLibraries
735
     */
736
    // @codingStandardsIgnoreLine
737
    public function loadLibraries() {
738
        global $DB;
739
 
740
        $results = $DB->get_records_sql(
741
              "SELECT id, machine_name, title, major_version, minor_version,
742
                      patch_version, runnable, restricted
743
                 FROM {hvp_libraries}
744
             ORDER BY title ASC, major_version ASC, minor_version ASC");
745
 
746
        $libraries = array();
747
        foreach ($results as $library) {
748
            $libraries[$library->machine_name][] = $library;
749
        }
750
 
751
        return $libraries;
752
    }
753
 
754
    /**
755
     * @inheritdoc
756
     */
757
    // @codingStandardsIgnoreLine
758
    public function setUnsupportedLibraries($libraries) {
759
        // Not supported.
760
    }
761
 
762
    /**
763
     * Implements getUnsupportedLibraries.
764
     */
765
    // @codingStandardsIgnoreLine
766
    public function getUnsupportedLibraries() {
767
        // Not supported.
768
    }
769
 
770
    /**
771
     * Implements getAdminUrl.
772
     */
773
    // @codingStandardsIgnoreLine
774
    public function getAdminUrl() {
775
        // Not supported.
776
    }
777
 
778
    /**
779
     * @inheritdoc
780
     */
781
    // @codingStandardsIgnoreLine
782
    public function getLibraryId($machinename, $majorversion = null, $minorversion = null) {
783
        global $DB;
784
 
785
        // Look for specific library.
786
        $sqlwhere = 'WHERE machine_name = ?';
787
        $sqlargs = array($machinename);
788
 
789
        if ($majorversion !== null) {
790
            // Look for major version.
791
            $sqlwhere .= ' AND major_version = ?';
792
            $sqlargs[] = $majorversion;
793
            if ($minorversion !== null) {
794
                // Look for minor version.
795
                $sqlwhere .= ' AND minor_version = ?';
796
                $sqlargs[] = $minorversion;
797
            }
798
        }
799
 
800
        // Get the lastest version which matches the input parameters.
801
        $libraries = $DB->get_records_sql("
802
                SELECT id
803
                  FROM {hvp_libraries}
804
          {$sqlwhere}
805
              ORDER BY major_version DESC,
806
                       minor_version DESC,
807
                       patch_version DESC
808
                ", $sqlargs, 0, 1);
809
        if ($libraries) {
810
            $library = reset($libraries);
811
            return $library ? $library->id : false;
812
        } else {
813
            return false;
814
        }
815
    }
816
 
817
    /**
818
     * Implements isPatchedLibrary
819
     */
820
    // @codingStandardsIgnoreLine
821
    public function isPatchedLibrary($library) {
822
        global $DB, $CFG;
823
 
824
        if (isset($CFG->mod_hvp_dev) && $CFG->mod_hvp_dev) {
825
            // Makes sure libraries are updated, patch version does not matter.
826
            return true;
827
        }
828
 
829
        $operator = $this->isInDevMode() ? '<=' : '<';
830
        $library = $DB->get_record_sql(
831
                'SELECT id
832
                  FROM {hvp_libraries}
833
                    WHERE machine_name = ?
834
                    AND major_version = ?
835
                    AND minor_version = ?
836
                    AND patch_version ' . $operator . ' ?',
837
                  array($library['machineName'],
838
                  $library['majorVersion'],
839
                  $library['minorVersion'],
840
                  $library['patchVersion'])
841
        );
842
 
843
        return $library ? true : false;
844
    }
845
 
846
    /**
847
     * Implements isInDevMode
848
     */
849
    // @codingStandardsIgnoreLine
850
    public function isInDevMode() {
851
        return false; // Not supported (Files in moodle not editable).
852
    }
853
 
854
    /**
855
     * Implements mayUpdateLibraries
856
     */
857
    // @codingStandardsIgnoreLine
858
    public function mayUpdateLibraries($allow = false) {
859
        static $override;
860
 
861
        // Allow overriding the permission check. Needed when installing.
862
        // since caps hasn't been set.
863
        if ($allow) {
864
            $override = true;
865
        }
866
        if ($override) {
867
            return true;
868
        }
869
 
870
        // Check permissions.
871
        $context = \context_system::instance();
872
        if (!has_capability('mod/hvp:updatelibraries', $context)) {
873
            return false;
874
        }
875
 
876
        return true;
877
    }
878
 
879
    /**
880
     * Implements getLibraryUsage
881
     *
882
     * Get number of content/nodes using a library, and the number of
883
     * dependencies to other libraries
884
     *
885
     * @param int $id
886
     * @param boolean $skipcontent Optional. Set as true to get number of content instances for library.
887
     * @return array The array contains two elements, keyed by 'content' and 'libraries'.
888
     *               Each element contains a number
889
     */
890
    // @codingStandardsIgnoreLine
891
    public function getLibraryUsage($id, $skipcontent = false) {
892
        global $DB;
893
 
894
        if ($skipcontent) {
895
            $content = -1;
896
        } else {
897
            $content = intval($DB->get_field_sql(
898
                "SELECT COUNT(distinct c.id)
899
                FROM {hvp_libraries} l
900
                JOIN {hvp_contents_libraries} cl ON l.id = cl.library_id
901
                JOIN {hvp} c ON cl.hvp_id = c.id
902
                WHERE l.id = ?", array($id)
903
            ));
904
        }
905
 
906
        $libraries = intval($DB->get_field_sql(
907
            "SELECT COUNT(*)
908
            FROM {hvp_libraries_libraries}
909
            WHERE required_library_id = ?", array($id)
910
        ));
911
 
912
        return array(
913
            'content' => $content,
914
            'libraries' => $libraries,
915
        );
916
    }
917
 
918
    /**
919
     * Implements getLibraryContentCount
920
     */
921
    // @codingStandardsIgnoreLine
922
    public function getLibraryContentCount() {
923
        global $DB;
924
        $contentcount = array();
925
 
926
        // Count content using the same content type.
927
        $res = $DB->get_records_sql(
928
          "SELECT c.main_library_id,
929
                  l.machine_name,
930
                  l.major_version,
931
                  l.minor_version,
932
                  c.count
933
             FROM (SELECT main_library_id,
934
                          count(id) as count
935
                     FROM {hvp}
936
                 GROUP BY main_library_id) c,
937
                 {hvp_libraries} l
938
            WHERE c.main_library_id = l.id"
939
        );
940
 
941
        // Extract results.
942
        foreach ($res as $lib) {
943
            $contentcount["{$lib->machine_name} {$lib->major_version}.{$lib->minor_version}"] = $lib->count;
944
        }
945
 
946
        return $contentcount;
947
    }
948
 
949
    /**
950
     * Implements saveLibraryData
951
     */
952
    // @codingStandardsIgnoreLine
953
    public function saveLibraryData(&$librarydata, $new = true) {
954
        global $DB;
955
 
956
        // Some special properties needs some checking and converting before they can be saved.
957
        $preloadedjs = $this->pathsToCsv($librarydata, 'preloadedJs');
958
        $preloadedcss = $this->pathsToCsv($librarydata, 'preloadedCss');
959
        $droplibrarycss = '';
960
 
961
        if (isset($librarydata['dropLibraryCss'])) {
962
            $libs = array();
963
            foreach ($librarydata['dropLibraryCss'] as $lib) {
964
                $libs[] = $lib['machineName'];
965
            }
966
            $droplibrarycss = implode(', ', $libs);
967
        }
968
 
969
        $embedtypes = '';
970
        if (isset($librarydata['embedTypes'])) {
971
            $embedtypes = implode(', ', $librarydata['embedTypes']);
972
        }
973
        if (!isset($librarydata['semantics'])) {
974
            $librarydata['semantics'] = '';
975
        }
976
        if (!isset($librarydata['fullscreen'])) {
977
            $librarydata['fullscreen'] = 0;
978
        }
979
        if (!isset($librarydata['hasIcon'])) {
980
            $librarydata['hasIcon'] = 0;
981
        }
982
        // TODO: Can we move the above code to H5PCore? It's the same for multiple
983
        // implementations. Perhaps core can update the data objects before calling
984
        // this function?
985
        // I think maybe it's best to do this when classes are created for
986
        // library, content, etc.
987
 
988
        $library = (object) array(
989
            'title' => $librarydata['title'],
990
            'machine_name' => $librarydata['machineName'],
991
            'major_version' => $librarydata['majorVersion'],
992
            'minor_version' => $librarydata['minorVersion'],
993
            'patch_version' => $librarydata['patchVersion'],
994
            'runnable' => $librarydata['runnable'],
995
            'fullscreen' => $librarydata['fullscreen'],
996
            'embed_types' => $embedtypes,
997
            'preloaded_js' => $preloadedjs,
998
            'preloaded_css' => $preloadedcss,
999
            'drop_library_css' => $droplibrarycss,
1000
            'semantics' => $librarydata['semantics'],
1001
            'has_icon' => $librarydata['hasIcon'],
1002
            'metadata_settings' => $librarydata['metadataSettings'],
1003
            'add_to' => isset($librarydata['addTo']) ? json_encode($librarydata['addTo']) : null,
1004
        );
1005
 
1006
        if ($new) {
1007
            // Create new library and keep track of id.
1008
            $library->id = $DB->insert_record('hvp_libraries', $library);
1009
            $librarydata['libraryId'] = $library->id;
1010
        } else {
1011
            // Update library data.
1012
            $library->id = $librarydata['libraryId'];
1013
 
1014
            // Save library data.
1015
            $DB->update_record('hvp_libraries', (object) $library);
1016
 
1017
            // Remove old dependencies.
1018
            $this->deleteLibraryDependencies($librarydata['libraryId']);
1019
        }
1020
 
1021
        // Log library successfully installed/upgraded.
1022
        new \mod_hvp\event(
1023
              'library', ($new ? 'create' : 'update'),
1024
              null, null,
1025
              $library->machine_name, $library->major_version . '.' . $library->minor_version
1026
        );
1027
 
1028
        // Update library translations.
1029
        $DB->delete_records('hvp_libraries_languages', array('library_id' => $librarydata['libraryId']));
1030
        if (isset($librarydata['language'])) {
1031
            foreach ($librarydata['language'] as $languagecode => $languagejson) {
1032
                $DB->insert_record('hvp_libraries_languages', array(
1033
                    'library_id' => $librarydata['libraryId'],
1034
                    'language_code' => $languagecode,
1035
                    'language_json' => $languagejson,
1036
                ));
1037
            }
1038
        }
1039
    }
1040
 
1041
    /**
1042
     * Convert list of file paths to csv
1043
     *
1044
     * @param array $librarydata
1045
     *  Library data as found in library.json files
1046
     * @param string $key
1047
     *  Key that should be found in $librarydata
1048
     * @return string
1049
     *  file paths separated by ', '
1050
     */
1051
    // @codingStandardsIgnoreLine
1052
    private function pathsToCsv($librarydata, $key) {
1053
        if (isset($librarydata[$key])) {
1054
            $paths = array();
1055
            foreach ($librarydata[$key] as $file) {
1056
                $paths[] = $file['path'];
1057
            }
1058
            return implode(', ', $paths);
1059
        }
1060
        return '';
1061
    }
1062
 
1063
    /**
1064
     * Implements lockDependencyStorage
1065
     */
1066
    // @codingStandardsIgnoreLine
1067
    public function lockDependencyStorage() {
1068
        // Library development mode not supported.
1069
    }
1070
 
1071
    /**
1072
     * Implements unlockDependencyStorage
1073
     */
1074
    // @codingStandardsIgnoreLine
1075
    public function unlockDependencyStorage() {
1076
        // Library development mode not supported.
1077
    }
1078
 
1079
    /**
1080
     * Implements deleteLibrary
1081
     */
1082
    // @codingStandardsIgnoreLine
1083
    public function deleteLibrary($library) {
1084
        global $DB;
1085
 
1086
        // Delete library files.
1087
        $librarybase = $this->getH5pPath() . '/libraries/';
1088
        $libname = "{$library->name}-{$library->major_version}.{$library->minor_version}";
1089
        \H5PCore::deleteFileTree("{$librarybase}{$libname}");
1090
 
1091
        // Remove library data from database.
1092
        $DB->delete('hvp_libraries_libraries', array('library_id' => $library->id));
1093
        $DB->delete('hvp_libraries_languages', array('library_id' => $library->id));
1094
        $DB->delete('hvp_libraries', array('id' => $library->id));
1095
    }
1096
 
1097
    /**
1098
     * Implements saveLibraryDependencies
1099
     *
1100
     * @inheritdoc
1101
     */
1102
    // @codingStandardsIgnoreLine
1103
    public function saveLibraryDependencies($libraryid, $dependencies, $dependencytype) {
1104
        global $DB;
1105
 
1106
        foreach ($dependencies as $dependency) {
1107
            // Find dependency library.
1108
            $dependencylibrary = $DB->get_record('hvp_libraries', array(
1109
                'machine_name' => $dependency['machineName'],
1110
                'major_version' => $dependency['majorVersion'],
1111
                'minor_version' => $dependency['minorVersion']
1112
            ));
1113
 
1114
            // Create relation.
1115
            $DB->insert_record('hvp_libraries_libraries', array(
1116
                'library_id' => $libraryid,
1117
                'required_library_id' => $dependencylibrary->id,
1118
                'dependency_type' => $dependencytype
1119
            ));
1120
        }
1121
    }
1122
 
1123
    /**
1124
     * @inheritdoc
1125
     */
1126
    // @codingStandardsIgnoreLine
1127
    public function updateContent($content, $contentmainid = null) {
1128
        global $DB;
1129
 
1130
        if (!isset($content['disable'])) {
1131
            $content['disable'] = \H5PCore::DISABLE_NONE;
1132
        }
1133
 
1134
        $data = array_merge(\H5PMetadata::toDBArray($content['metadata'], false), array(
1135
            'name' => isset($content['metadata']->title) ? $content['metadata']->title : $content['name'],
1136
            'course' => $content['course'],
1137
            'intro' => $content['intro'],
1138
            'introformat' => $content['introformat'],
1139
            'json_content' => $content['params'],
1140
            'embed_type' => 'div',
1141
            'main_library_id' => $content['library']['libraryId'],
1142
            'filtered' => '',
1143
            'disable' => $content['disable'],
1144
            'timemodified' => time(),
1145
        ));
1146
 
1147
        if (isset($content[ 'completionpass'])) {
1148
            $data[ 'completionpass' ] = $content[ 'completionpass' ];
1149
        }
1150
 
1151
        if (!isset($content['id'])) {
1152
            $data['slug'] = '';
1153
            $data['timecreated'] = $data['timemodified'];
1154
            $eventtype = 'create';
1155
            $id = $DB->insert_record('hvp', $data);
1156
        } else {
1157
            $data['id'] = $content['id'];
1158
            $data['synced'] = \H5PContentHubSyncStatus::NOT_SYNCED;
1159
            $DB->update_record('hvp', $data);
1160
            $eventtype = 'update';
1161
            $id = $data['id'];
1162
        }
1163
 
1164
        // Log content create/update/upload.
1165
        if (!empty($content['uploaded'])) {
1166
            $eventtype .= ' upload';
1167
        }
1168
        new \mod_hvp\event(
1169
                'content', $eventtype,
1170
                $id, $content['name'],
1171
                $content['library']['machineName'],
1172
                $content['library']['majorVersion'] . '.' . $content['library']['minorVersion']
1173
        );
1174
 
1175
        return $id;
1176
    }
1177
 
1178
    /**
1179
     * @inheritdoc
1180
     */
1181
    // @codingStandardsIgnoreLine
1182
    public function insertContent($content, $contentmainid = null) {
1183
        return $this->updateContent($content);
1184
    }
1185
 
1186
    /**
1187
     * @inheritdoc
1188
     */
1189
    // @codingStandardsIgnoreLine
1190
    public function resetContentUserData($contentid) {
1191
        global $DB;
1192
 
1193
        // Reset user data for this content.
1194
        $DB->execute("UPDATE {hvp_content_user_data}
1195
                         SET data = 'RESET'
1196
                       WHERE hvp_id = ?
1197
                         AND delete_on_content_change = 1",
1198
                     array($contentid));
1199
    }
1200
 
1201
    /**
1202
     * @inheritdoc
1203
     */
1204
    // @codingStandardsIgnoreLine
1205
    public function getWhitelist($islibrary, $defaultcontentwhitelist, $defaultlibrarywhitelist) {
1206
        return $defaultcontentwhitelist . ($islibrary ? ' ' . $defaultlibrarywhitelist : '');
1207
    }
1208
 
1209
    /**
1210
     * @inheritdoc
1211
     */
1212
    // @codingStandardsIgnoreLine
1213
    public function copyLibraryUsage($contentid, $copyfromid, $contentmainid = null) {
1214
        global $DB;
1215
 
1216
        $libraryusage = $DB->get_record('hvp_contents_libraries', array(
1217
            'id' => $copyfromid
1218
        ));
1219
 
1220
        $libraryusage->id = $contentid;
1221
        $DB->insert_record_raw('hvp_contents_libraries', (array)$libraryusage, false, false, true);
1222
 
1223
        // TODO: This must be verified at a later time.
1224
        // Currently in Moodle copyLibraryUsage() will never be called.
1225
    }
1226
 
1227
    /**
1228
     * @inheritdoc
1229
     */
1230
    // @codingStandardsIgnoreLine
1231
    public function loadLibrarySemantics($name, $majorversion, $minorversion) {
1232
        global $DB;
1233
 
1234
        $semantics = $DB->get_field_sql(
1235
            "SELECT semantics
1236
            FROM {hvp_libraries}
1237
            WHERE machine_name = ?
1238
            AND major_version = ?
1239
            AND minor_version = ?",
1240
            array($name, $majorversion, $minorversion));
1241
 
1242
        return ($semantics === false ? null : $semantics);
1243
    }
1244
 
1245
    /**
1246
     * @inheritdoc
1247
     */
1248
    // @codingStandardsIgnoreLine
1249
    public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) {
1250
        global $PAGE;
1251
 
1252
        $PAGE->set_context(null);
1253
 
1254
        $renderer = $PAGE->get_renderer('mod_hvp');
1255
        $renderer->hvp_alter_semantics($semantics, $name, $majorversion, $minorversion);
1256
    }
1257
 
1258
    /**
1259
     * Implements loadContent
1260
     */
1261
    // @codingStandardsIgnoreLine
1262
    public function loadContent($id) {
1263
        global $DB;
1264
 
1265
        $data = $DB->get_record_sql("
1266
          SELECT
1267
            hc.id,
1268
            hc.name,
1269
            hc.intro,
1270
            hc.introformat,
1271
            hc.json_content,
1272
            hc.filtered,
1273
            hc.slug,
1274
            hc.embed_type,
1275
            hc.disable,
1276
            hl.id AS library_id,
1277
            hl.machine_name,
1278
            hl.major_version,
1279
            hl.minor_version,
1280
            hl.embed_types,
1281
            hl.fullscreen,
1282
            hc.name as title,
1283
            hc.authors,
1284
            hc.source,
1285
            hc.license,
1286
            hc.license_version,
1287
            hc.license_extras,
1288
            hc.year_from,
1289
            hc.year_to,
1290
            hc.changes,
1291
            hc.author_comments,
1292
            hc.default_language,
1293
            hc.shared,
1294
            hc.synced,
1295
            hc.hub_id,
1296
            hc.a11y_title
1297
          FROM {hvp} hc
1298
          JOIN {hvp_libraries} hl ON hl.id = hc.main_library_id
1299
          WHERE hc.id = ?", array($id)
1300
        );
1301
 
1302
        // Return null if not found.
1303
        if ($data === false) {
1304
            return null;
1305
        }
1306
 
1307
        // Some databases do not support camelCase, so we need to manually
1308
        // map the values to the camelCase names used by the H5P core.
1309
        $content = array(
1310
            'id' => $data->id,
1311
            'title' => $data->name,
1312
            'intro' => $data->intro,
1313
            'introformat' => $data->introformat,
1314
            'params' => $data->json_content,
1315
            'filtered' => $data->filtered,
1316
            'slug' => $data->slug,
1317
            'embedType' => $data->embed_type,
1318
            'disable' => $data->disable,
1319
            'shared' => $data->shared,
1320
            'synced' => $data->synced,
1321
            'contentHubId' => $data->hub_id,
1322
            'libraryId' => $data->library_id,
1323
            'libraryName' => $data->machine_name,
1324
            'libraryMajorVersion' => $data->major_version,
1325
            'libraryMinorVersion' => $data->minor_version,
1326
            'libraryEmbedTypes' => $data->embed_types,
1327
            'libraryFullscreen' => $data->fullscreen,
1328
        );
1329
 
1330
        $metadatafields = [
1331
            'title',
1332
            'authors',
1333
            'source',
1334
            'license',
1335
            'license_version',
1336
            'license_extras',
1337
            'year_from',
1338
            'year_to',
1339
            'changes',
1340
            'author_comments',
1341
            'default_language',
1342
            'a11y_title'
1343
        ];
1344
 
1345
        $content['metadata'] = \H5PCore::snakeToCamel(
1346
            array_reduce($metadatafields, function ($array, $field) use ($data) {
1347
                if (isset($data->$field)) {
1348
                    $value = $data->$field;
1349
                    // Decode json fields.
1350
                    if (in_array($field, ['authors', 'changes'])) {
1351
                        $value = json_decode($data->$field);
1352
                    }
1353
 
1354
                    $array[$field] = $value;
1355
                }
1356
                return $array;
1357
            }, [])
1358
        );
1359
 
1360
        return $content;
1361
    }
1362
 
1363
    /**
1364
     * Implements loadContentDependencies
1365
     */
1366
    // @codingStandardsIgnoreLine
1367
    public function loadContentDependencies($id, $type = null) {
1368
        global $DB;
1369
 
1370
        $query = "SELECT hcl.id AS unidepid
1371
                       , hl.id
1372
                       , hl.machine_name
1373
                       , hl.major_version
1374
                       , hl.minor_version
1375
                       , hl.patch_version
1376
                       , hl.preloaded_css
1377
                       , hl.preloaded_js
1378
                       , hcl.drop_css
1379
                       , hcl.dependency_type
1380
                   FROM {hvp_contents_libraries} hcl
1381
                   JOIN {hvp_libraries} hl ON hcl.library_id = hl.id
1382
                  WHERE hcl.hvp_id = ?";
1383
        $queryargs = array($id);
1384
 
1385
        if ($type !== null) {
1386
            $query .= " AND hcl.dependency_type = ?";
1387
            $queryargs[] = $type;
1388
        }
1389
 
1390
        $query .= " ORDER BY hcl.weight";
1391
        $data = $DB->get_records_sql($query, $queryargs);
1392
 
1393
        $dependencies = array();
1394
        foreach ($data as $dependency) {
1395
            unset($dependency->unidepid);
1396
            $dependencies[$dependency->machine_name] = \H5PCore::snakeToCamel($dependency);
1397
        }
1398
 
1399
        return $dependencies;
1400
    }
1401
 
1402
    /**
1403
     * @inheritdoc
1404
     */
1405
    // @codingStandardsIgnoreLine
1406
    public function getOption($name, $default = false) {
1407
        $value = get_config('mod_hvp', $name);
1408
        if ($value === false) {
1409
            return $default;
1410
        }
1411
        return $value;
1412
    }
1413
 
1414
    /**
1415
     * Implements setOption().
1416
     */
1417
    // @codingStandardsIgnoreLine
1418
    public function setOption($name, $value) {
1419
        set_config($name, $value, 'mod_hvp');
1420
    }
1421
 
1422
    /**
1423
     * Implements updateContentFields().
1424
     */
1425
    // @codingStandardsIgnoreLine
1426
    public function updateContentFields($id, $fields) {
1427
        global $DB;
1428
 
1429
        $content = new \stdClass();
1430
        $content->id = $id;
1431
 
1432
        foreach ($fields as $name => $value) {
1433
            $content->$name = $value;
1434
        }
1435
 
1436
        $DB->update_record('hvp', $content);
1437
    }
1438
 
1439
    /**
1440
     * Implements deleteLibraryDependencies
1441
     */
1442
    // @codingStandardsIgnoreLine
1443
    public function deleteLibraryDependencies($libraryid) {
1444
        global $DB;
1445
 
1446
        $DB->delete_records('hvp_libraries_libraries', array('library_id' => $libraryid));
1447
    }
1448
 
1449
    /**
1450
     * Implements deleteContentData
1451
     */
1452
    // @codingStandardsIgnoreLine
1453
    public function deleteContentData($contentid) {
1454
        global $DB;
1455
 
1456
        // Remove content.
1457
        $DB->delete_records('hvp', array('id' => $contentid));
1458
 
1459
        // Remove content library dependencies.
1460
        $this->deleteLibraryUsage($contentid);
1461
 
1462
        // Remove user data for content.
1463
        $DB->delete_records('hvp_content_user_data', array('hvp_id' => $contentid));
1464
    }
1465
 
1466
    /**
1467
     * Implements deleteLibraryUsage
1468
     */
1469
    // @codingStandardsIgnoreLine
1470
    public function deleteLibraryUsage($contentid) {
1471
        global $DB;
1472
 
1473
        $DB->delete_records('hvp_contents_libraries', array('hvp_id' => $contentid));
1474
    }
1475
 
1476
    /**
1477
     * @inheritdoc
1478
     */
1479
    // @codingStandardsIgnoreLine
1480
    public function saveLibraryUsage($contentid, $librariesinuse) {
1481
        global $DB;
1482
 
1483
        $droplibrarycsslist = array();
1484
        foreach ($librariesinuse as $dependency) {
1485
            if (!empty($dependency['library']['dropLibraryCss'])) {
1486
                $droplibrarycsslist = array_merge($droplibrarycsslist, explode(', ', $dependency['library']['dropLibraryCss']));
1487
            }
1488
        }
1489
        // TODO: Consider moving the above code to core. Same for all impl.
1490
 
1491
        foreach ($librariesinuse as $dependency) {
1492
            $dropcss = in_array($dependency['library']['machineName'], $droplibrarycsslist) ? 1 : 0;
1493
            $DB->insert_record('hvp_contents_libraries', array(
1494
                'hvp_id' => $contentid,
1495
                'library_id' => $dependency['library']['libraryId'],
1496
                'dependency_type' => $dependency['type'],
1497
                'drop_css' => $dropcss,
1498
                'weight' => $dependency['weight']
1499
            ));
1500
        }
1501
    }
1502
 
1503
    /**
1504
     * Implements loadLibrary
1505
     */
1506
    // @codingStandardsIgnoreLine
1507
    public function loadLibrary($machinename, $majorversion, $minorversion) {
1508
        global $DB;
1509
 
1510
        $library = $DB->get_record('hvp_libraries', array(
1511
            'machine_name' => $machinename,
1512
            'major_version' => $majorversion,
1513
            'minor_version' => $minorversion
1514
        ));
1515
 
1516
        $librarydata = array(
1517
            'libraryId' => $library->id,
1518
            'machineName' => $library->machine_name,
1519
            'title' => $library->title,
1520
            'majorVersion' => $library->major_version,
1521
            'minorVersion' => $library->minor_version,
1522
            'patchVersion' => $library->patch_version,
1523
            'embedTypes' => $library->embed_types,
1524
            'preloadedJs' => $library->preloaded_js,
1525
            'preloadedCss' => $library->preloaded_css,
1526
            'dropLibraryCss' => $library->drop_library_css,
1527
            'fullscreen' => $library->fullscreen,
1528
            'runnable' => $library->runnable,
1529
            'semantics' => $library->semantics,
1530
            'restricted' => $library->restricted,
1531
            'hasIcon' => $library->has_icon
1532
        );
1533
 
1534
        $dependencies = $DB->get_records_sql(
1535
                'SELECT hl.id, hl.machine_name, hl.major_version, hl.minor_version, hll.dependency_type
1536
                   FROM {hvp_libraries_libraries} hll
1537
                   JOIN {hvp_libraries} hl ON hll.required_library_id = hl.id
1538
                  WHERE hll.library_id = ?', array($library->id));
1539
        foreach ($dependencies as $dependency) {
1540
            $librarydata[$dependency->dependency_type . 'Dependencies'][] = array(
1541
                'machineName' => $dependency->machine_name,
1542
                'majorVersion' => $dependency->major_version,
1543
                'minorVersion' => $dependency->minor_version
1544
            );
1545
        }
1546
 
1547
        return $librarydata;
1548
    }
1549
 
1550
    /**
1551
     * Implements clearFilteredParameters().
1552
     *
1553
     * @param array $libraryids array of library ids
1554
     *
1555
     * @throws \dml_exception
1556
     * @throws \coding_exception
1557
     */
1558
    // @codingStandardsIgnoreLine
1559
    public function clearFilteredParameters($libraryids) {
1560
        global $DB;
1561
        if (empty($libraryids)) {
1562
            return;
1563
        }
1564
 
1565
        list($insql, $inparams) = $DB->get_in_or_equal($libraryids);
1566
        $DB->execute("
1567
            UPDATE {hvp}
1568
            SET filtered = null
1569
            WHERE main_library_id $insql",
1570
            $inparams
1571
        );
1572
    }
1573
 
1574
    /**
1575
     * Implements getNumNotFiltered().
1576
     */
1577
    // @codingStandardsIgnoreLine
1578
    public function getNumNotFiltered() {
1579
        global $DB;
1580
 
1581
        return (int) $DB->get_field_sql(
1582
                "SELECT COUNT(id)
1583
                   FROM {hvp}
1584
                  WHERE " . $DB->sql_compare_text('filtered') . " = ''");
1585
    }
1586
 
1587
    /**
1588
     * Implements getNumContent().
1589
     */
1590
    // @codingStandardsIgnoreLine
1591
    public function getNumContent($libraryid, $skip = NULL) {
1592
        global $DB;
1593
        $skipquery = empty($skip) ? '' : " AND id NOT IN ($skip)";
1594
 
1595
        return (int) $DB->get_field_sql(
1596
                "SELECT COUNT(id) FROM {hvp} WHERE main_library_id = ?{$skipquery}",
1597
                array($libraryid));
1598
    }
1599
 
1600
    /**
1601
     * Implements isContentSlugAvailable
1602
     */
1603
    // @codingStandardsIgnoreLine
1604
    public function isContentSlugAvailable($slug) {
1605
        global $DB;
1606
 
1607
        return !$DB->get_records_sql("SELECT id, slug FROM {hvp} WHERE slug = ?", array($slug));
1608
    }
1609
 
1610
    /**
1611
     * Implements saveCachedAssets
1612
     */
1613
    // @codingStandardsIgnoreLine
1614
    public function saveCachedAssets($key, $libraries) {
1615
        global $DB;
1616
 
1617
        foreach ($libraries as $library) {
1618
            $cachedasset = (object) array(
1619
                'library_id' => $library['id'],
1620
                'hash' => $key
1621
            );
1622
            $DB->insert_record('hvp_libraries_cachedassets', $cachedasset);
1623
        }
1624
    }
1625
 
1626
    /**
1627
     * Implements deleteCachedAssets
1628
     */
1629
    // @codingStandardsIgnoreLine
1630
    public function deleteCachedAssets($libraryid) {
1631
        global $DB;
1632
 
1633
        // Get all the keys so we can remove the files.
1634
        $results = $DB->get_records_sql(
1635
                'SELECT hash
1636
                   FROM {hvp_libraries_cachedassets}
1637
                  WHERE library_id = ?',
1638
                array($libraryid));
1639
 
1640
        // Remove all invalid keys.
1641
        $hashes = array();
1642
        foreach ($results as $key) {
1643
            $hashes[] = $key->hash;
1644
            $DB->delete_records('hvp_libraries_cachedassets', array('hash' => $key->hash));
1645
        }
1646
 
1647
        return $hashes;
1648
    }
1649
 
1650
    /**
1651
     * Implements getLibraryStats
1652
     */
1653
    // @codingStandardsIgnoreLine
1654
    public function getLibraryStats($type) {
1655
        global $DB;
1656
        $count = array();
1657
 
1658
        // Get the counts for the given type of event.
1659
        $records = $DB->get_records_sql(
1660
                "SELECT id,
1661
                        library_name AS name,
1662
                        library_version AS version,
1663
                        num
1664
                   FROM {hvp_counters}
1665
                  WHERE type = ?",
1666
                array($type));
1667
 
1668
        // Extract num from records.
1669
        foreach ($records as $library) {
1670
            $count[$library->name . ' ' . $library->version] = $library->num;
1671
        }
1672
 
1673
        return $count;
1674
    }
1675
 
1676
    /**
1677
     * Implements getNumAuthors
1678
     */
1679
    // @codingStandardsIgnoreLine
1680
    public function getNumAuthors() {
1681
        global $DB;
1682
 
1683
        // Get number of unique courses using H5P.
1684
        return intval($DB->get_field_sql(
1685
                "SELECT COUNT(DISTINCT course)
1686
                   FROM {hvp}"
1687
        ));
1688
    }
1689
 
1690
    /**
1691
     * @inheritdoc
1692
     */
1693
    // @codingStandardsIgnoreLine
1694
    public function afterExportCreated($content, $filename) {
1695
    }
1696
 
1697
    /**
1698
     * Implements hasPermission
1699
     * @method hasPermission
1700
     * @param  \H5PPermission $permission
1701
     * @param  int $cmid context module id
1702
     * @return boolean
1703
     */
1704
    // @codingStandardsIgnoreLine
1705
    public function hasPermission($permission, $cmid = null) {
1706
        switch ($permission) {
1707
            case \H5PPermission::DOWNLOAD_H5P:
1708
            case \H5PPermission::COPY_H5P:
1709
                $cmcontext = \context_module::instance($cmid);
1710
                return has_capability('mod/hvp:getexport', $cmcontext);
1711
            case \H5PPermission::CREATE_RESTRICTED:
1712
                return has_capability('mod/hvp:userestrictedlibraries', $this->getajaxcoursecontext());
1713
            case \H5PPermission::UPDATE_LIBRARIES:
1714
                $context = \context_system::instance();
1715
                return has_capability('mod/hvp:updatelibraries', $context);
1716
            case \H5PPermission::INSTALL_RECOMMENDED:
1717
                return has_capability('mod/hvp:installrecommendedh5plibraries', $this->getajaxcoursecontext());
1718
            case \H5PPermission::EMBED_H5P:
1719
                $cmcontext = \context_module::instance($cmid);
1720
                return has_capability('mod/hvp:getembedcode', $cmcontext);
1721
        }
1722
        return false;
1723
    }
1724
 
1725
    /**
1726
     * Gets course context in AJAX
1727
     *
1728
     * @return bool|\context|\context_course
1729
     */
1730
    private function getajaxcoursecontext() {
1731
        $context = \context::instance_by_id(required_param('contextId', PARAM_RAW));
1732
        if ($context->contextlevel === CONTEXT_COURSE) {
1733
            return $context;
1734
        }
1735
 
1736
        return $context->get_course_context();
1737
    }
1738
 
1739
    /**
1740
     * Replaces existing content type cache with the one passed in
1741
     *
1742
     * @param object $contenttypecache Json with an array called 'libraries'
1743
     *  containing the new content type cache that should replace the old one.
1744
     */
1745
    // @codingStandardsIgnoreLine
1746
    public function replaceContentTypeCache($contenttypecache) {
1747
        global $DB;
1748
 
1749
        // Replace existing cache.
1750
        $DB->delete_records('hvp_libraries_hub_cache');
1751
        foreach ($contenttypecache->contentTypes as $ct) {
1752
            $DB->insert_record('hvp_libraries_hub_cache', (object) array(
1753
                'machine_name'      => $ct->id,
1754
                'major_version'     => $ct->version->major,
1755
                'minor_version'     => $ct->version->minor,
1756
                'patch_version'     => $ct->version->patch,
1757
                'h5p_major_version' => $ct->coreApiVersionNeeded->major,
1758
                'h5p_minor_version' => $ct->coreApiVersionNeeded->minor,
1759
                'title'             => $ct->title,
1760
                'summary'           => $ct->summary,
1761
                'description'       => $ct->description,
1762
                'icon'              => $ct->icon,
1763
                'created_at'        => (new \DateTime($ct->createdAt))->getTimestamp(),
1764
                'updated_at'        => (new \DateTime($ct->updatedAt))->getTimestamp(),
1765
                'is_recommended'    => $ct->isRecommended === true ? 1 : 0,
1766
                'popularity'        => $ct->popularity,
1767
                'screenshots'       => json_encode($ct->screenshots),
1768
                'license'           => json_encode(isset($ct->license) ? $ct->license : array()),
1769
                'example'           => $ct->example,
1770
                'tutorial'          => isset($ct->tutorial) ? $ct->tutorial : '',
1771
                'keywords'          => json_encode(isset($ct->keywords) ? $ct->keywords : array()),
1772
                'categories'        => json_encode(isset($ct->categories) ? $ct->categories : array()),
1773
                'owner'             => $ct->owner
1774
            ), false, true);
1775
        }
1776
    }
1777
 
1778
    /**
1779
     * Implements loadAddons
1780
     */
1781
    // @codingStandardsIgnoreLine
1782
    public function loadAddons() {
1783
        global $DB;
1784
        $addons = array();
1785
 
1786
        $records = $DB->get_records_sql(
1787
                "SELECT l1.id AS library_id,
1788
                        l1.machine_name,
1789
                        l1.major_version,
1790
                        l1.minor_version,
1791
                        l1.patch_version,
1792
                        l1.add_to,
1793
                        l1.preloaded_js,
1794
                        l1.preloaded_css
1795
                   FROM {hvp_libraries} l1
1796
              LEFT JOIN {hvp_libraries} l2
1797
                     ON l1.machine_name = l2.machine_name
1798
                    AND (l1.major_version < l2.major_version
1799
                         OR (l1.major_version = l2.major_version
1800
                             AND l1.minor_version < l2.minor_version))
1801
                  WHERE l1.add_to IS NOT NULL
1802
                    AND l2.machine_name IS NULL");
1803
 
1804
        // NOTE: These are treated as library objects but are missing the following properties:
1805
        // title, embed_types, drop_library_css, fullscreen, runnable, semantics, has_icon.
1806
 
1807
        // Extract num from records.
1808
        foreach ($records as $addon) {
1809
            $addons[] = \H5PCore::snakeToCamel($addon);
1810
        }
1811
 
1812
        return $addons;
1813
    }
1814
 
1815
    /**
1816
     * Implements getLibraryConfig
1817
     */
1818
    // @codingStandardsIgnoreLine
1819
    public function getLibraryConfig($libraries = null) {
1820
        global $CFG;
1821
        return (isset($CFG->mod_hvp_library_config) ? $CFG->mod_hvp_library_config : null);
1822
    }
1823
 
1824
    /**
1825
     * Implements libraryHasUpgrade
1826
     */
1827
    // @codingStandardsIgnoreLine
1828
    public function libraryHasUpgrade($library) {
1829
        global $DB;
1830
 
1831
        $results = $DB->get_records_sql(
1832
            "SELECT id
1833
                  FROM {hvp_libraries}
1834
                  WHERE machine_name = ?
1835
                  AND (major_version > ?
1836
                       OR (major_version = ? AND minor_version > ?))",
1837
            array(
1838
                $library['machineName'],
1839
                $library['majorVersion'],
1840
                $library['majorVersion'],
1841
                $library['minorVersion']
1842
            ),
1843
            0,
1844
            1
1845
        );
1846
 
1847
        return !empty($results);
1848
    }
1849
 
1850
    /**
1851
     * @inheritdoc
1852
     */
1853
    // @codingStandardsIgnoreLine
1854
    public function replaceContentHubMetadataCache($metadata, $lang = 'en') {
1855
        global $DB;
1856
 
1857
        // Check if exist in database.
1858
        $cache = $DB->get_record_sql(
1859
            'SELECT id
1860
                   FROM {hvp_content_hub_cache}
1861
                  WHERE language = ?',
1862
            array($lang)
1863
        );
1864
        if ($cache) {
1865
            // Update.
1866
            $DB->execute("UPDATE {hvp_content_hub_cache} SET json = ? WHERE id = ?", array($metadata, $cache->id));
1867
        } else {
1868
            // Insert.
1869
            $DB->insert_record('hvp_content_hub_cache', (object) array(
1870
                'json'         => $metadata,
1871
                'language'     => $lang,
1872
                'last_checked' => time(),
1873
            ));
1874
        }
1875
    }
1876
 
1877
    /**
1878
     * @inheritdoc
1879
     */
1880
    // @codingStandardsIgnoreLine
1881
    public function getContentHubMetadataCache($lang = 'en') {
1882
        global $DB;
1883
        $cache = $DB->get_record_sql(
1884
                'SELECT json
1885
                   FROM {hvp_content_hub_cache}
1886
                  WHERE language = ?',
1887
                array($lang)
1888
        );
1889
        return $cache ? $cache->json : null;
1890
    }
1891
 
1892
    /**
1893
     * @inheritdoc
1894
     */
1895
    // @codingStandardsIgnoreLine
1896
    public function getContentHubMetadataChecked($lang = 'en') {
1897
        global $DB;
1898
        $cache = $DB->get_record_sql(
1899
                'SELECT last_checked
1900
                  FROM {hvp_content_hub_cache}
1901
                 WHERE language = ?',
1902
                array($lang)
1903
        );
1904
        if ($cache) {
1905
            $time = new \DateTime();
1906
            $time->setTimestamp($cache->last_checked);
1907
            $cache = $time->format("D, d M Y H:i:s \G\M\T");
1908
        }
1909
        return $cache;
1910
    }
1911
 
1912
    /**
1913
     * @inheritdoc
1914
     */
1915
    // @codingStandardsIgnoreLine
1916
    public function setContentHubMetadataChecked($time, $lang = 'en') {
1917
        global $DB;
1918
        $DB->execute("UPDATE {hvp_content_hub_cache} SET last_checked = ? WHERE language = ?", array($time, $lang));
1919
    }
1920
}