Skip to content

Commit

Permalink
MDL-67058 core_h5p: task to get latest content types version from H5P
Browse files Browse the repository at this point in the history
  • Loading branch information
vmdef committed Nov 6, 2019
1 parent 01aa126 commit 5315acb
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 3 deletions.
1 change: 1 addition & 0 deletions h5p/classes/autoloader.php
Expand Up @@ -43,6 +43,7 @@ public static function autoload($classname): void {

$classes = [
'H5PCore' => '/lib/h5p/h5p.classes.php',
'H5PHubEndpoints' => '/lib/h5p/h5p.classes.php',
'H5PFrameworkInterface' => '/lib/h5p/h5p.classes.php',
'H5PContentValidator' => 'lib/h5p/h5p.classes.php',
'H5PValidator' => '/lib/h5p/h5p.classes.php',
Expand Down
142 changes: 140 additions & 2 deletions h5p/classes/core.php
Expand Up @@ -24,13 +24,14 @@

namespace core_h5p;

defined('MOODLE_INTERNAL') || die();

require_once("$CFG->libdir/filelib.php");
use H5PCore;
use H5PFrameworkInterface;
use stdClass;
use moodle_url;

defined('MOODLE_INTERNAL') || die();

/**
* H5P core class, containing functions and storage shared by the other H5P classes.
*
Expand Down Expand Up @@ -153,4 +154,141 @@ public static function get_scripts(): array {

return $urls;
}

/**
* Fetch and install the latest H5P content types libraries from the official H5P repository.
* If the latest version of a content type library is present in the system, nothing is done for that content type.
*
* @return stdClass
*/
public function fetch_latest_content_types(): ?\stdClass {

$contenttypes = self::get_latest_content_types();
if (!empty($contenttypes->error)) {
return $contenttypes;
}

$typesinstalled = [];

foreach ($contenttypes->contentTypes as $type) {
$library = [
'machineName' => $type->id,
'majorVersion' => $type->version->major,
'minorVersion' => $type->version->minor,
'patchVersion' => $type->version->patch,
];

$factory = new \core_h5p\factory();
$framework = $factory->get_framework();

$shoulddownload = true;
if ($framework->getLibraryId($type->id, $type->version->major, $type->version->minor)) {
if (!$framework->isPatchedLibrary($library)) {
$shoulddownload = false;
}
}

if ($shoulddownload) {
$installed['id'] = $this->fetch_content_type($library);
if ($installed['id']) {
$installed['name'] = $librarykey = \H5PCore::libraryToString($library);
$typesinstalled[] = $installed;
}
}
}

$result = new stdClass();
$result->error = '';
$result->typesinstalled = $typesinstalled;

return $result;
}

/**
* Given an H5P content type machine name, fetch and install the required library from the official H5P repository.
*
* @param array $library Library machineName, majorversion and minorversion.
* @return int|null Returns the id of the content type library installed, null otherwise.
*/
public function fetch_content_type(array $library): ?int {

$factory = new \core_h5p\factory();
$framework = $factory->get_framework();

// Get a temp path to download the content type.
$temppath = make_request_directory();
$tempfile = "{$temppath}/" . $library['machineName'] . ".h5p";

// Download the latest content type from the H5P official repository.
$endpoint = $this->get_api_endpoint($library['machineName']);
$result = download_file_content(
$endpoint,
null,
null,
true,
300,
20,
false,
$tempfile
);

if (!empty($result->error) || $result->status == '404') {
return null;
}

$framework->getUploadedH5pPath($tempfile);
$framework->getUploadedH5pFolderPath($temppath);

$validator = $factory->get_validator();

// Check if the h5p file is valid before saving it.
if ($validator->isValidPackage(false, false)) {
$h5pstorage = $factory->get_storage();
$h5pstorage->savePackage([], null, true);
$librarykey = \H5PCore::libraryToString($library);
return $h5pstorage->h5pC->librariesJsonData[$librarykey]["libraryId"];
}

return null;
}

/**
* Get H5P endpoints.
*
* If $library is null, moodle_url is the endpoint of the latest version of the H5P content types. If library is the
* machine name of a content type, moodle_url is the endpoint to download the content type.
*
* @param string|null $library The machineName of the library whose endpoint is requested.
* @return moodle_url The endpoint moodle_url object.
*/
public function get_api_endpoint(?string $library): moodle_url {
$h5purl = \H5PHubEndpoints::createURL(\H5PHubEndpoints::CONTENT_TYPES ) . $library;
return new moodle_url($h5purl);
}

/**
* Get the latest version of the H5P content types available in the official repository.
*
* @return stdClass An object with 2 properties:
* - string error: error message when there is any problem, empty otherwise
* - array contentTypes: an object for each H5P content type with its information
*/
public function get_latest_content_types(): \stdClass {
// Get the latest content-types json.
$postdata = ['uuid' => 'foo'];
$endpoint = $this->get_api_endpoint(null);
$request = download_file_content($endpoint, null, $postdata, true);

if (!empty($request->error) || $request->status != '200' || empty($request->results)) {
if (empty($request->error)) {
$request->error = get_string('fetchtypesfailure', 'core_h5p');
}
return $request;
}

$contenttypes = json_decode($request->results);
$contenttypes->error = '';

return $contenttypes;
}
}
40 changes: 40 additions & 0 deletions h5p/tests/generator/lib.php
Expand Up @@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

use core_h5p\factory;

defined('MOODLE_INTERNAL') || die();

/**
Expand Down Expand Up @@ -339,4 +341,42 @@ public function create_library_dependency_record(int $libid, int $requiredlibid,
)
);
}

/**
* Create content type records in the h5p_libraries database table.
*
* @param int $pending Number of content types not installed
* @return array Data of the content types not installed.
*/
public function create_content_types(int $pending): array {
global $DB;

$factory = new factory();
$core = $factory->get_core();

// Get info of latest content types versions.
$contenttypes = $core->get_latest_content_types()->contentTypes;

$size = count($contenttypes) - $pending;

// Avoid to install 2 content types.
$chunks = array_chunk($contenttypes, $size);

$contenttypes = $chunks[0];
$pendingtypes = $chunks[1];

// Fake installation of all other H5P content types.
foreach ($contenttypes as $contenttype) {
$library = [
'machinename' => $contenttype->id,
'majorversion' => $contenttype->version->major,
'minorversion' => $contenttype->version->minor,
'patchversion' => $contenttype->version->patch,
'runnable' => 1
];
$DB->insert_record('h5p_libraries', (object) $library);
}

return [$contenttypes, $pendingtypes];
}
}
152 changes: 152 additions & 0 deletions h5p/tests/h5p_core_test.php
@@ -0,0 +1,152 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Testing the H5P core methods.
*
* @package core_h5p
* @category test
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace core_h5p\local\tests;

