MDL-67194 core_h5p: save core API version that content types require
[moodle.git] / h5p / classes / core.php
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/>.
17 /**
18  * H5P core class.
19  *
20  * @package    core_h5p
21  * @copyright  2019 Sara Arjona <sara@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_h5p;
27 defined('MOODLE_INTERNAL') || die();
29 require_once("$CFG->libdir/filelib.php");
30 use H5PCore;
31 use H5PFrameworkInterface;
32 use stdClass;
33 use moodle_url;
35 /**
36  * H5P core class, containing functions and storage shared by the other H5P classes.
37  *
38  * @package    core_h5p
39  * @copyright  2019 Sara Arjona <sara@moodle.com>
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class core extends \H5PCore {
44     /** @var array The array containing all the present libraries */
45     protected $libraries;
47     /**
48      * Constructor for core_h5p/core.
49      *
50      * @param H5PFrameworkInterface $framework The frameworks implementation of the H5PFrameworkInterface
51      * @param string|\H5PFileStorage $path The H5P file storage directory or class
52      * @param string $url The URL to the file storage directory
53      * @param string $language The language code. Defaults to english
54      * @param boolean $export Whether export is enabled
55      */
56     public function __construct(H5PFrameworkInterface $framework, $path, string $url, string $language = 'en',
57             bool $export = false) {
59         parent::__construct($framework, $path, $url, $language, $export);
61         // Aggregate the assets by default.
62         $this->aggregateAssets = true;
63     }
65     /**
66      * Get the path to the dependency.
67      *
68      * @param array $dependency An array containing the information of the requested dependency library
69      * @return string The path to the dependency library
70      */
71     protected function getDependencyPath(array $dependency): string {
72         $library = $this->find_library($dependency);
74         return "libraries/{$library->id}/{$library->machinename}-{$library->majorversion}.{$library->minorversion}";
75     }
77     /**
78      * Get the paths to the content dependencies.
79      *
80      * @param int $id The H5P content ID
81      * @return array An array containing the path of each content dependency
82      */
83     public function get_dependency_roots(int $id): array {
84         $roots = [];
85         $dependencies = $this->h5pF->loadContentDependencies($id);
86         $context = \context_system::instance();
87         foreach ($dependencies as $dependency) {
88             $library = $this->find_library($dependency);
89             $roots[self::libraryToString($dependency, true)] = (moodle_url::make_pluginfile_url(
90                 $context->id,
91                 'core_h5p',
92                 'libraries',
93                 $library->id,
94                 "/" . self::libraryToString($dependency, true),
95                 ''
96             ))->out(false);
97         }
99         return $roots;
100     }
102     /**
103      * Get a particular dependency library.
104      *
105      * @param array $dependency An array containing information of the dependency library
106      * @return stdClass|null The library object if the library dependency exists, null otherwise
107      */
108     protected function find_library(array $dependency): ?\stdClass {
109         global $DB;
110         if (null === $this->libraries) {
111             $this->libraries = $DB->get_records('h5p_libraries');
112         }
114         $major = $dependency['majorVersion'];
115         $minor = $dependency['minorVersion'];
116         $patch = $dependency['patchVersion'];
118         foreach ($this->libraries as $library) {
119             if ($library->machinename !== $dependency['machineName']) {
120                 continue;
121             }
123             if ($library->majorversion != $major) {
124                 continue;
125             }
126             if ($library->minorversion != $minor) {
127                 continue;
128             }
129             if ($library->patchversion != $patch) {
130                 continue;
131             }
133             return $library;
134         }
136         return null;
137     }
139     /**
140      * Get core JavaScript files.
141      *
142      * @return array The array containg urls of the core JavaScript files
143      */
144     public static function get_scripts(): array {
145         global $CFG;
146         $cachebuster = '?ver='.$CFG->jsrev;
147         $liburl = $CFG->wwwroot . '/lib/h5p/';
148         $urls = [];
150         foreach (self::$scripts as $script) {
151             $urls[] = new moodle_url($liburl . $script . $cachebuster);
152         }
153         $urls[] = new moodle_url("/h5p/js/h5p_overrides.js");
155         return $urls;
156     }
158     /**
159      * Fetch and install the latest H5P content types libraries from the official H5P repository.
160      * If the latest version of a content type library is present in the system, nothing is done for that content type.
161      *
162      * @return stdClass
163      */
164     public function fetch_latest_content_types(): ?\stdClass {
166         $contenttypes = self::get_latest_content_types();
167         if (!empty($contenttypes->error)) {
168             return $contenttypes;
169         }
171         $typesinstalled = [];
173         foreach ($contenttypes->contentTypes as $type) {
174             // Don't fetch content types that require a higher H5P core API version.
175             if (!$this->is_required_core_api($type->coreApiVersionNeeded)) {
176                 continue;
177             }
179             $library = [
180                 'machineName' => $type->id,
181                 'majorVersion' => $type->version->major,
182                 'minorVersion' => $type->version->minor,
183                 'patchVersion' => $type->version->patch
184             ];
186             $factory = new \core_h5p\factory();
187             $framework = $factory->get_framework();
189             $shoulddownload = true;
190             if ($framework->getLibraryId($type->id, $type->version->major, $type->version->minor)) {
191                 if (!$framework->isPatchedLibrary($library)) {
192                     $shoulddownload = false;
193                 }
194             }
196             if ($shoulddownload) {
197                 $installed['id'] = $this->fetch_content_type($library);
198                 if ($installed['id']) {
199                     $installed['name'] = $librarykey = \H5PCore::libraryToString($library);
200                     $typesinstalled[] = $installed;
201                 }
202             }
203         }
205         $result = new stdClass();
206         $result->error = '';
207         $result->typesinstalled = $typesinstalled;
209         return $result;
210     }
212     /**
213      * Given an H5P content type machine name, fetch and install the required library from the official H5P repository.
214      *
215      * @param array $library Library machineName, majorversion and minorversion.
216      * @return int|null Returns the id of the content type library installed, null otherwise.
217      */
218     public function fetch_content_type(array $library): ?int {
219         $factory = new \core_h5p\factory();
221         // Get a temp path to download the content type.
222         $temppath = make_request_directory();
223         $tempfile = "{$temppath}/" . $library['machineName'] . ".h5p";
225         // Download the latest content type from the H5P official repository.
226         $fs = get_file_storage();
227         $file = $fs->create_file_from_url(
228             (object) [
229                 'component' => 'core_h5p',
230                 'filearea' => 'library_sources',
231                 'itemid' => 0,
232                 'contextid' => (\context_system::instance())->id,
233                 'filepath' => '/',
234                 'filename' => $library['machineName'],
235             ],
236             $this->get_api_endpoint($library['machineName']),
237             null,
238             true
239         );
241         if (!$file) {
242             return null;
243         }
245         helper::save_h5p($factory, $file, (object) [], false, true);
247         $file->delete();
249         $librarykey = static::libraryToString($library);
250         return $factory->get_storage()->h5pC->librariesJsonData[$librarykey]["libraryId"];
251     }
253     /**
254      * Get H5P endpoints.
255      *
256      * If $library is null, moodle_url is the endpoint of the latest version of the H5P content types. If library is the
257      * machine name of a content type, moodle_url is the endpoint to download the content type.
258      *
259      * @param string|null $library The machineName of the library whose endpoint is requested.
260      * @return moodle_url The endpoint moodle_url object.
261      */
262     public function get_api_endpoint(?string $library): moodle_url {
263         $h5purl = \H5PHubEndpoints::createURL(\H5PHubEndpoints::CONTENT_TYPES ) . $library;
264         return new moodle_url($h5purl);
265     }
267     /**
268      * Get the latest version of the H5P content types available in the official repository.
269      *
270      * @return stdClass An object with 2 properties:
271      *     - string error: error message when there is any problem, empty otherwise
272      *     - array contentTypes: an object for each H5P content type with its information
273      */
274     public function get_latest_content_types(): \stdClass {
275         // Get the latest content-types json.
276         $postdata = ['uuid' => 'foo'];
277         $endpoint = $this->get_api_endpoint(null);
278         $request = download_file_content($endpoint, null, $postdata, true);
280         if (!empty($request->error) || $request->status != '200' || empty($request->results)) {
281             if (empty($request->error)) {
282                 $request->error = get_string('fetchtypesfailure', 'core_h5p');
283             }
284             return $request;
285         }
287         $contenttypes = json_decode($request->results);
288         $contenttypes->error = '';
290         return $contenttypes;
291     }
293     /**
294      * Checks that the required H5P core API version or higher is installed.
295      *
296      * @param stdClass $coreapi Object with properties major and minor for the core API version required.
297      * @return bool True if the required H5P core API version is installed. False if not.
298      */
299     public function is_required_core_api($coreapi): bool {
300         if (isset($coreapi) && !empty($coreapi)) {
301             if (($coreapi->major > H5PCore::$coreApi['majorVersion']) ||
302                 (($coreapi->major == H5PCore::$coreApi['majorVersion']) && ($coreapi->minor > H5PCore::$coreApi['minorVersion']))) {
303                 return false;
304             }
305         }
306         return true;
307     }