Merge branch 'MDL-67057-master' of git://github.com/andrewnicols/moodle
This commit is contained in:
@@ -41,6 +41,9 @@ class framework implements \H5PFrameworkInterface {
|
||||
/** @var string The path to the last uploaded h5p file */
|
||||
private $lastuploadedfile;
|
||||
|
||||
/** @var stored_file The .h5p file */
|
||||
private $file;
|
||||
|
||||
/**
|
||||
* Returns info for the current platform.
|
||||
* Implements getPlatformInfo.
|
||||
@@ -634,8 +637,29 @@ class framework implements \H5PFrameworkInterface {
|
||||
* FALSE if the user is not allowed to update libraries.
|
||||
*/
|
||||
public function mayUpdateLibraries() {
|
||||
// Currently, capabilities are not being set/used, so everyone can update libraries.
|
||||
return true;
|
||||
return helper::can_update_library($this->get_file());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .h5p file.
|
||||
*
|
||||
* @return stored_file The .h5p file.
|
||||
*/
|
||||
public function get_file(): \stored_file {
|
||||
if (!isset($this->file)) {
|
||||
throw new \coding_exception('Using get_file() before file is set');
|
||||
}
|
||||
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the .h5p file.
|
||||
*
|
||||
* @param stored_file $file The .h5p file.
|
||||
*/
|
||||
public function set_file(\stored_file $file): void {
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -757,7 +781,19 @@ class framework implements \H5PFrameworkInterface {
|
||||
$content['contenthash'] = '';
|
||||
}
|
||||
|
||||
$data = array(
|
||||
// If the libraryid declared in the package is empty, get the latest version.
|
||||
if (empty($content['library']['libraryId'])) {
|
||||
$mainlibrary = $this->get_latest_library_version($content['library']['machineName']);
|
||||
if (empty($mainlibrary)) {
|
||||
// Raise an error if the main library is not defined and the latest version doesn't exist.
|
||||
$message = $this->t('Missing required library @library', ['@library' => $content['library']['machineName']]);
|
||||
$this->setErrorMessage($message, 'missing-required-library');
|
||||
return false;
|
||||
}
|
||||
$content['library']['libraryId'] = $mainlibrary->id;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'jsoncontent' => $content['params'],
|
||||
'displayoptions' => $content['disable'],
|
||||
'mainlibraryid' => $content['library']['libraryId'],
|
||||
@@ -765,7 +801,7 @@ class framework implements \H5PFrameworkInterface {
|
||||
'filtered' => null,
|
||||
'pathnamehash' => $content['pathnamehash'],
|
||||
'contenthash' => $content['contenthash']
|
||||
);
|
||||
];
|
||||
|
||||
if (!isset($content['id'])) {
|
||||
$data['timecreated'] = $data['timemodified'];
|
||||
@@ -1585,4 +1621,22 @@ class framework implements \H5PFrameworkInterface {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest library version.
|
||||
*
|
||||
* @param string $machinename The library's machine name
|
||||
* @return stdClass|null An object with the latest library version
|
||||
*/
|
||||
public function get_latest_library_version(string $machinename): ?\stdClass {
|
||||
global $DB;
|
||||
|
||||
$libraries = $DB->get_records('h5p_libraries', ['machinename' => $machinename],
|
||||
'majorversion DESC, minorversion DESC, patchversion DESC', '*', 0, 1);
|
||||
if ($libraries) {
|
||||
return reset($libraries);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains helper class for the H5P area.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2019 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Helper class for the H5P area.
|
||||
*
|
||||
* @copyright 2019 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Store an H5P file.
|
||||
*
|
||||
* @param factory $factory The \core_h5p\factory object
|
||||
* @param stored_file $file Moodle file instance
|
||||
* @param stdClass $config Button options config
|
||||
* @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)?
|
||||
*
|
||||
* @return int|false|null The H5P identifier or null if there is an error when saving or false if it's not a valid H5P package
|
||||
*/
|
||||
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();
|
||||
$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)) {
|
||||
$h5pstorage = $factory->get_storage();
|
||||
|
||||
$content = [
|
||||
'pathnamehash' => $file->get_pathnamehash(),
|
||||
'contenthash' => $file->get_contenthash(),
|
||||
];
|
||||
$options = ['disable' => self::get_display_options($core, $config)];
|
||||
|
||||
$h5pstorage->savePackage($content, null, $skipcontent, $options);
|
||||
|
||||
return $h5pstorage->contentId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the representation of display options as int.
|
||||
*
|
||||
* @param core $core The \core_h5p\core object
|
||||
* @param stdClass $config Button options config
|
||||
*
|
||||
* @return int The representation of display options as int
|
||||
*/
|
||||
public static function get_display_options(core $core, \stdClass $config): int {
|
||||
$export = isset($config->export) ? $config->export : 0;
|
||||
$embed = isset($config->embed) ? $config->embed : 0;
|
||||
$copyright = isset($config->copyright) ? $config->copyright : 0;
|
||||
$frame = ($export || $embed || $copyright);
|
||||
if (!$frame) {
|
||||
$frame = isset($config->frame) ? $config->frame : 0;
|
||||
}
|
||||
|
||||
$disableoptions = [
|
||||
core::DISPLAY_OPTION_FRAME => $frame,
|
||||
core::DISPLAY_OPTION_DOWNLOAD => $export,
|
||||
core::DISPLAY_OPTION_EMBED => $embed,
|
||||
core::DISPLAY_OPTION_COPYRIGHT => $copyright,
|
||||
];
|
||||
|
||||
return $core->getStorableDisplayOptions($disableoptions, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the author of the .h5p file is "trustable". If the file hasn't been uploaded by a user with the
|
||||
* required capability, the content won't be deployed.
|
||||
*
|
||||
* @param stored_file $file The .h5p file to be deployed
|
||||
* @return bool Returns true if the file can be deployed, false otherwise.
|
||||
*/
|
||||
public static function can_deploy_package(\stored_file $file): bool {
|
||||
$context = \context::instance_by_id($file->get_contextid());
|
||||
if (has_capability('moodle/h5p:deploy', $context, $file->get_userid())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content-type libraries can be upgraded.
|
||||
* The H5P content-type libraries can only be upgraded if the author of the .h5p file can manage content-types or if all the
|
||||
* content-types exist, to avoid users without the required capability to upload malicious content.
|
||||
*
|
||||
* @param stored_file $file The .h5p file to be deployed
|
||||
* @return bool Returns true if the content-type libraries can be created/updated, false otherwise.
|
||||
*/
|
||||
public static function can_update_library(\stored_file $file): bool {
|
||||
if (is_null($file)) {
|
||||
debugging('\core_h5p\h5p::can_update_library() now expects a \'file\' to be passed.',
|
||||
DEBUG_DEVELOPER);
|
||||
return false;
|
||||
}
|
||||
|
||||
$context = \context::instance_by_id($file->get_contextid());
|
||||
// Check if the owner of the .h5p file has the capability to manage content-types.
|
||||
if (has_capability('moodle/h5p:updatelibraries', $context, $file->get_userid())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience to take a fixture test file and create a stored_file.
|
||||
*
|
||||
* @param string $filepath The filepath of the file
|
||||
* @param int $userid The author of the file
|
||||
* @param \context $context The context where the file will be created
|
||||
* @return stored_file The file created
|
||||
*/
|
||||
public static function create_fake_stored_file_from_path(string $filepath, int $userid = 0,
|
||||
\context $context = null): \stored_file {
|
||||
if (is_null($context)) {
|
||||
$context = \context_system::instance();
|
||||
}
|
||||
$filerecord = [
|
||||
'contextid' => $context->id,
|
||||
'component' => 'core_h5p',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => rand(),
|
||||
'filepath' => '/',
|
||||
'filename' => basename($filepath),
|
||||
];
|
||||
if (!is_null($userid)) {
|
||||
$filerecord['userid'] = $userid;
|
||||
}
|
||||
|
||||
$fs = get_file_storage();
|
||||
return $fs->create_file_from_pathname($filerecord, $filepath);
|
||||
}
|
||||
|
||||
}
|
||||
+59
-49
@@ -76,10 +76,15 @@ class player {
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var context The \core_h5p\factory object.
|
||||
* @var factory The \core_h5p\factory object.
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var stdClass The error, exception and info messages, raised while preparing and running the player.
|
||||
*/
|
||||
private $messages;
|
||||
|
||||
/**
|
||||
* Inits the H5P player for rendering the content.
|
||||
*
|
||||
@@ -94,6 +99,8 @@ class player {
|
||||
|
||||
$this->factory = new \core_h5p\factory();
|
||||
|
||||
$this->messages = new \stdClass();
|
||||
|
||||
// Create \core_h5p\core instance.
|
||||
$this->core = $this->factory->get_core();
|
||||
|
||||
@@ -113,13 +120,20 @@ class player {
|
||||
* @return stdClass with framework error messages.
|
||||
*/
|
||||
public function get_messages() : \stdClass {
|
||||
$messages = new \stdClass();
|
||||
$messages->error = $this->core->h5pF->getMessages('error');
|
||||
|
||||
if (empty($messages->error)) {
|
||||
$messages->error = false;
|
||||
// Check if there are some errors and store them in $messages.
|
||||
if (empty($this->messages->error)) {
|
||||
$this->messages->error = $this->core->h5pF->getMessages('error') ?: false;
|
||||
} else {
|
||||
$this->messages->error = array_merge($this->messages->error, $this->core->h5pF->getMessages('error'));
|
||||
}
|
||||
return $messages;
|
||||
|
||||
if (empty($this->messages->info)) {
|
||||
$this->messages->info = $this->core->h5pF->getMessages('info') ?: false;
|
||||
} else {
|
||||
$this->messages->info = array_merge($this->messages->info, $this->core->h5pF->getMessages('info'));
|
||||
}
|
||||
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +228,7 @@ class player {
|
||||
* @return int|false H5P DB identifier.
|
||||
*/
|
||||
private function get_h5p_id(string $url, \stdClass $config) {
|
||||
global $DB;
|
||||
global $DB, $USER;
|
||||
|
||||
$fs = get_file_storage();
|
||||
|
||||
@@ -255,13 +269,48 @@ class player {
|
||||
|
||||
// Check if the user uploading the H5P content is "trustable". If the file hasn't been uploaded by a user with this
|
||||
// capability, the content won't be deployed and an error message will be displayed.
|
||||
if (!has_capability('moodle/h5p:deploy', $this->context, $file->get_userid())) {
|
||||
if (!helper::can_deploy_package($file)) {
|
||||
$this->core->h5pF->setErrorMessage(get_string('nopermissiontodeploy', 'core_h5p'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The H5P content can be only deployed if the author of the .h5p file can update libraries or if all the
|
||||
// content-type libraries exist, to avoid users without the h5p:updatelibraries capability upload malicious content.
|
||||
$onlyupdatelibs = !helper::can_update_library($file);
|
||||
|
||||
// Set the .h5p file, in order to check later the permissions to update libraries.
|
||||
$this->core->h5pF->set_file($file);
|
||||
|
||||
// Validate and store the H5P content before displaying it.
|
||||
return $this->save_h5p($file, $config);
|
||||
$h5pid = helper::save_h5p($this->factory, $file, $config, $onlyupdatelibs, false);
|
||||
if (!$h5pid && $file->get_userid() != $USER->id && has_capability('moodle/h5p:updatelibraries', $this->context)) {
|
||||
// The user has permission to update libraries but the package has been uploaded by a different
|
||||
// user without this permission. Check if there is some missing required library error.
|
||||
$missingliberror = false;
|
||||
$messages = $this->get_messages();
|
||||
if (!empty($messages->error)) {
|
||||
foreach ($messages->error as $error) {
|
||||
if ($error->code == 'missing-required-library') {
|
||||
$missingliberror = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($missingliberror) {
|
||||
// The message about the permissions to upload libraries should be removed.
|
||||
$infomsg = "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.";
|
||||
if (($key = array_search($infomsg, $messages->info)) !== false) {
|
||||
unset($messages->info[$key]);
|
||||
}
|
||||
|
||||
// No library will be installed and an error will be displayed, because this content is not trustable.
|
||||
$this->core->h5pF->setInfoMessage(get_string('notrustablefile', 'core_h5p'));
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
return $h5pid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,45 +451,6 @@ class player {
|
||||
return $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an H5P file
|
||||
*
|
||||
* @param stored_file $file Moodle file instance
|
||||
* @param stdClass $config Button options config.
|
||||
*
|
||||
* @return int|false The H5P identifier or false if it's not a valid H5P package.
|
||||
*/
|
||||
private function save_h5p($file, \stdClass $config) : int {
|
||||
// This may take a long time.
|
||||
\core_php_time_limit::raise();
|
||||
|
||||
$path = $this->core->fs->getTmpPath();
|
||||
$this->core->h5pF->getUploadedH5pFolderPath($path);
|
||||
// Add manually the extension to the file to avoid the validation fails.
|
||||
$path .= '.h5p';
|
||||
$this->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 = $this->factory->get_validator();
|
||||
if ($h5pvalidator->isValidPackage(false, false)) {
|
||||
$h5pstorage = $this->factory->get_storage();
|
||||
|
||||
$options = ['disable' => $this->get_display_options($config)];
|
||||
$content = [
|
||||
'pathnamehash' => $file->get_pathnamehash(),
|
||||
'contenthash' => $file->get_contenthash(),
|
||||
];
|
||||
|
||||
$h5pstorage->savePackage($content, null, false, $options);
|
||||
return $h5pstorage->contentId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the representation of display options as int.
|
||||
* @param stdClass $config Button options config.
|
||||
|
||||
+12
-12
@@ -61,15 +61,18 @@ if (empty($messages->error) && empty($messages->exception)) {
|
||||
// Add H5P assets to the page.
|
||||
$h5pplayer->add_assets_to_page();
|
||||
|
||||
// Check if there is some error when adding assets to the page.
|
||||
// Print page HTML.
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// Check if some error has been raised when adding assets to the page. If that's the case, display them above the H5P content.
|
||||
$messages = $h5pplayer->get_messages();
|
||||
if (empty($messages->error) && empty($messages->exception)) {
|
||||
|
||||
// Print page HTML.
|
||||
echo $OUTPUT->header();
|
||||
|
||||
echo $h5pplayer->output();
|
||||
if (!empty($messages->exception) || !empty($messages->error)) {
|
||||
$messages->h5picon = new \moodle_url('/h5p/pix/icon.svg');
|
||||
echo $OUTPUT->render_from_template('core_h5p/h5perror', $messages);
|
||||
}
|
||||
|
||||
// Display the H5P content.
|
||||
echo $h5pplayer->output();
|
||||
} else {
|
||||
// If there is any error or exception when creating the player, it should be displayed.
|
||||
$PAGE->set_context(context_system::instance());
|
||||
@@ -80,12 +83,9 @@ if (empty($messages->error) && empty($messages->exception)) {
|
||||
$PAGE->add_body_class('h5p-embed');
|
||||
$PAGE->set_pagelayout('embedded');
|
||||
|
||||
// Errors can't be printed yet, because some more errors might been added while preparing the output
|
||||
}
|
||||
|
||||
if (!empty($messages->error) || !empty($messages->exception)) {
|
||||
// Print all the errors.
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// Print all the errors.
|
||||
$messages->h5picon = new \moodle_url('/h5p/pix/icon.svg');
|
||||
echo $OUTPUT->render_from_template('core_h5p/h5perror', $messages);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
Variables required for this template:
|
||||
* h5picon - The icon
|
||||
* message - The error message to display.
|
||||
* message - The error messages to display.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -33,7 +33,9 @@
|
||||
<div class="d-flex h-100 position-relative align-items-center bg-light h5pmessages">
|
||||
<div class="position-absolute py-2 bg-secondary" style="top: 0px; left: 0px; right: 0px;">
|
||||
<div class="container">
|
||||
<img src="{{{h5picon}}}" class="h5picon" alt="{{#str}}h5p, core_h5p{{/str}}" style="width: 50px; height: auto; opacity: 0.5">
|
||||
{{#h5picon}}
|
||||
<img src="{{{.}}}" class="h5picon" alt="{{#str}}h5p, core_h5p{{/str}}" style="width: 50px; height: auto; opacity: 0.5">
|
||||
{{/h5picon}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-5">
|
||||
@@ -42,9 +44,14 @@
|
||||
{{{ exception }}}
|
||||
</div>
|
||||
{{/exception}}
|
||||
{{#info}}
|
||||
<div class="alert alert-block fade in alert-info my-2" role="alert">
|
||||
{{{ . }}}
|
||||
</div>
|
||||
{{/info}}
|
||||
{{#error}}
|
||||
<div class="alert alert-block fade in alert-warning my-2" role="alert">
|
||||
{{{code}}} : {{{ message }}}
|
||||
{{#code}}{{{code}}} : {{/code}}{{{ message }}}
|
||||
</div>
|
||||
{{/error}}
|
||||
</div>
|
||||
|
||||
BIN
Binary file not shown.
@@ -702,10 +702,107 @@ class framework_testcase extends \advanced_testcase {
|
||||
/**
|
||||
* Test the behaviour of mayUpdateLibraries().
|
||||
*/
|
||||
public function test_mayUpdateLibraries() {
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
public function test_mayUpdateLibraries(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create some users.
|
||||
$contextsys = \context_system::instance();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$admin = get_admin();
|
||||
$managerrole = $DB->get_record('role', ['shortname' => 'manager'], '*', MUST_EXIST);
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
|
||||
$manager = $this->getDataGenerator()->create_user();
|
||||
role_assign($managerrole->id, $manager->id, $contextsys);
|
||||
|
||||
// Create a course with a label and enrol the user.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
|
||||
list(, $labelcm) = get_course_and_cm_from_instance($label->id, 'label');
|
||||
$contextlabel = \context_module::instance($labelcm->id);
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
|
||||
// Create the .h5p file.
|
||||
$path = __DIR__ . '/fixtures/h5ptest.zip';
|
||||
|
||||
// Admin and manager should have permission to update libraries.
|
||||
$file = helper::create_fake_stored_file_from_path($path, $admin->id, $contextsys);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertTrue($mayupdatelib);
|
||||
|
||||
$file = helper::create_fake_stored_file_from_path($path, $manager->id, $contextsys);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertTrue($mayupdatelib);
|
||||
|
||||
// By default, normal user hasn't permission to update libraries (in both contexts, system and module label).
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id, $contextsys);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertFalse($mayupdatelib);
|
||||
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id, $contextlabel);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertFalse($mayupdatelib);
|
||||
|
||||
// If the current user (admin) can update libraries, the method should return true (even if the file userid hasn't the
|
||||
// required capabilility in the file context).
|
||||
$file = helper::create_fake_stored_file_from_path($path, $admin->id, $contextlabel);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertTrue($mayupdatelib);
|
||||
|
||||
// If the update capability is assigned to the user, they should be able to update the libraries (only in the context
|
||||
// where the capability has been assigned).
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id, $contextlabel);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertFalse($mayupdatelib);
|
||||
assign_capability('moodle/h5p:updatelibraries', CAP_ALLOW, $studentrole->id, $contextlabel);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertTrue($mayupdatelib);
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id, $contextsys);
|
||||
$this->framework->set_file($file);
|
||||
$mayupdatelib = $this->framework->mayUpdateLibraries();
|
||||
$this->assertFalse($mayupdatelib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of get_file() and set_file().
|
||||
*/
|
||||
public function test_get_file(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create some users.
|
||||
$contextsys = \context_system::instance();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
// The H5P file.
|
||||
$path = __DIR__ . '/fixtures/h5ptest.zip';
|
||||
|
||||
// An error should be raised when it's called before initialitzing it.
|
||||
$this->expectException('coding_exception');
|
||||
$this->expectExceptionMessage('Using get_file() before file is set');
|
||||
$this->framework->get_file();
|
||||
|
||||
// Check the value when only path and user are set.
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id);
|
||||
$this->framework->set_file($file);
|
||||
$file = $this->framework->get_file();
|
||||
$this->assertEquals($user->id, $$file->get_userid());
|
||||
$this->assertEquals($contextsys->id, $file->get_contextid());
|
||||
|
||||
// Check the value when also the context is set.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$contextcourse = \context_course::instance($course->id);
|
||||
$file = helper::create_fake_stored_file_from_path($path, $user->id, $contextcourse);
|
||||
$this->framework->set_file($file);
|
||||
$file = $this->framework->get_file();
|
||||
$this->assertEquals($user->id, $$file->get_userid());
|
||||
$this->assertEquals($contextcourse->id, $file->get_contextid());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -845,6 +942,41 @@ class framework_testcase extends \advanced_testcase {
|
||||
$this->assertEquals($content['disable'], $dbcontent->displayoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of insertContent().
|
||||
*/
|
||||
public function test_insertContent_latestlibrary() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
|
||||
// Create a library record.
|
||||
$lib = $generator->create_library_record('TestLibrary', 'Test', 1, 1, 2);
|
||||
|
||||
$content = array(
|
||||
'params' => json_encode(['param1' => 'Test']),
|
||||
'library' => array(
|
||||
'libraryId' => 0,
|
||||
'machineName' => 'TestLibrary',
|
||||
),
|
||||
'disable' => 8
|
||||
);
|
||||
|
||||
// Insert h5p content.
|
||||
$contentid = $this->framework->insertContent($content);
|
||||
|
||||
// Get the entered content from the db.
|
||||
$dbcontent = $DB->get_record('h5p', ['id' => $contentid]);
|
||||
|
||||
// Make sure the h5p content was properly inserted.
|
||||
$this->assertNotEmpty($dbcontent);
|
||||
$this->assertEquals($content['params'], $dbcontent->jsoncontent);
|
||||
$this->assertEquals($content['disable'], $dbcontent->displayoptions);
|
||||
// As the libraryId was empty, the latest library has been used.
|
||||
$this->assertEquals($lib->id, $dbcontent->mainlibraryid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of updateContent().
|
||||
*/
|
||||
@@ -2014,4 +2146,48 @@ class framework_testcase extends \advanced_testcase {
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the behaviour of get_latest_library_version().
|
||||
*/
|
||||
public function test_get_latest_library_version() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
|
||||
// Create a library record.
|
||||
$machinename = 'TestLibrary';
|
||||
$lib1 = $generator->create_library_record($machinename, 'Test', 1, 1, 2);
|
||||
$lib2 = $generator->create_library_record($machinename, 'Test', 1, 2, 1);
|
||||
|
||||
$content = array(
|
||||
'params' => json_encode(['param1' => 'Test']),
|
||||
'library' => array(
|
||||
'libraryId' => 0,
|
||||
'machineName' => 'TestLibrary',
|
||||
),
|
||||
'disable' => 8
|
||||
);
|
||||
|
||||
// Get the latest id (at this point, should be lib2).
|
||||
$latestlib = $this->framework->get_latest_library_version($machinename);
|
||||
$this->assertEquals($lib2->id, $latestlib->id);
|
||||
|
||||
// Get the latest id (at this point, should be lib3).
|
||||
$lib3 = $generator->create_library_record($machinename, 'Test', 2, 1, 0);
|
||||
$latestlib = $this->framework->get_latest_library_version($machinename);
|
||||
$this->assertEquals($lib3->id, $latestlib->id);
|
||||
|
||||
// Get the latest id (at this point, should be still lib3).
|
||||
$lib4 = $generator->create_library_record($machinename, 'Test', 1, 1, 3);
|
||||
$latestlib = $this->framework->get_latest_library_version($machinename);
|
||||
$this->assertEquals($lib3->id, $latestlib->id);
|
||||
|
||||
// Get the latest id (at this point, should be lib5).
|
||||
$lib5 = $generator->create_library_record($machinename, 'Test', 2, 1, 6);
|
||||
$latestlib = $this->framework->get_latest_library_version($machinename);
|
||||
$this->assertEquals($lib5->id, $latestlib->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
<?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 helper.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @category test
|
||||
* @copyright 2019 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use advanced_testcase;
|
||||
|
||||
/**
|
||||
* Test class covering the H5P helper.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2019 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the behaviour of get_display_options().
|
||||
*
|
||||
* @dataProvider get_display_options_provider
|
||||
* @param bool $frame Whether the frame should be displayed or not
|
||||
* @param bool $export Whether the export action button should be displayed or not
|
||||
* @param bool $embed Whether the embed action button should be displayed or not
|
||||
* @param bool $copyright Whether the copyright action button should be displayed or not
|
||||
* @param int $expected The expectation with the displayoptions value
|
||||
*/
|
||||
public function test_get_display_options(bool $frame, bool $export, bool $embed, bool $copyright, int $expected): void {
|
||||
$this->setRunTestInSeparateProcess(true);
|
||||
$this->resetAfterTest();
|
||||
|
||||
$factory = new \core_h5p\factory();
|
||||
$core = $factory->get_core();
|
||||
$config = (object)[
|
||||
'frame' => $frame,
|
||||
'export' => $export,
|
||||
'embed' => $embed,
|
||||
'copyright' => $copyright,
|
||||
];
|
||||
$displayoptions = helper::get_display_options($core, $config);
|
||||
|
||||
$this->assertEquals($expected, $displayoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_display_options().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_display_options_provider(): array {
|
||||
return [
|
||||
'All display options disabled' => [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
15,
|
||||
],
|
||||
'All display options enabled' => [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
0,
|
||||
],
|
||||
'Frame disabled and the rest enabled' => [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
0,
|
||||
],
|
||||
'Only export enabled' => [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
12,
|
||||
],
|
||||
'Only embed enabled' => [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
10,
|
||||
],
|
||||
'Only copyright enabled' => [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
6,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of save_h5p() when there are some missing libraries in the system.
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function test_save_h5p_missing_libraries(): void {
|
||||
$this->resetAfterTest();
|
||||
$factory = new \core_h5p\factory();
|
||||
|
||||
// Create a user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
// This is a valid .H5P file.
|
||||
$path = __DIR__ . '/fixtures/greeting-card-887.h5p';
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
|
||||
$config = (object)[
|
||||
'frame' => 1,
|
||||
'export' => 1,
|
||||
'embed' => 0,
|
||||
'copyright' => 0,
|
||||
];
|
||||
|
||||
// There are some missing libraries in the system, so an error should be returned.
|
||||
$h5pid = helper::save_h5p($factory, $file, $config);
|
||||
$this->assertFalse($h5pid);
|
||||
$errors = $factory->get_framework()->getMessages('error');
|
||||
$this->assertCount(1, $errors);
|
||||
$error = reset($errors);
|
||||
$this->assertEquals('missing-required-library', $error->code);
|
||||
$this->assertEquals('Missing required library H5P.GreetingCard 1.0', $error->message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of save_h5p() when the libraries exist in the system.
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function test_save_h5p_existing_libraries(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$factory = new \core_h5p\factory();
|
||||
|
||||
// Create a user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
// This is a valid .H5P file.
|
||||
$path = __DIR__ . '/fixtures/greeting-card-887.h5p';
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
|
||||
$config = (object)[
|
||||
'frame' => 1,
|
||||
'export' => 1,
|
||||
'embed' => 0,
|
||||
'copyright' => 0,
|
||||
];
|
||||
// The required libraries exist in the system before saving the .h5p file.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
|
||||
$lib = $generator->create_library_record('H5P.GreetingCard', 'GreetingCard', 1, 0);
|
||||
$h5pid = helper::save_h5p($factory, $file, $config);
|
||||
$this->assertNotEmpty($h5pid);
|
||||
|
||||
// No errors are raised.
|
||||
$errors = $factory->get_framework()->getMessages('error');
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// And the content in the .h5p file has been saved as expected.
|
||||
$h5p = $DB->get_record('h5p', ['id' => $h5pid]);
|
||||
$this->assertEquals($lib->id, $h5p->mainlibraryid);
|
||||
$this->assertEquals(helper::get_display_options($factory->get_core(), $config), $h5p->displayoptions);
|
||||
$this->assertContains('Hello world!', $h5p->jsoncontent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of save_h5p() when the .h5p file is invalid.
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function test_save_h5p_invalid_file(): void {
|
||||
$this->resetAfterTest();
|
||||
$factory = new \core_h5p\factory();
|
||||
|
||||
// Create a user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
// Prepare an invalid .H5P file.
|
||||
$path = __DIR__ . '/fixtures/h5ptest.zip';
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
$config = (object)[
|
||||
'frame' => 1,
|
||||
'export' => 1,
|
||||
'embed' => 0,
|
||||
'copyright' => 0,
|
||||
];
|
||||
|
||||
// When saving an invalid .h5p file, an error should be raised.
|
||||
$h5pid = helper::save_h5p($factory, $file, $config);
|
||||
$this->assertFalse($h5pid);
|
||||
$errors = $factory->get_framework()->getMessages('error');
|
||||
$this->assertCount(2, $errors);
|
||||
|
||||
$expectederrorcodes = ['invalid-content-folder', 'invalid-h5p-json-file'];
|
||||
foreach ($errors as $error) {
|
||||
$this->assertContains($error->code, $expectederrorcodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of can_deploy_package().
|
||||
*/
|
||||
public function test_can_deploy_package(): void {
|
||||
$this->resetAfterTest();
|
||||
$factory = new \core_h5p\factory();
|
||||
|
||||
// Create a user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$admin = get_admin();
|
||||
|
||||
// Prepare a valid .H5P file.
|
||||
$path = __DIR__ . '/fixtures/greeting-card-887.h5p';
|
||||
|
||||
// Files created by users can't be deployed.
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
$candeploy = helper::can_deploy_package($file);
|
||||
$this->assertFalse($candeploy);
|
||||
|
||||
// Files created by admins can be deployed, even when the current user is not the admin.
|
||||
$this->setUser($user);
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$admin->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
$candeploy = helper::can_deploy_package($file);
|
||||
$this->assertTrue($candeploy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of can_update_library().
|
||||
*/
|
||||
public function can_update_library(): void {
|
||||
$this->resetAfterTest();
|
||||
$factory = new \core_h5p\factory();
|
||||
|
||||
// Create a user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$admin = get_admin();
|
||||
|
||||
// Prepare a valid .H5P file.
|
||||
$path = __DIR__ . '/fixtures/greeting-card-887.h5p';
|
||||
|
||||
// Libraries can't be updated when the file has been created by users.
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
$candeploy = helper::can_update_library($file);
|
||||
$this->assertFalse($candeploy);
|
||||
|
||||
// Libraries can be updated when the file has been created by admin, even when the current user is not the admin.
|
||||
$this->setUser($user);
|
||||
$file = helper::create_fake_stored_file_from_path($path, (int)$admin->id);
|
||||
$factory->get_framework()->set_file($file);
|
||||
$candeploy = helper::can_update_library($file);
|
||||
$this->assertTrue($candeploy);
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,8 @@ $string['nocopyright'] = 'No copyright information available for this content.';
|
||||
$string['noextension'] = 'The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)';
|
||||
$string['nopermissiontodeploy'] = 'This file can\'t be displayed because it has been uploaded by a user without the required capability to deploy H5P content.';
|
||||
$string['nojson'] = 'The main h5p.json file is not valid';
|
||||
$string['notrustablefile'] = 'This file can\'t be displayed because it has been uploaded by a user without the capability to update H5P content types.
|
||||
Please contact your administrator to ask for the content type to be installed.';
|
||||
$string['nounzip'] = 'The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)';
|
||||
$string['offlineDialogBody'] = 'We were unable to send information about your completion of this task. Please check your internet connection.';
|
||||
$string['offlineDialogHeader'] = 'Your connection to the server was lost';
|
||||
|
||||
+2
-1
@@ -261,6 +261,7 @@ $string['grade:view'] = 'View own grades';
|
||||
$string['grade:viewall'] = 'View grades of other users';
|
||||
$string['grade:viewhidden'] = 'View hidden grades for owner';
|
||||
$string['h5p:deploy'] = 'Deploy H5P content';
|
||||
$string['h5p:updatelibraries'] = 'Manage H5P content types';
|
||||
$string['h5p:setdisplayoptions'] = 'Set H5P display options';
|
||||
$string['highlightedcellsshowdefault'] = 'The permissions highlighted in the table below are the defaults for the role archetype currently selected above.';
|
||||
$string['highlightedcellsshowinherit'] = 'The highlighted cells in the table below show the permission (if any) that will be inherited. Apart from the capabilities whose permission you actually want to alter, you should leave everything set to Inherit.';
|
||||
@@ -496,4 +497,4 @@ $string['privacy:metadata:role_cohortroles'] = 'Roles to cohort';
|
||||
$string['course:togglecompletion'] = 'Manually mark activities as complete';
|
||||
|
||||
// Deprecated since Moodle 3.8.
|
||||
$string['eventrolecapabilitiesupdated'] = 'Role capabilities updated';
|
||||
$string['eventrolecapabilitiesupdated'] = 'Role capabilities updated';
|
||||
|
||||
@@ -2450,4 +2450,14 @@ $capabilities = array(
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
)
|
||||
),
|
||||
|
||||
// Allow to update H5P content-type libraries.
|
||||
'moodle/h5p:updatelibraries' => [
|
||||
'riskbitmask' => RISK_XSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => [
|
||||
'manager' => CAP_ALLOW,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2019110500.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2019110500.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user