Merge branch 'MDL-67790-master' of git://github.com/aanabit/moodle
This commit is contained in:
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_contentbank/actions",["jquery","core/ajax","core/notification","core/str","core/templates","core/url","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g,h){var j={DELETE_CONTENT:"[data-action=\"deletecontent\"]"},k=function(){this.registerEvents()};k.prototype.registerEvents=function(){a(j.DELETE_CONTENT).click(function(b){b.preventDefault();var e=a(this).data("contentname"),f=a(this).data("contentid"),j=a(this).data("contextid"),k="";d.get_strings([{key:"deletecontent",component:"core_contentbank"},{key:"deletecontentconfirm",component:"core_contentbank",param:{name:e}},{key:"delete",component:"core"}]).then(function(a){var b=a[0],c=a[1];k=a[2];return g.create({title:b,body:c,type:g.types.SAVE_CANCEL,large:!0})}).done(function(a){a.setSaveButtonText(k);a.getRoot().on(h.save,function(){return i(f,j)});a.getRoot().on(h.hidden,function(){a.destroy()});a.show()}).catch(c.exception)})};function i(a,e){var g="success";b.call([{methodname:"core_contentbank_delete_content",args:{contentids:{contentid:a}}}])[0].then(function(a){if(a.result){return d.get_string("contentdeleted","core_contentbank")}g="error";return d.get_string("contentnotdeleted","core_contentbank")}).done(function(a){var b={contextid:e};if("success"==g){b.statusmsg=a}else{b.errormsg=a}window.location.href=f.relativeUrl("contentbank/index.php",b,!1)}).fail(c.exception)}return{init:function init(){return new k}}});
|
||||
define ("core_contentbank/actions",["jquery","core/ajax","core/notification","core/str","core/templates","core/url","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g,h){var k={DELETE_CONTENT:"[data-action=\"deletecontent\"]",RENAME_CONTENT:"[data-action=\"renamecontent\"]"},l=function(){this.registerEvents()};l.prototype.registerEvents=function(){a(k.DELETE_CONTENT).click(function(b){b.preventDefault();var e=a(this).data("contentname"),f=a(this).data("contentid"),j=a(this).data("contextid"),k="";d.get_strings([{key:"deletecontent",component:"core_contentbank"},{key:"deletecontentconfirm",component:"core_contentbank",param:{name:e}},{key:"delete",component:"core"}]).then(function(a){var b=a[0],c=a[1];k=a[2];return g.create({title:b,body:c,type:g.types.SAVE_CANCEL,large:!0})}).done(function(a){a.setSaveButtonText(k);a.getRoot().on(h.save,function(){return i(f,j)});a.getRoot().on(h.hidden,function(){a.destroy()});a.show()}).catch(c.exception)});a(k.RENAME_CONTENT).click(function(b){b.preventDefault();var f=a(this).data("contentname"),i=a(this).data("contentid"),k="";d.get_strings([{key:"renamecontent",component:"core_contentbank"},{key:"rename",component:"core_contentbank"}]).then(function(a){var b=a[0];k=a[1];return g.create({title:b,body:e.render("core_contentbank/renamecontent",{contentid:i,name:f}),type:g.types.SAVE_CANCEL})}).then(function(b){b.setSaveButtonText(k);b.getRoot().on(h.save,function(){var b=a("#newname").val();return j(i,b)});b.getRoot().on(h.hidden,function(){b.destroy()});b.show()}).catch(c.exception)})};function i(a,e){var g="success";b.call([{methodname:"core_contentbank_delete_content",args:{contentids:{contentid:a}}}])[0].then(function(a){if(a.result){return d.get_string("contentdeleted","core_contentbank")}g="error";return d.get_string("contentnotdeleted","core_contentbank")}).done(function(a){var b={contextid:e};if("success"==g){b.statusmsg=a}else{b.errormsg=a}window.location.href=f.relativeUrl("contentbank/index.php",b,!1)}).fail(c.exception)}function j(a,e){var g="success";b.call([{methodname:"core_contentbank_rename_content",args:{contentid:a,name:e}}])[0].then(function(a){if(a){return d.get_string("contentrenamed","core_contentbank")}g="error";return d.get_string("contentnotrenamed","core_contentbank")}).then(function(b){var d=null;if("success"==g){d={id:a,statusmsg:b};window.location.href=f.relativeUrl("contentbank/view.php",d,!1)}else{c.addNotification({message:b,type:"error"});c.fetchNotifications()}}).catch(c.exception)}return{init:function init(){return new l}}});
|
||||
//# sourceMappingURL=actions.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -39,6 +39,7 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
|
||||
*/
|
||||
var ACTIONS = {
|
||||
DELETE_CONTENT: '[data-action="deletecontent"]',
|
||||
RENAME_CONTENT: '[data-action="renamecontent"]',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,6 +109,54 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.RENAME_CONTENT).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var contentname = $(this).data('contentname');
|
||||
var contentid = $(this).data('contentid');
|
||||
|
||||
var strings = [
|
||||
{
|
||||
key: 'renamecontent',
|
||||
component: 'core_contentbank'
|
||||
},
|
||||
{
|
||||
key: 'rename',
|
||||
component: 'core_contentbank'
|
||||
},
|
||||
];
|
||||
|
||||
var saveButtonText = '';
|
||||
Str.get_strings(strings).then(function(langStrings) {
|
||||
var modalTitle = langStrings[0];
|
||||
saveButtonText = langStrings[1];
|
||||
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: Templates.render('core_contentbank/renamecontent', {'contentid': contentid, 'name': contentname}),
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(saveButtonText);
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
// The action is now confirmed, sending an action for it.
|
||||
var newname = $("#newname").val();
|
||||
return renameContent(contentid, newname);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Show the modal.
|
||||
modal.show();
|
||||
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -146,6 +195,49 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
|
||||
}).fail(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename content in the content bank.
|
||||
*
|
||||
* @param {int} contentid The content to rename.
|
||||
* @param {string} name The new name for the content.
|
||||
*/
|
||||
function renameContent(contentid, name) {
|
||||
var request = {
|
||||
methodname: 'core_contentbank_rename_content',
|
||||
args: {
|
||||
contentid: contentid,
|
||||
name: name
|
||||
}
|
||||
};
|
||||
var requestType = 'success';
|
||||
Ajax.call([request])[0].then(function(data) {
|
||||
if (data) {
|
||||
return Str.get_string('contentrenamed', 'core_contentbank');
|
||||
}
|
||||
requestType = 'error';
|
||||
return Str.get_string('contentnotrenamed', 'core_contentbank');
|
||||
|
||||
}).then(function(message) {
|
||||
var params = null;
|
||||
if (requestType == 'success') {
|
||||
params = {
|
||||
id: contentid,
|
||||
statusmsg: message
|
||||
};
|
||||
// Redirect to the content view page and display the message as a notification.
|
||||
window.location.href = Url.relativeUrl('contentbank/view.php', params, false);
|
||||
} else {
|
||||
// Fetch error notifications.
|
||||
Notification.addNotification({
|
||||
message: message,
|
||||
type: 'error'
|
||||
});
|
||||
Notification.fetchNotifications();
|
||||
}
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
return /** @alias module:core_contentbank/actions */ {
|
||||
// Public variables and functions.
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace core_contentbank;
|
||||
|
||||
use core_text;
|
||||
use stored_file;
|
||||
use stdClass;
|
||||
use coding_exception;
|
||||
@@ -102,6 +103,33 @@ abstract class content {
|
||||
return $DB->update_record('contentbank_content', $this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new name to the content.
|
||||
*
|
||||
* @param string $name The name of the content.
|
||||
* @return bool True if the content has been succesfully updated. False otherwise.
|
||||
* @throws \coding_exception if not loaded.
|
||||
*/
|
||||
public function set_name(string $name): bool {
|
||||
if (empty($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean name.
|
||||
$name = clean_param($name, PARAM_TEXT);
|
||||
if (core_text::strlen($name) > 255) {
|
||||
$name = core_text::substr($name, 0, 255);
|
||||
}
|
||||
|
||||
$oldname = $this->content->name;
|
||||
$this->content->name = $name;
|
||||
$updated = $this->update_content();
|
||||
if (!$updated) {
|
||||
$this->content->name = $oldname;
|
||||
}
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the content.
|
||||
*
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
namespace core_contentbank;
|
||||
|
||||
use coding_exception;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,7 @@ abstract class contenttype {
|
||||
/** Plugin implements uploading feature */
|
||||
const CAN_UPLOAD = 'upload';
|
||||
|
||||
/** @var context This content's context. **/
|
||||
/** @var context This contenttype's context. **/
|
||||
protected $context = null;
|
||||
|
||||
/**
|
||||
@@ -99,6 +98,18 @@ abstract class contenttype {
|
||||
return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename this content from the content_bank.
|
||||
* This method can be overwritten by the plugins if they need to change some other specific information.
|
||||
*
|
||||
* @param content $content The content to rename.
|
||||
* @param string $name The name of the content.
|
||||
* @return boolean true if the content has been renamed; false otherwise.
|
||||
*/
|
||||
public function rename_content(content $content, string $name): bool {
|
||||
return $content->set_name($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contenttype name of this content.
|
||||
*
|
||||
@@ -240,6 +251,41 @@ abstract class contenttype {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can managed this content.
|
||||
*
|
||||
* @param content $content The content to be managed.
|
||||
* @return bool True if content could be managed. False otherwise.
|
||||
*/
|
||||
public final function can_manage(content $content): bool {
|
||||
global $USER;
|
||||
|
||||
if ($this->context->id != $content->get_content()->contextid) {
|
||||
// The content has to have exactly the same context as this contenttype.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check main contentbank management permission.
|
||||
$hascapability = has_capability('moodle/contentbank:manageanycontent', $this->context);
|
||||
if ($content->get_content()->usercreated == $USER->id) {
|
||||
// This content has been created by the current user; check if they can manage their content.
|
||||
$hascapability = $hascapability || has_capability('moodle/contentbank:manageowncontent', $this->context);
|
||||
}
|
||||
|
||||
return $hascapability && $this->is_manage_allowed($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if content allows managing.
|
||||
*
|
||||
* @param content $content The content to be managed.
|
||||
* @return bool True if content allows uploading. False otherwise.
|
||||
*/
|
||||
protected function is_manage_allowed(content $content): bool {
|
||||
// Plugins can overwrite this function to add any check they need.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin supports the feature.
|
||||
*
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* External API to rename content bank content.
|
||||
*
|
||||
* @package core_contentbank
|
||||
* @copyright 2020 Amaia Anabitarte <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_contentbank\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
use external_warnings;
|
||||
|
||||
/**
|
||||
* This is the external method for renaming a content.
|
||||
*
|
||||
* @copyright 2020 Amaia Anabitarte <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class rename_content extends external_api {
|
||||
/**
|
||||
* rename_content parameters.
|
||||
*
|
||||
* @since Moodle 3.9
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'contentid' => new external_value(PARAM_INT, 'The content id to rename', VALUE_REQUIRED),
|
||||
'name' => new external_value(PARAM_RAW, 'The new name for the content', VALUE_REQUIRED),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename content from the contentbank.
|
||||
*
|
||||
* @since Moodle 3.9
|
||||
* @param int $contentid The content id to rename.
|
||||
* @param string $name The new name.
|
||||
* @return array True if the content has been renamed; false and the warning, otherwise.
|
||||
*/
|
||||
public static function execute(int $contentid, string $name): array {
|
||||
global $DB;
|
||||
|
||||
$result = false;
|
||||
$warnings = [];
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'contentid' => $contentid,
|
||||
'name' => $name,
|
||||
]);
|
||||
$params['name'] = clean_param($params['name'], PARAM_TEXT);
|
||||
try {
|
||||
$record = $DB->get_record('contentbank_content', ['id' => $contentid], '*', MUST_EXIST);
|
||||
$contenttypeclass = "\\$record->contenttype\\contenttype";
|
||||
if (class_exists($contenttypeclass)) {
|
||||
$context = \context::instance_by_id($record->contextid, MUST_EXIST);
|
||||
self::validate_context($context);
|
||||
$contenttype = new $contenttypeclass($context);
|
||||
$contentclass = "\\$record->contenttype\\content";
|
||||
$content = new $contentclass($record);
|
||||
// Check capability.
|
||||
if ($contenttype->can_manage($content)) {
|
||||
// This content can be renamed.
|
||||
if ($contenttype->rename_content($content, $params['name'])) {
|
||||
$result = true;
|
||||
} else {
|
||||
$warnings[] = [
|
||||
'item' => $contentid,
|
||||
'warningcode' => 'contentnotrenamed',
|
||||
'message' => get_string('contentnotrenamed', 'core_contentbank')
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// The user has no permission to manage this content.
|
||||
$warnings[] = [
|
||||
'item' => $contentid,
|
||||
'warningcode' => 'nopermissiontomanage',
|
||||
'message' => get_string('nopermissiontomanage', 'core_contentbank')
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\moodle_exception $e) {
|
||||
// The content or the context don't exist.
|
||||
$warnings[] = [
|
||||
'item' => $contentid,
|
||||
'warningcode' => 'exception',
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => $result,
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* rename_content return.
|
||||
*
|
||||
* @since Moodle 3.9
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'result' => new external_value(PARAM_BOOL, 'The processing result'),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
@core @core_contentbank @contenttype_h5p @_file_upload @javascript
|
||||
Feature: Manage H5P content from the content bank
|
||||
In order to manage H5P content in the content bank
|
||||
As an admin
|
||||
I need to be able to edit any H5P content in the content bank
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@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 |
|
||||
And the following "contentbank content" exist:
|
||||
| course| contenttype | user | contentname |
|
||||
| C1 | contenttype_h5p | admin | filltheblanks.h5p |
|
||||
| C1 | contenttype_h5p | teacher1 | ipsums.h5p |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add the "Navigation" block if not present
|
||||
And I log out
|
||||
|
||||
Scenario: Teachers can rename their own content in the content bank
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I expand "Site pages" node
|
||||
And I click on "Content bank" "link"
|
||||
And I follow "ipsums.h5p"
|
||||
When I open the action menu in "region-main-settings-menu" "region"
|
||||
And I should see "Rename"
|
||||
And I choose "Rename" in the open action menu
|
||||
And I set the field "Content name" to "New name"
|
||||
And I click on "Rename" "button"
|
||||
And I wait until the page is ready
|
||||
Then I should not see "ipsums.h5p"
|
||||
And I should see "New name"
|
||||
|
||||
Scenario: Teachers can't rename content created by other users in the content bank
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I expand "Site pages" node
|
||||
And I click on "Content bank" "link"
|
||||
When I follow "filltheblanks.h5p"
|
||||
Then "region-main-settings-menu" "region" should not exist
|
||||
@@ -51,14 +51,14 @@ class contenttype_h5p_content_plugin_testcase extends advanced_testcase {
|
||||
|
||||
// Create a dummy file.
|
||||
$filename = 'content.h5p';
|
||||
$dummy = array(
|
||||
$dummy = [
|
||||
'contextid' => \context_system::instance()->id,
|
||||
'component' => 'contentbank',
|
||||
'filearea' => 'public',
|
||||
'itemid' => $content->get_id(),
|
||||
'filepath' => '/',
|
||||
'filename' => $filename
|
||||
);
|
||||
];
|
||||
$fs = get_file_storage();
|
||||
$fs->create_file_from_string($dummy, 'dummy content');
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_contentbank/renamecontent
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"contentid": 17,
|
||||
"name": "Accordion"
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="form-check w-100 justify-content-start">
|
||||
<label for="newname">{{#str}}contentname, core_contentbank{{/str}}</label>
|
||||
<input type="text" size="5" id="newname" name="newname" value="{{{ name }}}" class="form-control text-ltr">
|
||||
</div>
|
||||
@@ -64,6 +64,55 @@ class core_contenttype_content_testcase extends \advanced_testcase {
|
||||
$this->assertEquals($record->name, $content->get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_set_name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function set_name_provider() {
|
||||
return [
|
||||
'Standard name' => ['New name', 'New name'],
|
||||
'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'],
|
||||
'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'],
|
||||
'Name with tags' => ['This is <b>bold</b>', 'This is bold'],
|
||||
'Long name' => [str_repeat('a', 100), str_repeat('a', 100)],
|
||||
'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for 'set_name' behaviour.
|
||||
*
|
||||
* @dataProvider set_name_provider
|
||||
* @param string $newname The name to set
|
||||
* @param string $expected The name result
|
||||
*
|
||||
* @covers ::set_name
|
||||
*/
|
||||
public function test_set_name(string $newname, string $expected) {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$oldname = "Old name";
|
||||
$context = context_system::instance();
|
||||
|
||||
// Create content.
|
||||
$record = new stdClass();
|
||||
$record->name = $oldname;
|
||||
|
||||
$contenttype = new contenttype($context);
|
||||
$content = $contenttype->create_content($record);
|
||||
$this->assertEquals($oldname, $content->get_name());
|
||||
|
||||
$content->set_name($newname);
|
||||
$this->assertEquals($expected, $content->get_name());
|
||||
|
||||
$record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
|
||||
$this->assertEquals($expected, $record->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for behaviour of get_content_type().
|
||||
*
|
||||
|
||||
@@ -176,7 +176,6 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase {
|
||||
$this->assertInstanceOf('\\contenttype_testable\\content', $content);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the behaviour of can_delete().
|
||||
*/
|
||||
@@ -254,4 +253,105 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase {
|
||||
|
||||
$this->contenttype = new \contenttype_testable\contenttype($systemcontext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_rename_content.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rename_content_provider() {
|
||||
return [
|
||||
'Standard name' => ['New name', 'New name'],
|
||||
'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'],
|
||||
'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'],
|
||||
'Name with tags' => ['This is <b>bold</b>', 'This is bold'],
|
||||
'Long name' => [str_repeat('a', 100), str_repeat('a', 100)],
|
||||
'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of rename_content().
|
||||
*
|
||||
* @dataProvider rename_content_provider
|
||||
* @param string $newname The name to set
|
||||
* @param string $expected The name result
|
||||
*
|
||||
* @covers ::rename_content
|
||||
*/
|
||||
public function test_rename_content(string $newname, string $expected) {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create course and teacher user.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$contenttype = new contenttype($coursecontext);
|
||||
|
||||
// Add some content to the content bank as teacher.
|
||||
$this->setUser($teacher);
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
|
||||
$contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id);
|
||||
$content = array_shift($contents);
|
||||
|
||||
$oldname = $content->get_name();
|
||||
|
||||
// Check the content is renamed as expected by a user with permission.
|
||||
$renamed = $contenttype->rename_content($content, $newname);
|
||||
$this->assertTrue($renamed);
|
||||
$record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
|
||||
$this->assertNotEquals($oldname, $record->name);
|
||||
$this->assertEquals($expected, $record->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of can_manage().
|
||||
*
|
||||
* @covers ::can_manage
|
||||
*/
|
||||
public function test_can_manage() {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
|
||||
|
||||
// Create course and teacher user.
|
||||
$teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
$contenttype = new contenttype($coursecontext);
|
||||
|
||||
// Add some content to the content bank as admin.
|
||||
$this->setAdminUser();
|
||||
$contentsbyadmin = $generator->generate_contentbank_data('contenttype_testable', 1, $USER->id, $coursecontext);
|
||||
$contentbyadmin = array_shift($contentsbyadmin);
|
||||
|
||||
// Add some content to the content bank as teacher.
|
||||
$contentsbyteacher = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id, $coursecontext);
|
||||
$contentbyteacher = array_shift($contentsbyteacher);
|
||||
|
||||
// Check the content has been created as expected.
|
||||
$records = $DB->count_records('contentbank_content');
|
||||
$this->assertEquals(2, $records);
|
||||
|
||||
// Check manager can manage by default all the contents created.
|
||||
$this->setUser($manager);
|
||||
$this->assertTrue($contenttype->can_manage($contentbyteacher));
|
||||
$this->assertTrue($contenttype->can_manage($contentbyadmin));
|
||||
|
||||
// Check teacher can only edit their own content.
|
||||
$this->setUser($teacher);
|
||||
$this->assertTrue($contenttype->can_manage($contentbyteacher));
|
||||
$this->assertFalse($contenttype->can_manage($contentbyadmin));
|
||||
|
||||
// Unassign capability to teacher role and check they not can not edit any content.
|
||||
unassign_capability('moodle/contentbank:manageowncontent', $teacherroleid);
|
||||
$this->assertFalse($contenttype->can_manage($contentbyteacher));
|
||||
$this->assertFalse($contenttype->can_manage($contentbyadmin));
|
||||
}
|
||||
}
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Core content bank external functions tests.
|
||||
*
|
||||
* @package core_contentbank
|
||||
* @category external
|
||||
* @copyright 2020 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 3.9
|
||||
*/
|
||||
|
||||
namespace core_contentbank;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
|
||||
require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
use core_contentbank\external\delete_content;
|
||||
use core_contentbank\external\external;
|
||||
use core_contentbank\external\rename_content;
|
||||
use external_api;
|
||||
|
||||
/**
|
||||
* Core content bank external functions tests.
|
||||
*
|
||||
* @package core_contentbank
|
||||
* @copyright 2020 Sara Arjona <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_contentbank\external
|
||||
*/
|
||||
class rename_content_testcase extends \externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for test_rename_content.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rename_content_provider() {
|
||||
return [
|
||||
'Standard name' => ['New name', 'New name'],
|
||||
'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017'],
|
||||
'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle'],
|
||||
'Name with tags' => ['This is <b>bold</b>', 'This is bold'],
|
||||
'Long name' => [str_repeat('a', 100), str_repeat('a', 100)],
|
||||
'Too long name' => [str_repeat('a', 300), str_repeat('a', 255)]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of rename_content() for users with permission.
|
||||
*
|
||||
* @dataProvider rename_content_provider
|
||||
* @param string $newname The name to set
|
||||
* @param string $expected The name result
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_rename_content_with_permission(string $newname, string $expected) {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create users.
|
||||
$roleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
|
||||
$this->getDataGenerator()->role_assign($roleid, $teacher->id);
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Add some content to the content bank as teacher.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
|
||||
$contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id);
|
||||
$content = array_shift($contents);
|
||||
|
||||
$oldname = $content->get_name();
|
||||
|
||||
// Call the WS and check the content is renamed as expected.
|
||||
$result = rename_content::execute($content->get_id(), $newname);
|
||||
$result = external_api::clean_returnvalue(rename_content::execute_returns(), $result);
|
||||
$this->assertTrue($result['result']);
|
||||
$record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
|
||||
$this->assertNotEquals($oldname, $record->name);
|
||||
$this->assertEquals($expected, $record->name);
|
||||
|
||||
// Call the WS using an unexisting contentid and check an error is thrown.
|
||||
$this->expectException(\invalid_response_exception::class);
|
||||
$result = rename_content::execute_returns($content->get_id() + 1, $oldname);
|
||||
$result = external_api::clean_returnvalue(rename_content::execute_returns(), $result);
|
||||
$this->assertFalse($result['result']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of rename_content() for users with permission.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_rename_content_without_permission() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create users.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
// Add some content to the content bank as teacher.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
|
||||
$contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id);
|
||||
$content = array_shift($contents);
|
||||
|
||||
$oldname = $content->get_name();
|
||||
$newname = 'New name';
|
||||
|
||||
// Call the WS and check the content has not been renamed by the student.
|
||||
$this->setUser($student);
|
||||
$result = rename_content::execute($content->get_id(), $newname);
|
||||
$result = external_api::clean_returnvalue(rename_content::execute_returns(), $result);
|
||||
$this->assertFalse($result['result']);
|
||||
$record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
|
||||
$this->assertEquals($oldname, $record->name);
|
||||
$this->assertNotEquals($newname, $record->name);
|
||||
}
|
||||
}
|
||||
+40
-13
@@ -35,7 +35,10 @@ $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST)
|
||||
$context = context::instance_by_id($record->contextid, MUST_EXIST);
|
||||
require_capability('moodle/contentbank:access', $context);
|
||||
|
||||
$returnurl = new \moodle_url('/contentbank/index.php');
|
||||
$statusmsg = optional_param('statusmsg', '', PARAM_RAW);
|
||||
$errormsg = optional_param('errormsg', '', PARAM_RAW);
|
||||
|
||||
$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
|
||||
$plugin = core_plugin_manager::instance()->get_plugin_info($record->contenttype);
|
||||
if (!$plugin || !$plugin->is_enabled()) {
|
||||
print_error('unsupported', 'core_contentbank', $returnurl);
|
||||
@@ -46,7 +49,6 @@ $title = get_string('contentbank');
|
||||
if ($PAGE->course) {
|
||||
require_login($PAGE->course->id);
|
||||
}
|
||||
$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
|
||||
|
||||
$PAGE->set_url(new \moodle_url('/contentbank/view.php', ['id' => $id]));
|
||||
$PAGE->set_context($context);
|
||||
@@ -57,13 +59,32 @@ $PAGE->set_title($title);
|
||||
$PAGE->set_pagetype('contenbank');
|
||||
|
||||
$contenttypeclass = "\\$record->contenttype\\contenttype";
|
||||
$contenttype = new $contenttypeclass($context);
|
||||
$contentclass = "\\$record->contenttype\\content";
|
||||
if (!class_exists($contenttypeclass) || !class_exists($contentclass)) {
|
||||
print_error('contenttypenotfound', 'error', $returnurl, $record->contenttype);
|
||||
}
|
||||
$contenttype = new $contenttypeclass($context);
|
||||
$content = new $contentclass($record);
|
||||
|
||||
// Create the cog menu with all the secondary actions, such as delete, rename...
|
||||
$actionmenu = new action_menu();
|
||||
$actionmenu->set_alignment(action_menu::TR, action_menu::BR);
|
||||
if ($contenttype->can_manage($content)) {
|
||||
// Add the rename content item to the menu.
|
||||
$attributes = [
|
||||
'data-action' => 'renamecontent',
|
||||
'data-contentname' => $content->get_name(),
|
||||
'data-contentid' => $content->get_id(),
|
||||
];
|
||||
$actionmenu->add_secondary_action(new action_menu_link(
|
||||
new moodle_url('#'),
|
||||
new pix_icon('e/styleparagraph', get_string('rename')),
|
||||
get_string('rename'),
|
||||
false,
|
||||
$attributes
|
||||
));
|
||||
}
|
||||
if ($contenttype->can_delete($content)) {
|
||||
// Create the cog menu with all the secondary actions, such as delete, rename...
|
||||
$actionmenu = new action_menu();
|
||||
$actionmenu->set_alignment(action_menu::TR, action_menu::BR);
|
||||
// Add the delete content item to the menu.
|
||||
$attributes = [
|
||||
'data-action' => 'deletecontent',
|
||||
@@ -78,18 +99,24 @@ if ($contenttype->can_delete($content)) {
|
||||
false,
|
||||
$attributes
|
||||
));
|
||||
|
||||
// Add the cog menu to the header.
|
||||
$PAGE->add_header_action(html_writer::div(
|
||||
$OUTPUT->render($actionmenu),
|
||||
'd-print-none',
|
||||
['id' => 'region-main-settings-menu']
|
||||
));
|
||||
}
|
||||
|
||||
// Add the cog menu to the header.
|
||||
$PAGE->add_header_action(html_writer::div(
|
||||
$OUTPUT->render($actionmenu),
|
||||
'd-print-none',
|
||||
['id' => 'region-main-settings-menu']
|
||||
));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->box_start('generalbox');
|
||||
|
||||
// If needed, display notifications.
|
||||
if ($errormsg !== '') {
|
||||
echo $OUTPUT->notification($errormsg);
|
||||
} else if ($statusmsg !== '') {
|
||||
echo $OUTPUT->notification($statusmsg, 'notifysuccess');
|
||||
}
|
||||
if ($contenttype->can_access()) {
|
||||
echo $contenttype->get_view_content($record);
|
||||
}
|
||||
|
||||
@@ -24,23 +24,27 @@
|
||||
|
||||
$string['author'] = 'Author';
|
||||
$string['contentdeleted'] = 'The content has been deleted.';
|
||||
$string['contentname'] = 'Content name';
|
||||
$string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.';
|
||||
$string['contentnotrenamed'] = 'An error was encountered while trying to rename the content.';
|
||||
$string['contentrenamed'] = 'The content has been renamed.';
|
||||
$string['deletecontent'] = 'Delete content';
|
||||
$string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
|
||||
$string['file'] = 'Upload content';
|
||||
$string['file_help'] = 'Files may be stored in the content bank for use in courses. Only files used by content types enabled on the site may be uploaded.';
|
||||
$string['name'] = 'Content';
|
||||
$string['nopermissiontodelete'] = 'You do not have permission to delete content.';
|
||||
$string['nopermissiontomanage'] = 'You do not have permission to manage content.';
|
||||
$string['privacy:metadata:content:contenttype'] = 'The contenttype plugin of the content in the content bank.';
|
||||
$string['privacy:metadata:content:name'] = 'Name of the content in the content bank.';
|
||||
$string['privacy:metadata:content:timecreated'] = 'The time when the content was created.';
|
||||
$string['privacy:metadata:content:timemodified'] = 'The time when the content was modified.';
|
||||
$string['privacy:metadata:content:usercreated'] = 'The user has created the content.';
|
||||
$string['privacy:metadata:content:usercreated'] = 'The user who created the content.';
|
||||
$string['privacy:metadata:content:usermodified'] = 'Last user has modified the content.';
|
||||
$string['privacy:metadata:content:usermodified'] = 'The last user who modified the content.';
|
||||
$string['privacy:metadata:contentbankcontent'] = 'Stores the content of the content bank.';
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user creating or modifying content bank content.';
|
||||
$string['rename'] = 'Rename';
|
||||
$string['renamecontent'] = 'Rename content';
|
||||
$string['timecreated'] = 'Time created';
|
||||
$string['unsupported'] = 'This content type is not supported.';
|
||||
$string['upload'] = 'Upload';
|
||||
|
||||
+3
-1
@@ -153,7 +153,9 @@ $string['confirmunassignno'] = 'Cancel';
|
||||
$string['contentbank:access'] = 'Access the content bank';
|
||||
$string['contentbank:deleteanycontent'] = 'Delete any content from the content bank';
|
||||
$string['contentbank:deleteowncontent'] = 'Delete content from own content bank';
|
||||
$string['contentbank:upload'] = 'Upload content to the content bank';
|
||||
$string['contentbank:manageanycontent'] = 'Manage any content from the content bank (rename, move, publish, share, etc.)';
|
||||
$string['contentbank:manageowncontent'] = 'Manage content from own content bank (rename, move, publish, share, etc.)';
|
||||
$string['contentbank:upload'] = 'Upload new content in the content bank';
|
||||
$string['context'] = 'Context';
|
||||
$string['course:activityvisibility'] = 'Hide/show activities';
|
||||
$string['course:bulkmessaging'] = 'Send a message to many people';
|
||||
|
||||
@@ -217,6 +217,11 @@ class behat_core_generator extends behat_generator_base {
|
||||
'required' => array('user', 'preference', 'value'),
|
||||
'switchids' => array('user' => 'userid')
|
||||
],
|
||||
'contentbank content' => [
|
||||
'datagenerator' => 'contentbank_content',
|
||||
'required' => array('course', 'contenttype', 'user', 'contentname'),
|
||||
'switchids' => array('course' => 'courseid', 'user' => 'userid')
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -816,4 +821,22 @@ class behat_core_generator extends behat_generator_base {
|
||||
protected function process_user_preferences(array $data) {
|
||||
set_user_preference($data['preference'], $data['value'], $data['userid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content in the given context's content bank
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_contentbank_content(array $data) {
|
||||
$context = context_course::instance($data['courseid']);
|
||||
$contenttypeclass = "\\".$data['contenttype']."\\contenttype";
|
||||
if (class_exists($contenttypeclass)) {
|
||||
$contenttype = new $contenttypeclass($context);
|
||||
$record = new stdClass();
|
||||
$record->usercreated = $data['userid'];
|
||||
$record->name = $data['contentname'];
|
||||
$content = $contenttype->create_content($record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2522,4 +2522,26 @@ $capabilities = array(
|
||||
'user' => CAP_ALLOW,
|
||||
]
|
||||
],
|
||||
|
||||
// Manage (rename, move, publish, share, etc.) any content from the content bank.
|
||||
'moodle/contentbank:manageanycontent' => [
|
||||
'riskbitmask' => RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => array(
|
||||
'manager' => CAP_ALLOW,
|
||||
'coursecreator' => CAP_ALLOW,
|
||||
)
|
||||
],
|
||||
|
||||
// Manage (rename, move, publish, share, etc.) content created by yourself.
|
||||
'moodle/contentbank:manageowncontent' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => array(
|
||||
'manager' => CAP_ALLOW,
|
||||
'coursecreator' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2773,6 +2773,15 @@ $functions = array(
|
||||
'ajax' => 'true',
|
||||
'capabilities' => 'moodle/contentbank:deleteanycontent',
|
||||
],
|
||||
'core_contentbank_rename_content' => [
|
||||
'classname' => 'core_contentbank\external\rename_content',
|
||||
'methodname' => 'execute',
|
||||
'classpath' => '',
|
||||
'description' => 'Rename a content in the content bank.',
|
||||
'type' => 'write',
|
||||
'ajax' => 'true',
|
||||
'capabilities' => 'moodle/contentbank:manageowncontent',
|
||||
],
|
||||
);
|
||||
|
||||
$services = array(
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2020050200.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2020050200.02; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '3.9dev+ (Build: 20200502)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user