MDL-69331 core_contentbank: Hide disabled H5P content-types

If a H5P content-type is disabled:
- The content bank won't display existing contents having it as a
main library.
- The content bank won't allow to create new contents using it.
This commit is contained in:
Sara Arjona 2021-02-25 17:21:32 +01:00
parent 73a7133ef8
commit 8a81d74244
18 changed files with 565 additions and 87 deletions

View file

@ -185,16 +185,20 @@ class api {
*
* @param string $url H5P pluginfile URL.
* @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
* @param bool $skipcapcheck Whether capabilities should be checked or not to get the pluginfile URL because sometimes they
* might be controlled before calling this method.
*
* @return array of [file, stdClass|false]:
* - file local file for this $url.
* - stdClass is an H5P object or false if there isn't any H5P with this URL.
*/
public static function get_content_from_pluginfile_url(string $url, bool $preventredirect = true): array {
public static function get_content_from_pluginfile_url(string $url, bool $preventredirect = true,
bool $skipcapcheck = false): array {
global $DB;
// Deconstruct the URL and get the pathname associated.
if (self::can_access_pluginfile_hash($url, $preventredirect)) {
if ($skipcapcheck || self::can_access_pluginfile_hash($url, $preventredirect)) {
$pathnamehash = self::get_pluginfile_hash($url);
}
@ -223,17 +227,19 @@ class api {
* @param factory $factory The \core_h5p\factory object
* @param stdClass $messages The error, exception and info messages, raised while preparing and running an H5P content.
* @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
* @param bool $skipcapcheck Whether capabilities should be checked or not to get the pluginfile URL because sometimes they
* might be controlled before calling this method.
*
* @return array of [file, h5pid]:
* - file local file for this $url.
* - h5pid is the H5P identifier or false if there isn't any H5P with this URL.
*/
public static function create_content_from_pluginfile_url(string $url, \stdClass $config, factory $factory,
\stdClass &$messages, bool $preventredirect = true): array {
\stdClass &$messages, bool $preventredirect = true, bool $skipcapcheck = false): array {
global $USER;
$core = $factory->get_core();
list($file, $h5p) = self::get_content_from_pluginfile_url($url, $preventredirect);
list($file, $h5p) = self::get_content_from_pluginfile_url($url, $preventredirect, $skipcapcheck);
if (!$file) {
$core->h5pF->setErrorMessage(get_string('h5pfilenotfound', 'core_h5p'));
@ -657,4 +663,60 @@ class api {
return true;
}
/**
* Check whether an H5P package is valid or not.
*
* @param \stored_file $file The file with the H5P content.
* @param bool $onlyupdatelibs Whether new libraries can be installed or only the existing ones can be updated
* @param bool $skipcontent Should the content be skipped (so only the libraries will be saved)?
* @param factory|null $factory The \core_h5p\factory object
* @param bool $deletefiletree Should the temporary files be deleted before returning?
* @return bool True if the H5P file is valid (expected format, valid libraries...); false otherwise.
*/
public static function is_valid_package(\stored_file $file, bool $onlyupdatelibs, bool $skipcontent = false,
?factory $factory = null, bool $deletefiletree = true): bool {
// This may take a long time.
\core_php_time_limit::raise();
$isvalid = false;
if (empty($factory)) {
$factory = new factory();
}
$core = $factory->get_core();
$h5pvalidator = $factory->get_validator();
// Set the H5P file path.
$core->h5pF->set_file($file);
$path = $core->fs->getTmpPath();
$core->h5pF->getUploadedH5pFolderPath($path);
// Add manually the extension to the file to avoid the validation fails.
$path .= '.h5p';
$core->h5pF->getUploadedH5pPath($path);
// Copy the .h5p file to the temporary folder.
$file->copy_content_to($path);
if ($h5pvalidator->isValidPackage($skipcontent, $onlyupdatelibs)) {
if ($skipcontent) {
$isvalid = true;
} else if (!empty($h5pvalidator->h5pC->mainJsonData['mainLibrary'])) {
$mainlibrary = (object) ['machinename' => $h5pvalidator->h5pC->mainJsonData['mainLibrary']];
if (self::is_library_enabled($mainlibrary)) {
$isvalid = true;
} else {
// If the main library of the package is disabled, the H5P content will be considered invalid.
$core->h5pF->setErrorMessage(get_string('mainlibrarydisabled', 'core_h5p'));
}
}
}
if ($deletefiletree) {
// Remove temp content folder.
\H5PCore::deleteFileTree($path);
}
return $isvalid;
}
}

View file

@ -51,7 +51,8 @@ class editor_ajax implements H5PEditorAjaxInterface {
global $DB;
$sql = "SELECT hl2.id, hl2.machinename as machine_name, hl2.title, hl2.majorversion as major_version,
hl2.minorversion AS minor_version, hl2.patchversion as patch_version, '' as has_icon, 0 as restricted
hl2.minorversion AS minor_version, hl2.patchversion as patch_version, '' as has_icon, 0 as restricted,
hl2.enabled
FROM {h5p_libraries} hl2
LEFT JOIN {h5p_libraries} hl1
ON hl1.machinename = hl2.machinename

View file

@ -50,30 +50,10 @@ class helper {
*/
public static function save_h5p(factory $factory, \stored_file $file, \stdClass $config, bool $onlyupdatelibs = false,
bool $skipcontent = false) {
// This may take a long time.
\core_php_time_limit::raise();
$core = $factory->get_core();
$core->h5pF->set_file($file);
$path = $core->fs->getTmpPath();
$core->h5pF->getUploadedH5pFolderPath($path);
// Add manually the extension to the file to avoid the validation fails.
$path .= '.h5p';
$core->h5pF->getUploadedH5pPath($path);
// Copy the .h5p file to the temporary folder.
$file->copy_content_to($path);
// Check if the h5p file is valid before saving it.
$h5pvalidator = $factory->get_validator();
if ($h5pvalidator->isValidPackage($skipcontent, $onlyupdatelibs)) {
// If the main library of the package is disabled, the H5P content won't be saved.
$mainlibrary = (object) ['machinename' => $h5pvalidator->h5pC->mainJsonData['mainLibrary']];
if (!api::is_library_enabled($mainlibrary)) {
$core->h5pF->setErrorMessage(get_string('mainlibrarydisabled', 'core_h5p'));
return false;
}
if (api::is_valid_package($file, $onlyupdatelibs, $skipcontent, $factory, false)) {
$core = $factory->get_core();
$h5pvalidator = $factory->get_validator();
$h5pstorage = $factory->get_storage();
$content = [
@ -90,6 +70,7 @@ class helper {
return $h5pstorage->contentId;
}
return false;
}

View file

@ -105,8 +105,11 @@ class player {
* @param stdClass $config Configuration for H5P buttons.
* @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
* @param string $component optional moodle component to sent xAPI tracking
* @param bool $skipcapcheck Whether capabilities should be checked or not to get the pluginfile URL because sometimes they
* might be controlled before calling this method.
*/
public function __construct(string $url, \stdClass $config, bool $preventredirect = true, string $component = '') {
public function __construct(string $url, \stdClass $config, bool $preventredirect = true, string $component = '',
bool $skipcapcheck = false) {
if (empty($url)) {
throw new \moodle_exception('h5pinvalidurl', 'core_h5p');
}
@ -128,7 +131,8 @@ class player {
$config,
$this->factory,
$this->messages,
$this->preventredirect
$this->preventredirect,
$skipcapcheck
);
if ($file) {
$this->context = \context::instance_by_id($file->get_contextid());

View file

@ -37,7 +37,7 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_h5p\api
*/
class api_testcase extends \advanced_testcase {
class api_test extends \advanced_testcase {
/**
* Test the behaviour of delete_library().
@ -742,4 +742,101 @@ class api_testcase extends \advanced_testcase {
],
];
}
/**
* Test the behaviour of is_valid_package().
* @runInSeparateProcess
*
* @covers ::is_valid_package
* @dataProvider is_valid_package_provider
*
* @param string $filename The H5P content to validate.
* @param bool $expected Expected result after calling the method.
* @param bool $isadmin Whether the user calling the method will be admin or not.
* @param bool $onlyupdatelibs Whether new libraries can be installed or only the existing ones can be updated.
* @param bool $skipcontent Should the content be skipped (so only the libraries will be saved)?
*/
public function test_is_valid_package(string $filename, bool $expected, bool $isadmin = false, bool $onlyupdatelibs = false,
bool $skipcontent = false): void {
global $USER;
$this->resetAfterTest();
if ($isadmin) {
$this->setAdminUser();
$user = $USER;
} else {
// Create a user.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
}
// Prepare the file.
$path = __DIR__ . $filename;
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
// Check if the H5P content is valid or not.
$result = api::is_valid_package($file, $onlyupdatelibs, $skipcontent);
$this->assertEquals($expected, $result);
}
/**
* Data provider for test_is_valid_package().
*
* @return array
*/
public function is_valid_package_provider(): array {
return [
'Valid H5P file (as admin)' => [
'filename' => '/fixtures/greeting-card-887.h5p',
'expected' => true,
'isadmin' => true,
],
'Valid H5P file (as user) without library update and checking content' => [
'filename' => '/fixtures/greeting-card-887.h5p',
'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them.
'isadmin' => false,
'onlyupdatelibs' => false,
'skipcontent' => false,
],
'Valid H5P file (as user) with library update and checking content' => [
'filename' => '/fixtures/greeting-card-887.h5p',
'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them.
'isadmin' => false,
'onlyupdatelibs' => true,
'skipcontent' => false,
],
'Valid H5P file (as user) without library update and skipping content' => [
'filename' => '/fixtures/greeting-card-887.h5p',
'expected' => true, // Content check is skipped so the package will be considered valid.
'isadmin' => false,
'onlyupdatelibs' => false,
'skipcontent' => true,
],
'Valid H5P file (as user) with library update and skipping content' => [
'filename' => '/fixtures/greeting-card-887.h5p',
'expected' => true, // Content check is skipped so the package will be considered valid.
'isadmin' => false,
'onlyupdatelibs' => true,
'skipcontent' => true,
],
'Invalid H5P file (as admin)' => [
'filename' => '/fixtures/h5ptest.zip',
'expected' => false,
'isadmin' => true,
],
'Invalid H5P file (as user)' => [
'filename' => '/fixtures/h5ptest.zip',
'expected' => false,
'isadmin' => false,
],
'Invalid H5P file (as user) skipping content' => [
'filename' => '/fixtures/h5ptest.zip',
'expected' => true, // Content check is skipped so the package will be considered valid.
'isadmin' => false,
'onlyupdatelibs' => false,
'skipcontent' => true,
],
];
}
}

View file

@ -1,6 +1,17 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 3.11 ===
* Added $skipcapcheck parameter to H5P constructor, api::create_content_from_pluginfile_url() and
api::get_content_from_pluginfile_url() to let skip capabilities check to get the pluginfile URL.
* Added new field "enabled" to h5p_libraries to let define if a content type is enabled (1) or not (0).
For now, only runnable content-types can be disabled/enabled. When a content-type is disabled, their
contents are not displayed and no new contents using it can be created/uploaded.
Some extra methods have been added to the api too in order to support this field:
- set_library_enabled
- is_library_enabled
- is_valid_package
=== 3.10 ===
* Added a new cache for h5p_library_files (MDL-69207)