use core_h5p\factory;

defined('MOODLE_INTERNAL') || die();

/**
* Test class covering the H5PFileStorage interface implementation.
*
* @package core_h5p
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @runTestsInSeparateProcesses
*/
class h5p_core_test extends \advanced_testcase {

protected function setup() {
parent::setUp();

$factory = new factory();
$this->core = $factory->get_core();
}

/**
* Check that given an H5P content type machine name, the required library are fetched and installed from the official H5P
* repository.
*
* This test require access to an external URL (H5P libraries repository), so can take long time to execute.
* PHPUNIT_LONGTEST constant should be set in phpunit.xml or directly in config.php.
* define('PHPUNIT_LONGTEST', true);
*
* return void
*/
public function test_fetch_content_type(): void {
global $DB;

$this->resetAfterTest(true);

if (!defined('PHPUNIT_LONGTEST')) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}

$library = [
'machineName' => 'H5P.Accordion',
'majorVersion' => 1,
'minorVersion' => 0,
'patchVersion' => 0,
];

$sql = 'SELECT count(id)
FROM {files}
WHERE ' . $DB->sql_like('filepath', ':filepath');
$params['filepath'] = "/{$library['machineName']}-%";

$contentfiles = $DB->count_records_sql($sql, $params);

$this->assertEquals(0, $contentfiles);

$this->core->fetch_content_type($library);

$contentfiles = $DB->count_records_sql($sql, $params);
$this->assertGreaterThan(0, $contentfiles);
}

