Merge branch 'MDL-71956-master' of https://github.com/sarjona/moodle
This commit is contained in:
@@ -49,7 +49,7 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
* @return string
|
||||
*/
|
||||
public function filter($text, array $options = array()) {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
|
||||
if (!is_string($text) or empty($text)) {
|
||||
// Non string data can not be filtered anyway.
|
||||
@@ -83,7 +83,10 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
// It is needed to add "/embed" at the end of URLs like https:://*.h5p.com/content/12345 (H5P.com).
|
||||
$params['urlmodifier'] = '';
|
||||
|
||||
if (($source == $localsource)) {
|
||||
// Local files may display a button below the content to modify it when editing mode is on. This button will appear
|
||||
// only if the user has the proper capabilities.
|
||||
$params['canbeedited'] = (!empty($USER->editing)) && ($source == $localsource);
|
||||
if ($source == $localsource) {
|
||||
$params['tagbegin'] = '<iframe src="'.$CFG->wwwroot.'/h5p/embed.php?url=';
|
||||
$escapechars = $source;
|
||||
$ultimatepattern = $source;
|
||||
@@ -103,7 +106,7 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
}
|
||||
|
||||
$h5pcontenturl = new filterobject($source, null, null, false,
|
||||
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params);
|
||||
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params + ['ish5plink' => false]);
|
||||
|
||||
$h5pcontenturl->workregexp = '#'.$ultimatepattern.'#';
|
||||
$h5pcontents[] = $h5pcontenturl;
|
||||
@@ -112,7 +115,7 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
$linkregexp = '~<a [^>]*href=["\']('.$escapechars.'[^"\']*)["\'][^>]*>([^<]*)</a>~is';
|
||||
|
||||
$h5plinkurl = new filterobject($linkregexp, null, null, false,
|
||||
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params);
|
||||
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params + ['ish5plink' => true]);
|
||||
$h5plinkurl->workregexp = $linkregexp;
|
||||
$h5plinks[] = $h5plinkurl;
|
||||
}
|
||||
@@ -135,7 +138,52 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
return $matches[0];
|
||||
}
|
||||
}, $text);
|
||||
}
|
||||
|
||||
// The "Edit" button below each H5P content will be displayed only for users with permissions to edit the content (to
|
||||
// avoid confusion). So the original H5P file behind this URL will be obtained and checked using the methods in the API.
|
||||
// As the H5P URL is required in order to get this information, this action can be done only here(the
|
||||
// prepare_replacement_callback method has only the placeholders).
|
||||
foreach ($h5pcontents as $h5pcontent) {
|
||||
$text = preg_replace_callback($h5pcontent->workregexp,
|
||||
function ($matches) use ($h5pcontent) {
|
||||
global $USER, $CFG;
|
||||
|
||||
// The Edit button placeholder has been added only if the file can be edited.
|
||||
if ($h5pcontent->replacementcallbackdata['canbeedited']) {
|
||||
// If the content was originally a link, ignore it (it won't have the placeholder).
|
||||
$matchurl = new \moodle_url($matches[0]);
|
||||
if (strpos($matchurl->get_path(), 'h5p/embed.php') !== false) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$contenturl = $matches[0];
|
||||
list($file, $h5p) = \core_h5p\api::get_original_content_from_pluginfile_url($contenturl, true, true);
|
||||
if ($file) {
|
||||
filter_prepare_phrase_for_replacement($h5pcontent);
|
||||
|
||||
// Check if the user can edit this content.
|
||||
if (\core_h5p\api::can_edit_content($file)) {
|
||||
// If the user can modify the content, replace the placeholder with a link to the editor.
|
||||
$title = get_string('editcontent', 'core_h5p');
|
||||
$editorurl = $CFG->wwwroot . '/h5p/edit.php?url=' . $contenturl;
|
||||
$htmlcode = html_writer::start_tag(
|
||||
'a',
|
||||
['class' => 'autolink', 'title' => $title, 'href' => $editorurl]
|
||||
);
|
||||
$htmlcode .= $title . html_writer::end_tag('a');
|
||||
$content = str_replace('$2', $htmlcode, $h5pcontent->workreplacementphrase);
|
||||
} else {
|
||||
// If the user can't edit the content, remove the placeholder.
|
||||
$content = str_replace('$2', '', $h5pcontent->workreplacementphrase);
|
||||
}
|
||||
|
||||
return str_replace('$1', $contenturl, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
}, $text);
|
||||
}
|
||||
|
||||
$result = filter_phrases($text, $h5pcontents, null, null, false, true);
|
||||
@@ -166,9 +214,12 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
* @param string $tagbegin HTML to insert before any match
|
||||
* @param string $tagend HTML to insert after any match
|
||||
* @param string $urlmodifier string to add to the match URL
|
||||
* @param bool $canbeedited Whether the content can be modified or not (to display a link to edit it or not).
|
||||
* @param bool $ish5plink Whether the original content comes from an H5P link or not.
|
||||
* @return array [$hreftagbegin, $hreftagend, $replacementphrase] for filterobject.
|
||||
*/
|
||||
public function filterobject_prepare_replacement_callback($tagbegin, $tagend, $urlmodifier) {
|
||||
public function filterobject_prepare_replacement_callback($tagbegin, $tagend, $urlmodifier, $canbeedited, $ish5plink) {
|
||||
|
||||
$sourceurl = "$1";
|
||||
if ($urlmodifier !== "") {
|
||||
$sourceurl .= $urlmodifier;
|
||||
@@ -184,6 +235,11 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
self::$loadresizerjs = false;
|
||||
}
|
||||
|
||||
if ($canbeedited && !$ish5plink) {
|
||||
// Placeholder to be replaced by the edit content button (depending on the user permissions).
|
||||
$tagend .= "$2";
|
||||
}
|
||||
|
||||
return [$tagbegin, $tagend, $h5piframesrc];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
@editor @editor_atto @atto @atto_h5p @filter @filter_displayh5p @core_h5p @_file_upload @_switch_iframe
|
||||
Feature: Inline editing H5P content anywhere
|
||||
In order to edit an existing H5P content
|
||||
As a user
|
||||
I need to see the button and access to the H5P editor
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| teacher2 | Teacher | 2 | teacher2@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | teacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "contentbank content" exist:
|
||||
| contextlevel | reference | contenttype | user | contentname | filepath |
|
||||
| Course | C1 | contenttype_h5p | admin | Greeting card | /h5p/tests/fixtures/greeting-card-887.h5p |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | introformat | course | content | contentformat | idnumber |
|
||||
| page | PageName1 | PageDesc1 | 1 | C1 | H5Ptest | 1 | 1 |
|
||||
And the "displayh5p" filter is "on"
|
||||
# Override this capability to let teachers and students to Turn editing on.
|
||||
And the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| moodle/course:update | Allow | teacher | System | |
|
||||
| moodle/course:update | Allow | student | System | |
|
||||
|
||||
@javascript @mod @mod_page
|
||||
Scenario: Edit H5P content from a page using link to private file
|
||||
Given the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| moodle/h5p:updatelibraries | Allow | editingteacher | System | |
|
||||
And I log in as "teacher1"
|
||||
# Upload the H5P to private user files.
|
||||
And I follow "Manage private files..."
|
||||
And I upload "h5p/tests/fixtures/greeting-card-887.h5p" file to "Files" filemanager
|
||||
And I click on "Save changes" "button"
|
||||
# Add H5P content to the page.
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Private files" repository in file picker
|
||||
And I click on "greeting-card-887.h5p" "file" in repository content area
|
||||
And I click on "Link to the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I click on "Save and display" "button"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should not see "Edit H5P content"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
Then I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check admin can't see the Edit button (it's a private file and only the author can edit it).
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
|
||||
@javascript @mod @mod_page @repository_contentbank
|
||||
Scenario: Edit H5P content from a page using link to content bank file
|
||||
Given I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to the page.
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "Greeting card" "file" in repository content area
|
||||
And I click on "Link to the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I click on "Save and display" "button"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should not see "Edit H5P content"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
Then I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can see the Edit button too.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
|
||||
@javascript @mod @mod_page @repository_contentbank
|
||||
Scenario: Edit H5P content from a page using copy to content bank file
|
||||
Given I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to the page.
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "Greeting card" "file" in repository content area
|
||||
And I click on "Make a copy of the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I click on "Save and display" "button"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should not see "Edit H5P content"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
Then I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can see the Edit button too.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
|
||||
@javascript @mod @mod_page
|
||||
Scenario: Edit H5P content from a page using external URL
|
||||
Given the following config values are set as admin:
|
||||
| allowedsources | https://moodle.h5p.com/content/[id] | filter_displayh5p |
|
||||
And I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to the page.
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
|
||||
And I set the field with xpath "//input[@data-region='h5pfile']" to "https://moodle.h5p.com/content/1290772960722742119"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I click on "Save and display" "button"
|
||||
And ".h5p-placeholder" "css_element" should exist
|
||||
And I switch to "h5pcontent" iframe
|
||||
And I should see "Lorum ipsum"
|
||||
And I switch to the main frame
|
||||
# The Edit button is never displayed (because it's not a local file).
|
||||
And I should not see "Edit H5P content"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
Then I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can't see the Edit button.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "PageName1" "page activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
|
||||
@javascript @block @block_html @core_block @repository_contentbank
|
||||
Scenario: Edit H5P content from a block using copy to content bank file
|
||||
Given I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to the block.
|
||||
And I turn editing mode on
|
||||
And I add the "Text" block
|
||||
And I configure the "(new text block)" block
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_config_text" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "Greeting card" "file" in repository content area
|
||||
And I click on "Make a copy of the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I press "Save changes"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should see "Edit H5P content"
|
||||
When I turn editing mode off
|
||||
Then I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can see the Edit button too.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I should not see "Edit H5P content"
|
||||
+20
-7
@@ -267,7 +267,8 @@ class api {
|
||||
* - The user is the author of the file.
|
||||
* - The component is different from user (i.e. private files).
|
||||
* - If the component is contentbank, the user can edit this file (calling the ContentBank API).
|
||||
* - If the component is mod_h5pactivity, the user has the addinstance capability.
|
||||
* - If the component is mod_xxx or block_xxx, the user has the addinstance capability.
|
||||
* - If the component implements the can_edit_content in the h5p\canedit class and the callback to this method returns true.
|
||||
*
|
||||
* @param \stored_file $file The H5P file to check.
|
||||
*
|
||||
@@ -277,25 +278,37 @@ class api {
|
||||
public static function can_edit_content(\stored_file $file): bool {
|
||||
global $USER;
|
||||
|
||||
list($type, $component) = \core_component::normalize_component($file->get_component());
|
||||
|
||||
// Private files.
|
||||
$currentuserisauthor = $file->get_userid() == $USER->id;
|
||||
$isuserfile = $file->get_component() === 'user';
|
||||
$isuserfile = $component === 'user';
|
||||
if ($currentuserisauthor && $isuserfile) {
|
||||
// The user can edit the content because it's a private user file and she is the owner.
|
||||
return true;
|
||||
}
|
||||
|
||||
// For mod_h5pactivity, check whether the user can add/edit them.
|
||||
if ($file->get_component() === 'mod_h5pactivity') {
|
||||
// Check if the plugin where the file belongs implements the custom can_edit_content method and call it if that's the case.
|
||||
$classname = '\\' . $file->get_component() . '\\h5p\\canedit';
|
||||
$methodname = 'can_edit_content';
|
||||
if (method_exists($classname, $methodname)) {
|
||||
return $classname::{$methodname}($file);
|
||||
}
|
||||
|
||||
// For mod/block files, check if the user has the addinstance capability of the component where the file belongs.
|
||||
if ($type === 'mod' || $type === 'block') {
|
||||
// For any other component, check whether the user can add/edit them.
|
||||
$context = \context::instance_by_id($file->get_contextid());
|
||||
if (has_capability("mod/h5pactivity:addinstance", $context)) {
|
||||
// The user can edit the content because she has the capability for creating H5P activities where the file belongs.
|
||||
$plugins = \core_component::get_plugin_list($type);
|
||||
$isvalid = array_key_exists($component, $plugins);
|
||||
if ($isvalid && has_capability("$type/$component:addinstance", $context)) {
|
||||
// The user can edit the content because she has the capability for creating instances where the file belongs.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For contentbank files, use the API to check if the user has access.
|
||||
if ($file->get_component() == 'contentbank') {
|
||||
if ($component == 'contentbank') {
|
||||
$cb = new \core_contentbank\contentbank();
|
||||
$content = $cb->get_content_from_id($file->get_itemid());
|
||||
$contenttype = $content->get_content_type_instance();
|
||||
|
||||
+90
-5
@@ -27,6 +27,8 @@ declare(strict_types = 1);
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
@@ -444,11 +446,13 @@ class api_test extends \advanced_testcase {
|
||||
* @param string $fileauthor Author of the file to check.
|
||||
* @param string $filecomponent Component of the file to check.
|
||||
* @param bool $expected Expected result after calling the can_edit_content method.
|
||||
* @param string $filearea Area of the file to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_can_edit_content(string $currentuser, string $fileauthor, string $filecomponent, bool $expected): void {
|
||||
global $USER;
|
||||
public function test_can_edit_content(string $currentuser, string $fileauthor, string $filecomponent, bool $expected,
|
||||
$filearea = 'unittest'): void {
|
||||
global $USER, $DB;
|
||||
|
||||
$this->setRunTestInSeparateProcess(true);
|
||||
$this->resetAfterTest();
|
||||
@@ -472,6 +476,20 @@ class api_test extends \advanced_testcase {
|
||||
$this->setUser($users[$currentuser]);
|
||||
}
|
||||
|
||||
$itemid = rand();
|
||||
if ($filearea === 'post') {
|
||||
// Create a forum and add a discussion.
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $users[$fileauthor]->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
$post = $DB->get_record('forum_posts', ['discussion' => $discussion->id]);
|
||||
$itemid = $post->id;
|
||||
}
|
||||
|
||||
// Create the file.
|
||||
$filename = 'greeting-card-887.h5p';
|
||||
$path = __DIR__ . '/fixtures/' . $filename;
|
||||
@@ -491,8 +509,8 @@ class api_test extends \advanced_testcase {
|
||||
$filerecord = [
|
||||
'contextid' => $context->id,
|
||||
'component' => $filecomponent,
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => rand(),
|
||||
'filearea' => $filearea,
|
||||
'itemid' => $itemid,
|
||||
'filepath' => '/',
|
||||
'filename' => basename($path),
|
||||
'userid' => $users[$fileauthor]->id,
|
||||
@@ -589,19 +607,80 @@ class api_test extends \advanced_testcase {
|
||||
'expected' => false,
|
||||
],
|
||||
|
||||
// Component = mod_book.
|
||||
'mod_book: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_book',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_book: Admin user, teacher is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_book',
|
||||
'expected' => true,
|
||||
],
|
||||
|
||||
// Component = mod_forum.
|
||||
'mod_forum: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => false,
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Admin user, teacher is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Teacher user, admin is author' => [
|
||||
'currentuser' => 'teacher',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Student user, teacher is author' => [
|
||||
'currentuser' => 'student',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => false,
|
||||
],
|
||||
'mod_forum/post: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
'mod_forum/post: Teacher user, admin is author' => [
|
||||
'currentuser' => 'teacher',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
'mod_forum/post: Student user, teacher is author' => [
|
||||
'currentuser' => 'student',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => false,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
|
||||
// Component = block_html.
|
||||
'block_html: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'block_html',
|
||||
'expected' => true,
|
||||
],
|
||||
'block_html: Admin user, teacher is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'block_html',
|
||||
'expected' => true,
|
||||
],
|
||||
|
||||
// Component = contentbank.
|
||||
'contentbank: Admin user is author' => [
|
||||
@@ -654,6 +733,12 @@ class api_test extends \advanced_testcase {
|
||||
'filecomponent' => 'mod_unexisting',
|
||||
'expected' => false,
|
||||
],
|
||||
'Unexisting block' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'block_unexisting',
|
||||
'expected' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ information provided here is intended especially for developers.
|
||||
* Added edit.php and editcontent_form class, for modifying H5P content given an H5P identifier (from the h5p table).
|
||||
* Added a new parameter to the player::display method, to define whether the edit button should be displayed below the
|
||||
H5P content or not. Default value for this parameter is false.
|
||||
* H5P subsystem is allowed to act as an API (level 2) too.
|
||||
* Plugins can now implement h5p\canedit::can_edit_content method to define, if required, any custom behaviour for deciding
|
||||
whether an H5P content can be edited or not. The specific plugin check will completely override the generic check.
|
||||
|
||||
=== 3.11 ===
|
||||
* Added $skipcapcheck parameter to H5P constructor, api::create_content_from_pluginfile_url() and
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_forum\h5p;
|
||||
|
||||
/**
|
||||
* Class to check if the H5P content can be edited for this plugin.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2021 Sara Arjona ([email protected])
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class canedit {
|
||||
|
||||
/**
|
||||
* Check if the user can edit an H5P file. In that case, this method will return true if the file belongs to mod_forum
|
||||
* filearea is post and the user can edit the post where the H5P is.
|
||||
*
|
||||
* @param \stored_file $file The H5P file to check.
|
||||
*
|
||||
* @return boolean Whether the user can edit or not the given file.
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public static function can_edit_content(\stored_file $file): bool {
|
||||
global $USER;
|
||||
|
||||
list($type, $component) = \core_component::normalize_component($file->get_component());
|
||||
|
||||
if ($type === 'mod' && $component === 'forum') {
|
||||
// For mod_forum files in posts, check if the user can edit the post where the H5P is.
|
||||
if ($file->get_filearea() === 'post') {
|
||||
// Check if the user can edit the forum post.
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$postid = $file->get_itemid();
|
||||
$postentity = $postvault->get_from_id($postid);
|
||||
if (!empty($postentity)) {
|
||||
$discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
|
||||
$managerfactory = \mod_forum\local\container::get_manager_factory();
|
||||
$forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
if ($capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For any other fileare, check whether the user can add/edit them.
|
||||
$context = \context::instance_by_id($file->get_contextid());
|
||||
$plugins = \core_component::get_plugin_list($type);
|
||||
$isvalid = array_key_exists($component, $plugins);
|
||||
if ($isvalid && has_capability("$type/$component:addinstance", $context)) {
|
||||
// The user can edit the content because she has the capability for creating instances where the file belongs.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
@mod @mod_forum @editor @editor_atto @atto @atto_h5p @filter @filter_displayh5p @core_h5p @_file_upload @_switch_iframe
|
||||
Feature: Inline editing H5P content in mod_forum
|
||||
In order to edit an existing H5P content
|
||||
As a user
|
||||
I need to see the button and access to the H5P editor
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| teacher2 | Teacher | 2 | teacher2@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | teacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "contentbank content" exist:
|
||||
| contextlevel | reference | contenttype | user | contentname | filepath |
|
||||
| Course | C1 | contenttype_h5p | admin | Greeting card | /h5p/tests/fixtures/greeting-card-887.h5p |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | introformat | course | content | contentformat | idnumber |
|
||||
| forum | ForumName1 | PageDesc1 | 1 | C1 | H5Ptest | 1 | 1 |
|
||||
And the "displayh5p" filter is "on"
|
||||
# Override this capability to let teachers and students to Turn editing on.
|
||||
And the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| moodle/course:update | Allow | teacher | System | |
|
||||
| moodle/course:update | Allow | student | System | |
|
||||
|
||||
@javascript @repository_contentbank
|
||||
Scenario: Edit H5P content from a forum intro using copy to content bank file
|
||||
Given I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to the forum description.
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_introeditor" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "Greeting card" "file" in repository content area
|
||||
And I click on "Make a copy of the file" "radio"
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I click on "Save and display" "button"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should not see "Edit H5P content"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
Then I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can see the Edit button too.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can't see the Edit button, because she can't edit the forum activity.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I should not see "Edit H5P content"
|
||||
|
||||
@javascript @repository_contentbank
|
||||
Scenario: Edit H5P content from a forum post
|
||||
Given I am on the "C1" "Course" page logged in as "admin"
|
||||
# Add H5P content to a forum post as admin.
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I click on "Add a new discussion topic" "link"
|
||||
And I set the following fields to these values:
|
||||
| Subject | Forum post by admin |
|
||||
And I click on "Insert H5P" "button" in the "#fitem_id_message" "css_element"
|
||||
And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
|
||||
And I select "Content bank" repository in file picker
|
||||
And I click on "Greeting card" "file" in repository content area
|
||||
And I click on "Select this file" "button"
|
||||
And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
|
||||
And I press "Post to forum"
|
||||
And I follow "Forum post by admin"
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I switch to "h5p-iframe" class iframe
|
||||
And I should see "Hello world!"
|
||||
And I switch to the main frame
|
||||
# The Edit button is only displayed when editing mode is on.
|
||||
And I should not see "Edit H5P content"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I follow "Forum post by admin"
|
||||
Then I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher1 can see the Edit button because she can edit the post too.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I follow "Forum post by admin"
|
||||
And I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check teacher2 (non-editing teacher) can see the Edit button because she can edit the post too.
|
||||
And I log in as "teacher2"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I follow "Forum post by admin"
|
||||
And I should see "Edit H5P content"
|
||||
And I log out
|
||||
# Check student1 can't see the Edit button.
|
||||
And I log in as "student1"
|
||||
And I turn editing mode on
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "ForumName1" "forum activity" page
|
||||
And I follow "Forum post by admin"
|
||||
And I should not see "Edit H5P content"
|
||||
@@ -0,0 +1,204 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace mod_forum\h5p;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Test class covering the H5P canedit class.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2021 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \mod_forum\h5p\canedit
|
||||
*/
|
||||
class h5p_canedit_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the behaviour of can_edit_content().
|
||||
*
|
||||
* @covers ::can_edit_content
|
||||
* @dataProvider can_edit_content_provider
|
||||
*
|
||||
* @param string $currentuser User who will call the method.
|
||||
* @param string $fileauthor Author of the file to check.
|
||||
* @param string $filecomponent Component of the file to check.
|
||||
* @param bool $expected Expected result after calling the can_edit_content method.
|
||||
* @param string $filearea Area of the file to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_can_edit_content(string $currentuser, string $fileauthor, string $filecomponent, bool $expected,
|
||||
$filearea = 'unittest'): void {
|
||||
global $USER, $DB;
|
||||
|
||||
$this->setRunTestInSeparateProcess(true);
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create some users.
|
||||
$this->setAdminUser();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$users = [
|
||||
'admin' => $USER,
|
||||
'teacher' => $teacher,
|
||||
'student' => $student,
|
||||
];
|
||||
|
||||
// Set current user.
|
||||
if ($currentuser !== 'admin') {
|
||||
$this->setUser($users[$currentuser]);
|
||||
}
|
||||
|
||||
$itemid = rand();
|
||||
if ($filearea === 'post') {
|
||||
// Create a forum and add a discussion.
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $users[$fileauthor]->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
|
||||
$post = $DB->get_record('forum_posts', ['discussion' => $discussion->id]);
|
||||
$itemid = $post->id;
|
||||
}
|
||||
|
||||
// Create the file.
|
||||
$filename = 'greeting-card-887.h5p';
|
||||
$path = __DIR__ . '/../../../h5p/tests/fixtures/' . $filename;
|
||||
if ($filecomponent === 'contentbank') {
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
|
||||
$contents = $generator->generate_contentbank_data(
|
||||
'contenttype_h5p',
|
||||
1,
|
||||
(int)$users[$fileauthor]->id,
|
||||
$context,
|
||||
true,
|
||||
$path
|
||||
);
|
||||
$content = array_shift($contents);
|
||||
$file = $content->get_file();
|
||||
} else {
|
||||
$filerecord = [
|
||||
'contextid' => $context->id,
|
||||
'component' => $filecomponent,
|
||||
'filearea' => $filearea,
|
||||
'itemid' => $itemid,
|
||||
'filepath' => '/',
|
||||
'filename' => basename($path),
|
||||
'userid' => $users[$fileauthor]->id,
|
||||
];
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->create_file_from_pathname($filerecord, $path);
|
||||
}
|
||||
|
||||
// Check if the currentuser can edit the file.
|
||||
$result = \mod_forum\h5p\canedit::can_edit_content($file);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_can_edit_content().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function can_edit_content_provider(): array {
|
||||
return [
|
||||
// Component = mod_forum.
|
||||
'mod_forum: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Admin user, teacher is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Teacher user, admin is author' => [
|
||||
'currentuser' => 'teacher',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
],
|
||||
'mod_forum: Student user, teacher is author' => [
|
||||
'currentuser' => 'student',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => false,
|
||||
],
|
||||
'mod_forum/post: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
'mod_forum/post: Teacher user, admin is author' => [
|
||||
'currentuser' => 'teacher',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => true,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
'mod_forum/post: Student user, teacher is author' => [
|
||||
'currentuser' => 'student',
|
||||
'fileauthor' => 'teacher',
|
||||
'filecomponent' => 'mod_forum',
|
||||
'expected' => false,
|
||||
'filearea' => 'post',
|
||||
],
|
||||
|
||||
// Component <> mod_forum.
|
||||
'mod_page: Admin user is author' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_page',
|
||||
'expected' => false,
|
||||
],
|
||||
|
||||
// Unexisting components.
|
||||
'Unexisting component' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'unexisting_component',
|
||||
'expected' => false,
|
||||
],
|
||||
'Unexisting module activity' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'mod_unexisting',
|
||||
'expected' => false,
|
||||
],
|
||||
'Unexisting block' => [
|
||||
'currentuser' => 'admin',
|
||||
'fileauthor' => 'admin',
|
||||
'filecomponent' => 'block_unexisting',
|
||||
'expected' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user