/**
* Test that latest version of non installed H5P content type libraries are fetched and installed from the
* official H5P repository. To speed up the test, only if checked that one content type is installed.
*
* This test require access to an external URL (H5P libraries repository), so can take long time to execute.
* PHPUNIT_LONGTEST constant should be set in phpunit.xml or directly in config.php.
* define('PHPUNIT_LONGTEST', true);
*
* return void
*/
public function test_fetch_latest_content_types(): void {
global $DB;

$this->resetAfterTest(true);

if (!defined('PHPUNIT_LONGTEST')) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}

$contentfiles = $DB->count_records('h5p_libraries');

// Initially there are no h5p records in database.
$this->assertEquals(0, $contentfiles);

// Fetch generator.
$generator = \testing_util::get_data_generator();
$h5pgenerator = $generator->get_plugin_generator('core_h5p');

// Get info of latest content types versions.
[$contenttypes, $contenttoinstall] = $h5pgenerator->create_content_types(1);
// Number of H5P content types.
$numcontenttypes = count($contenttypes) + count($contenttoinstall);

$contenttoinstall = $contenttoinstall[0];

// Content type libraries has runnable set to 1.
$conditions = ['runnable' => 1];
$contentfiles = $DB->get_records('h5p_libraries', $conditions, '', 'machinename');

// There is a record for each installed content type, except the one that was hold for later.
$this->assertEquals($numcontenttypes - 1, count($contentfiles));
$this->assertArrayNotHasKey($contenttoinstall->id, $contentfiles);

$result = $this->core->fetch_latest_content_types();

$contentfiles = $DB->get_records('h5p_libraries', $conditions, '', 'machinename');

// There is a new record for the new installed content type.
$this->assertCount($numcontenttypes, $contentfiles);
$this->assertArrayHasKey($contenttoinstall->id, $contentfiles);
$this->assertCount(1, $result->typesinstalled);
$this->assertStringStartsWith($contenttoinstall->id, $result->typesinstalled[0]['name']);

// New execution doesn't install any content type.
$result = $this->core->fetch_latest_content_types();

$contentfiles = $DB->get_records('h5p_libraries', $conditions, '', 'machinename');

$this->assertEquals($numcontenttypes, count($contentfiles));
$this->assertCount(0, $result->typesinstalled);
}
}
1 change: 1 addition & 0 deletions lang/en/admin.php
Expand Up @@ -647,6 +647,7 @@
$string['checkboxno'] = 'No';
$string['checkboxyes'] = 'Yes';
$string['choosefiletoedit'] = 'Choose file to edit';
$string['h5pgetcontenttypestask'] = 'Download available H5P content types from h5p.org';
$string['iconvrequired'] = 'Installing ICONV extension is required.';
$string['ignore'] = 'Ignore';
$string['includemoduleuserdata'] = 'Include module user data';
Expand Down
1 change: 1 addition & 0 deletions lang/en/h5p.php
Expand Up @@ -71,6 +71,7 @@
$string['editor'] = 'Editor';
$string['embed'] = 'Embed';
$string['embedtitle'] = 'View the embed code for this content.';
$string['fetchtypesfailure'] = 'No information could be obtained on the H5P content types available. H5P repository connection failure';
$string['fileExceedsMaxSize'] = 'One of the files inside the package exceeds the maximum file size allowed. ({$a->%file} {$a->%used} > {$a->%max})';
$string['fullscreen'] = 'Fullscreen';
$string['gpl'] = 'General Public License v3';
Expand Down

0 comments on commit 5315acb

Please sign in to comment.