Merge branch 'MDL-67810-master' of git://github.com/vmdef/moodle

This commit is contained in:
Jun Pataleta 2020-05-28 12:19:25 +08:00
commit 0a1b55ce36
28 changed files with 1150 additions and 23 deletions

View File

@ -24,6 +24,7 @@
namespace core_contentbank;
use core_plugin_manager;
use stored_file;
use context;
@ -35,6 +36,8 @@ use context;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contentbank {
/** @var array Enabled content types. */
private $enabledcontenttypes = null;
/**
* Obtains the list of core_contentbank_content objects currently active.
@ -44,16 +47,20 @@ class contentbank {
* @return string[] Array of contentbank contenttypes.
*/
public function get_enabled_content_types(): array {
if (!is_null($this->enabledcontenttypes)) {
return $this->enabledcontenttypes;
}
$enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
$types = [];
foreach ($enabledtypes as $name) {
$contenttypeclassname = "\\contenttype_$name\\contenttype";
$contentclassname = "\\contenttype_$name\\content";
if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
$types[] = $name;
$types[$contenttypeclassname] = $name;
}
}
return $types;
return $this->enabledcontenttypes = $types;
}
/**
@ -292,4 +299,37 @@ class contentbank {
}
return $result;
}
/**
* Get the list of content types that have the requested feature.
*
* @param string $feature Feature code e.g CAN_UPLOAD.
* @param null|\context $context Optional context to check the permission to use the feature.
* @param bool $enabled Whether check only the enabled content types or all of them.
*
* @return string[] List of content types where the user has permission to access the feature.
*/
public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array {
$contenttypes = [];
// Check enabled content types or all of them.
if ($enabled) {
$contenttypestocheck = $this->get_enabled_content_types();
} else {
$plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
foreach ($plugins as $plugin) {
$contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype";
$contenttypestocheck[$contenttypeclassname] = $plugin->name;
}
}
foreach ($contenttypestocheck as $classname => $name) {
$contenttype = new $classname($context);
// The method names that check the features permissions must follow the pattern can_feature.
if ($contenttype->{"can_$feature"}()) {
$contenttypes[$classname] = $name;
}
}
return $contenttypes;
}
}

View File

@ -41,7 +41,10 @@ abstract class contenttype {
/** Plugin implements uploading feature */
const CAN_UPLOAD = 'upload';
/** @var context This contenttype's context. **/
/** Plugin implements edition feature */
const CAN_EDIT = 'edit';
/** @var \context This contenttype's context. **/
protected $context = null;
/**
@ -59,7 +62,7 @@ abstract class contenttype {
/**
* Fills content_bank table with appropiate information.
*
* @param stdClass $record An optional content record compatible object (default null)
* @param \stdClass $record An optional content record compatible object (default null)
* @return content Object with content bank information.
*/
public function create_content(\stdClass $record = null): ?content {
@ -127,7 +130,7 @@ abstract class contenttype {
* 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.
* @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 {
@ -139,7 +142,7 @@ abstract class contenttype {
* 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 context $context The new context.
* @param \context $context The new context.
* @return boolean true if the content has been renamed; false otherwise.
*/
public function move_content(content $content, \context $context): bool {
@ -325,6 +328,37 @@ abstract class contenttype {
return true;
}
/**
* Returns whether or not the user has permission to use the editor.
*
* @return bool True if the user can edit content. False otherwise.
*/
final public function can_edit(): bool {
if (!$this->is_feature_supported(self::CAN_EDIT)) {
return false;
}
if (!$this->can_access()) {
return false;
}
$classname = 'contenttype/'.$this->get_plugin_name();
$editioncap = $classname.':useeditor';
$hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
return $hascapabilities && $this->is_edit_allowed();
}
/**
* Returns plugin allows edition.
*
* @return bool True if plugin allows edition. False otherwise.
*/
protected function is_edit_allowed(): bool {
// Plugins can overwrite this function to add any check they need.
return true;
}
/**
* Returns the plugin supports the feature.
*
@ -348,4 +382,17 @@ abstract class contenttype {
* @return array
*/
abstract public function get_manageable_extensions(): array;
/**
* Returns the list of different types of the given content type.
*
* A content type can have one or more options for creating content. This method will report all of them or only the content
* type itself if it has no other options.
*
* @return array An object for each type:
* - string typename: descriptive name of the type.
* - string typeeditorparams: params required by this content type editor.
* - url typeicon: this type icon.
*/
abstract public function get_contenttype_types(): array;
}

View File

@ -0,0 +1,90 @@
<?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/>.
/**
* Provides {@see \core_contentbank\form\edit_content} class.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\form;
use moodleform;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Defines the form for editing a content.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class edit_content extends moodleform {
/** @var int Context the content belongs to. */
protected $contextid;
/** @var string Content type plugin name. */
protected $plugin;
/** @var int Content id in the content bank. */
protected $id;
/**
* Constructor.
*
* @param string $action The action attribute for the form.
* @param array $customdata Data to set during instance creation.
* @param string $method Form method.
*/
public function __construct(string $action = null, array $customdata = null, string $method = 'post') {
parent::__construct($action, $customdata, $method);
$this->contextid = $customdata['contextid'];
$this->plugin = $customdata['plugin'];
$this->id = $customdata['id'];
$mform =& $this->_form;
$mform->addElement('hidden', 'contextid', $this->contextid);
$this->_form->setType('contextid', PARAM_INT);
$mform->addElement('hidden', 'plugin', $this->plugin);
$this->_form->setType('plugin', PARAM_PLUGIN);
$mform->addElement('hidden', 'id', $this->id);
$this->_form->setType('id', PARAM_INT);
}
/**
* Overrides formslib's add_action_buttons() method.
*
*
* @param bool $cancel
* @param string|null $submitlabel
*
* @return void
*/
public function add_action_buttons($cancel = true, $submitlabel = null): void {
if (is_null($submitlabel)) {
$submitlabel = get_string('save');
}
parent::add_action_buttons($cancel, $submitlabel);
}
}

View File

@ -98,7 +98,56 @@ class bankcontent implements renderable, templatable {
);
}
$data->contents = $contentdata;
$data->tools = $this->toolbar;
// The tools are displayed in the action bar on the index page.
foreach ($this->toolbar as $tool) {
// Customize the output of a tool, like dropdowns.
$method = 'export_tool_'.$tool['name'];
if (method_exists($this, $method)) {
$this->$method($tool);
}
$data->tools[] = $tool;
}
return $data;
}
/**
* Adds the content type items to display to the Add dropdown.
*
* Each content type is represented as an object with the properties:
* - name: the name of the content type.
* - baseurl: the base content type editor URL.
* - types: different types of the content type to display as dropdown items.
*
* @param array $tool Data for rendering the Add dropdown, including the editable content types.
*/
private function export_tool_add(array &$tool) {
$editabletypes = $tool['contenttypes'];
$addoptions = [];
foreach ($editabletypes as $class => $type) {
$contentype = new $class($this->context);
// Get the creation options of each content type.
$types = $contentype->get_contenttype_types();
if ($types) {
// Add a text describing the content type as first option. This will be displayed in the drop down to
// separate the options for the different content types.
$contentdesc = new stdClass();
$contentdesc->typename = get_string('description', $contentype->get_contenttype_name());
array_unshift($types, $contentdesc);
// Context data for the template.
$addcontenttype = new stdClass();
// Content type name.
$addcontenttype->name = $type;
// Content type editor base URL.
$tool['link']->param('plugin', $type);
$addcontenttype->baseurl = $tool['link']->out();
// Different types of the content type.
$addcontenttype->types = $types;
$addoptions[] = $addcontenttype;
}
}
$tool['contenttypes'] = $addoptions;
}
}

View File

@ -0,0 +1,94 @@
<?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/>.
/**
* Class containing data for a content view.
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_contentbank\output;
use core_contentbank\content;
use core_contentbank\contenttype;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Class containing data for the content view.
*
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class viewcontent implements renderable, templatable {
/**
* @var contenttype Content bank content type.
*/
private $contenttype;
/**
* @var stdClass Record of the contentbank_content table.
*/
private $content;
/**
* Construct this renderable.
*
* @param contenttype $contenttype Content bank content type.
* @param content $content Record of the contentbank_content table.
*/
public function __construct(contenttype $contenttype, content $content) {
$this->contenttype = $contenttype;
$this->content = $content;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
*
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = new stdClass();
// Get the content type html.
$contenthtml = $this->contenttype->get_view_content($this->content);
$data->contenthtml = $contenthtml;
// Check if the user can edit this content type.
if ($this->contenttype->can_edit()) {
$data->usercanedit = true;
$urlparams = [
'contextid' => $this->content->get_contextid(),
'plugin' => $this->contenttype->get_plugin_name(),
'id' => $this->content->get_id()
];
$editcontenturl = new moodle_url('/contentbank/edit.php', $urlparams);
$data->editcontenturl = $editcontenturl->out(false);
}
$closeurl = new moodle_url('/contentbank/index.php', ['contextid' => $this->content->get_contextid()]);
$data->closeurl = $closeurl->out(false);
return $data;
}
}

View File

@ -25,7 +25,11 @@
namespace contenttype_h5p;
use core\event\contentbank_content_viewed;
use html_writer;
use stdClass;
use core_h5p\editor_ajax;
use core_h5p\file_storage;
use core_h5p\local\library\autoloader;
use H5PCore;
/**
* H5P content bank manager class
@ -65,8 +69,7 @@ class contenttype extends \core_contentbank\contenttype {
$event->trigger();
$fileurl = $content->get_file_url();
$html = html_writer::tag('h2', $content->get_name());
$html .= \core_h5p\player::display($fileurl, new \stdClass(), true);
$html = \core_h5p\player::display($fileurl, new \stdClass(), true);
return $html;
}
@ -107,7 +110,7 @@ class contenttype extends \core_contentbank\contenttype {
* @return array
*/
protected function get_implemented_features(): array {
return [self::CAN_UPLOAD];
return [self::CAN_UPLOAD, self::CAN_EDIT];
}
/**
@ -127,4 +130,42 @@ class contenttype extends \core_contentbank\contenttype {
protected function is_access_allowed(): bool {
return true;
}
/**
* Returns the list of different H5P content types the user can create.
*
* @return array An object for each H5P content type:
* - string typename: descriptive name of the H5P content type.
* - string typeeditorparams: params required by the H5P editor.
* - url typeicon: H5P content type icon.
*/
public function get_contenttype_types(): array {
// Get the H5P content types available.
autoloader::register();
$editorajax = new editor_ajax();
$h5pcontenttypes = $editorajax->getLatestLibraryVersions();
$types = [];
$h5pfilestorage = new file_storage();
foreach ($h5pcontenttypes as $h5pcontenttype) {
$library = [
'name' => $h5pcontenttype->machine_name,
'majorVersion' => $h5pcontenttype->major_version,
'minorVersion' => $h5pcontenttype->minor_version,
];
$key = H5PCore::libraryToString($library);
$type = new stdClass();
$type->key = $key;
$type->typename = $h5pcontenttype->title;
$type->typeeditorparams = 'library=' . $key;
$type->typeicon = $h5pfilestorage->get_icon_url(
$h5pcontenttype->id,
$h5pcontenttype->machine_name,
$h5pcontenttype->major_version,
$h5pcontenttype->minor_version);
$types[] = $type;
}
return $types;
}
}

View File

@ -0,0 +1,152 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the class that defines the form for the H5P authoring tool.
*
* @package contenttype_h5p
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace contenttype_h5p\form;
use contenttype_h5p\content;
use contenttype_h5p\contenttype;
use core_contentbank\form\edit_content;
use core_h5p\api;
use core_h5p\editor as h5peditor;
use core_h5p\factory;
use stdClass;
/**
* Defines the form for editing an H5P content.
*
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editor extends edit_content {
/** @var $h5peditor H5P editor object */
private $h5peditor;
/** @var $content The content being edited */
private $content;
/**
* Defines the form fields.
*/
protected function definition() {
global $DB;
$mform = $this->_form;
// Id of the content to edit.
$id = $this->_customdata['id'];
// H5P content type to create.
$library = optional_param('library', null, PARAM_TEXT);
if (empty($id) && empty($library)) {
$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $this->_customdata['contextid']]);
print_error('invalidcontentid', 'error', $returnurl);
}
$this->h5peditor = new h5peditor();
if ($id) {
// The H5P editor needs the H5P content id (h5p table).
$record = $DB->get_record('contentbank_content', ['id' => $id]);
$this->content = new content($record);
$file = $this->content->get_file();
$h5p = api::get_content_from_pathnamehash($file->get_pathnamehash());
$mform->addElement('hidden', 'h5pid', $h5p->id);
$mform->setType('h5pid', PARAM_INT);
$this->h5peditor->set_content($h5p->id);
} else {
// The H5P editor needs the H5P content type library name for a new content.
$mform->addElement('hidden', 'library', $library);
$mform->setType('library', PARAM_TEXT);
$this->h5peditor->set_library($library, $this->_customdata['contextid'], 'contentbank', 'public');
}
$mformid = 'coolh5peditor';
$mform->setAttributes(array('id' => $mformid) + $mform->getAttributes());
$this->add_action_buttons();
$this->h5peditor->add_editor_to_form($mform);
$this->add_action_buttons();
}
/**
* Modify or create an H5P content from the form data.
*
* @param stdClass $data Form data to create or modify an H5P content.
*
* @return int The id of the edited or created content.
*/
public function save_content(stdClass $data): int {
global $DB;
// The H5P libraries expect data->id as the H5P content id.
// The method \H5PCore::saveContent throws an error if id is set but empty.
if (empty($data->id)) {
unset($data->id);
} else {
// The H5P libraries save in $data->id the H5P content id (h5p table), so the content id is saved in another var.
$contentid = $data->id;
}
$h5pcontentid = $this->h5peditor->save_content($data);
$factory = new factory();
$h5pfs = $factory->get_framework();
// Needs the H5P file id to create or update the content bank record.
$h5pcontent = $h5pfs->loadContent($h5pcontentid);
$fs = get_file_storage();
$file = $fs->get_file_by_hash($h5pcontent['pathnamehash']);
// Creating new content.
if (!isset($data->h5pid)) {
// The initial name of the content is the title of the H5P content.
$cbrecord = new stdClass();
$cbrecord->name = json_decode($data->h5pparams)->metadata->title;
$context = \context::instance_by_id($data->contextid, MUST_EXIST);
// Create entry in content bank.
$contenttype = new contenttype($context);
$newcontent = $contenttype->create_content($cbrecord);
if ($file && $newcontent) {
$updatedfilerecord = new stdClass();
$updatedfilerecord->id = $file->get_id();
$updatedfilerecord->itemid = $newcontent->get_id();
// As itemid changed, the pathnamehash has to be updated in the file table.
$pathnamehash = \file_storage::get_pathname_hash($file->get_contextid(), $file->get_component(),
$file->get_filearea(), $updatedfilerecord->itemid, $file->get_filepath(), $file->get_filename());
$updatedfilerecord->pathnamehash = $pathnamehash;
$DB->update_record('files', $updatedfilerecord);
// The pathnamehash in the h5p table must match the file pathnamehash.
$h5pfs->updateContentFields($h5pcontentid, ['pathnamehash' => $pathnamehash]);
}
} else {
// Update content.
$this->content->update_content();
}
return $contentid ?? $newcontent->get_id();
}
}

View File

@ -44,4 +44,14 @@ $capabilities = [
'editingteacher' => CAP_ALLOW,
]
],
'contenttype/h5p:useeditor' => [
'riskbitmask' => RISK_SPAM,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => [
'manager' => CAP_ALLOW,
'coursecreator' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
]
],
];

View File

@ -22,8 +22,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['description'] = 'H5P Interactive Content';
$string['pluginname'] = 'H5P';
$string['pluginname_help'] = 'Content bank to upload and share H5P content';
$string['privacy:metadata'] = 'The H5P content bank plugin does not store any personal data.';
$string['h5p:access'] = 'Access H5P content in the content bank';
$string['h5p:upload'] = 'Upload new H5P content';
$string['h5p:useeditor'] = 'Create or edit content using the H5P editor';

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020041500.00; // The current plugin version (Date: YYYYMMDDXX)
$plugin->version = 2020051500.01; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2020041500.00; // Requires this Moodle version
$plugin->component = 'contenttype_h5p'; // Full name of the plugin (used for diagnostics).

110
contentbank/edit.php Normal file
View File

@ -0,0 +1,110 @@
<?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/>.
/**
* Create or update contents through the specific content type editor
*
* @package core_contentbank
* @copyright 2020 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../config.php');
require_login();
$contextid = required_param('contextid', PARAM_INT);
$pluginname = required_param('plugin', PARAM_PLUGIN);
$id = optional_param('id', null, PARAM_INT);
$context = context::instance_by_id($contextid, MUST_EXIST);
require_capability('moodle/contentbank:access', $context);
$returnurl = new \moodle_url('/contentbank/view.php', ['id' => $id]);
if (!empty($id)) {
$record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST);
$contentclass = "$record->contenttype\\content";
$content = new $contentclass($record);
// Set the heading title.
$heading = $content->get_name();
// The content type of the content overwrites the pluginname param value.
$contenttypename = $content->get_content_type();
} else {
$contenttypename = "contenttype_$pluginname";
$heading = get_string('addinganew', 'moodle', get_string('description', $contenttypename));
}
// Check plugin is enabled.
$plugin = core_plugin_manager::instance()->get_plugin_info($contenttypename);
if (!$plugin || !$plugin->is_enabled()) {
print_error('unsupported', 'core_contentbank', $returnurl);
}
// Create content type instance.
$contenttypeclass = "$contenttypename\\contenttype";
if (class_exists($contenttypeclass)) {
$contenttype = new $contenttypeclass($context);
} else {
print_error('unsupported', 'core_contentbank', $returnurl);
}
// Checks the user can edit this content type.
if (!$contenttype->can_edit()) {
print_error('contenttypenoedit', 'core_contentbank', $returnurl, $contenttype->get_plugin_name());
}
$values = [
'contextid' => $contextid,
'plugin' => $pluginname,
'id' => $id
];
$title = get_string('contentbank');
\core_contentbank\helper::get_page_ready($context, $title, true);
if ($PAGE->course) {
require_login($PAGE->course->id);
}
$PAGE->set_url(new \moodle_url('/contentbank/edit.php', $values));
$PAGE->set_context($context);
$PAGE->navbar->add(get_string('edit'));
$PAGE->set_title($title);
$PAGE->set_heading($heading);
// Instantiate the content type form.
$editorclass = "$contenttypename\\form\\editor";
if (!class_exists($editorclass)) {
print_error('noformdesc');
}
$editorform = new $editorclass(null, $values);
if ($editorform->is_cancelled()) {
if (empty($id)) {
$returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
}
redirect($returnurl);
} else if ($data = $editorform->get_data()) {
$id = $editorform->save_content($data);
// Just in case we've created a new content.
$returnurl->param('id', $id);
redirect($returnurl);
}
echo $OUTPUT->header();
$editorform->display();
echo $OUTPUT->footer();

View File

@ -62,6 +62,19 @@ $foldercontents = $cb->search_contents($search, $contextid, $contenttypes);
// Get the toolbar ready.
$toolbar = array ();
// Place the Add button in the toolbar.
if (has_capability('moodle/contentbank:useeditor', $context)) {
// Get the content types for which the user can use an editor.
$editabletypes = $cb->get_contenttypes_with_capability_feature(\core_contentbank\contenttype::CAN_EDIT, $context);
if (!empty($editabletypes)) {
// Editor base URL.
$editbaseurl = new moodle_url('/contentbank/edit.php', ['contextid' => $contextid]);
$toolbar[] = ['name' => get_string('add'), 'link' => $editbaseurl, 'dropdown' => true, 'contenttypes' => $editabletypes];
}
}
// Place the Upload button in the toolbar.
if (has_capability('moodle/contentbank:upload', $context)) {
// Don' show upload button if there's no plugin to support any file extension.
$accepted = $cb->get_supported_extensions_as_string($context);

View File

@ -15,7 +15,7 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_contentbank/list
@template core_contentbank/bankcontent
Example context (json):
{
@ -32,10 +32,36 @@
},
{
"name": "resume.pdf",
"title": "resume",
"timemodified": 1589792039,
"size": "699.3KB",
"bytes": 716126,
"type": "Archive (PDF)",
"icon": "http://something/theme/image.php/boost/core/1584597850/f/pdf-64"
}
],
"tools": [
{
"name": "Add",
"dropdown": true,
"link": "http://something/contentbank/edit.php?contextid=1",
"contenttypes": [
{
"name": "H5P Interactive Content",
"baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
"types": [
{
"typename": "H5P Interactive Content"
},
{
"typename": "Accordion",
"typeeditorparams": "library=Accordion-1.4",
"typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
}
]
}
]
},
{
"name": "Upload",
"link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",

View File

@ -20,6 +20,27 @@
Example context (json):
{
"tools": [
{
"name": "Add",
"dropdown": true,
"link": "http://something/contentbank/edit.php?contextid=1",
"contenttypes": [
{
"name": "h5p",
"baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
"types": [
{
"typename": "H5P Interactive Content"
},
{
"typename": "Accordion",
"typeeditorparams": "library=Accordion-1.4",
"typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
}
]
}
]
},
{
"name": "Upload",
"link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",
@ -34,9 +55,14 @@
}}
{{#tools}}
<a href="{{{ link }}}" class="icon-no-margin btn btn-secondary" title="{{{ name }}}">
{{#pix}} {{{ icon }}} {{/pix}} {{{ name }}}
</a>
{{#dropdown}}
{{>core_contentbank/bankcontent/toolbar_dropdown}}
{{/dropdown}}
{{^dropdown}}
<a href="{{{ link }}}" class="icon-no-margin btn btn-secondary" title="{{{ name }}}">
{{#pix}} {{{ icon }}} {{/pix}} {{{ name }}}
</a>
{{/dropdown}}
{{/tools}}
<button class="icon-no-margin btn btn-secondary active ml-2"
title="{{#str}} displayicons, contentbank {{/str}}"
@ -47,4 +73,4 @@ data-action="viewgrid">
title="{{#str}} displaydetails, contentbank {{/str}}"
data-action="viewlist">
{{#pix}}t/viewdetails, core, {{#str}} displaydetails, contentbank {{/str}} {{/pix}}
</button>
</button>

View File

@ -0,0 +1,64 @@
{{!
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/bankcontent/toolbar_dropdown
Example context (json):
{
"name": "Add",
"dropdown": true,
"link": "http://something/contentbank/edit.php?contextid=1",
"contenttypes": [
{
"name": "h5p",
"baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
"types": [
{
"typename": "H5P Interactive Content"
},
{
"typename": "Accordion",
"typeeditorparams": "library=Accordion-1.4",
"typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
}
]
}
]
}
}}
<div class="btn-group mr-1" role="group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" data-action="{{name}}-content"
aria-haspopup="true" aria-expanded="false" {{^contenttypes}}title="{{#str}}nocontenttypes, core_contentbank{{/str}}"
disabled{{/contenttypes}}>
{{#name}} {{name}} {{/name}}
</button>
<div class="dropdown-menu dropdown-scrollable dropdown-menu-right">
{{#contenttypes}}
{{#types}}
{{^typeeditorparams}}
<h6 class="dropdown-header">{{ typename }}</h6>
{{/typeeditorparams}}
{{#typeeditorparams}}
<a class="dropdown-item icon-size-4" href="{{{ baseurl }}}&{{{ typeeditorparams }}}">
<img alt="" class="icon" src="{{{ typeicon }}}"> {{ typename }}
</a>
{{/typeeditorparams}}
{{/types}}
{{/contenttypes}}
</div>
</div>

View File

@ -0,0 +1,52 @@
{{!
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 comments.
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/view_content
View content page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* contenthtml - string - content html.
* usercanedit - boolean - whether the user has permission to edit the content.
* editcontenturl - string - edit page URL.
* closeurl - string - close landing page.
Example context (json):
{
"contenthtml" : "<iframe src=\"http://something/h5p/embed.php?url=h5pfileurl\"></iframe>",
"usercanedit" : true,
"editcontenturl" : "http://something/contentbank/edit.php?contextid=1&plugin=h5p&id=1",
"closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php"
}
}}
<div class="core_contentbank_viewcontent">
<div class="d-flex justify-content-end flex-column flex-sm-row">
{{>core_contentbank/viewcontent/toolbarview}}
</div>
<div class="container mt-1 mb-1" data-region="viewcontent-content">
{{{ contenthtml }}}
</div>
<div class="d-flex justify-content-end flex-column flex-sm-row">
{{>core_contentbank/viewcontent/toolbarview}}
</div>
</div>

View File

@ -0,0 +1,50 @@
{{!
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 comments.
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/viewcontent/toolbarview
Contentbank view toolbar.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* contenthtml - string - content html.
* usercanedit - boolean - whether the user has permission to edit the content.
* editcontenturl - string - edit page URL.
* closeurl - string - close landing page.
Example context (json):
{
"usercanedit" : true,
"editcontenturl" : "http://something/contentbank/edit.php?contextid=1&plugin=h5p&id=1",
"closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php"
}
}}
{{#usercanedit}}
<div class="cb-toolbar-container mb-2">
<a href="{{editcontenturl}}" class="btn btn-primary" data-action="edit-content">
{{#str}}edit{{/str}}
</a>
<a href="{{closeurl}}" class="btn btn-secondary" data-action="close-content">
{{#str}}close, core_contentbank{{/str}}
</a>
</div>
{{/usercanedit}}

View File

@ -0,0 +1,99 @@
@core @core_contentbank @contentbank_h5p @_file_upload @javascript
Feature: Content bank use editor feature
In order to add/edit content
As a user
I need to be able to access the edition options
Background:
Given I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I add the "Navigation" block if not present
And I configure the "Navigation" block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
Scenario: Users see the Add button disabled if there is no content type available for creation
Given I click on "Site pages" "list_item" in the "Navigation" "block"
When I click on "Content bank" "link"
Then the "[data-action=Add-content]" "css_element" should be disabled
Scenario: Users can see the Add button if there is content type available for creation
Given I follow "Dashboard" in the user menu
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "filltheblanks.h5p" "link"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
When I click on "Content bank" "link"
And I click on "filltheblanks.h5p" "link"
And I click on "Close" "link"
Then I click on "[data-action=Add-content]" "css_element"
And I should see "Fill in the Blanks"
Scenario: Users can edit content if they have the required permission
Given I follow "Dashboard" in the user menu
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link" in the "Navigation" "block"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "filltheblanks.h5p" "link"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
When I click on "Content bank" "link"
And I click on "filltheblanks.h5p" "link"
Then I click on "Edit" "link"
And I switch to "h5p-editor-iframe" class iframe
And I switch to the main frame
And I click on "Cancel" "button"
And I should see "filltheblanks.h5p" in the "h1" "css_element"
Scenario: Users can create new content if they have the required permission
Given I navigate to "H5P > Manage H5P content types" in site administration
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
And I should see "H5P content types uploaded successfully"
And I click on "Site pages" "list_item" in the "Navigation" "block"
When I click on "Content bank" "link" in the "Navigation" "block"
And I click on "[data-action=Add-content]" "css_element"
Then I click on "Fill in the Blanks" "link"
And I switch to "h5p-editor-iframe" class iframe
And I switch to the main frame
And I click on "Cancel" "button"
Scenario: Users can't edit content if they don't have the required permission
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | user1@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 I navigate to "H5P > Manage H5P content types" in site administration
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
And I should see "H5P content types uploaded successfully"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Content bank" "link"
And "[data-action=Add-content]" "css_element" should exist
When the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/contentbank:useeditor | Prohibit | editingteacher | System | |
And I reload the page
Then "[data-action=Add-content]" "css_element" should not exist

View File

@ -507,4 +507,100 @@ class core_contentbank_testcase extends advanced_testcase {
// Check there's no error when trying to move content context from an empty content bank.
$this->assertTrue($cb->delete_contents($systemcontext, $coursecontext));
}
/**
* Data provider for get_contenttypes_with_capability_feature.
*
* @return array
*/
public function get_contenttypes_with_capability_feature_provider(): array {
return [
'no-contenttypes_enabled' => [
'contenttypesenabled' => [],
'contenttypescanfeature' => [],
],
'contenttype_enabled_noeditable' => [
'contenttypesenabled' => ['testable'],
'contenttypescanfeature' => [],
],
'contenttype_enabled_editable' => [
'contenttypesenabled' => ['testable'],
'contenttypescanfeature' => ['testable'],
],
'no-contenttype_enabled_editable' => [
'contenttypesenabled' => [],
'contenttypescanfeature' => ['testable'],
],
];
}
/**
* Tests for get_contenttypes_with_capability_feature() function.
*
* @dataProvider get_contenttypes_with_capability_feature_provider
* @param array $contenttypesenabled Content types enabled.
* @param array $contenttypescanfeature Content types the user has the permission to use the feature.
*
* @covers ::get_contenttypes_with_capability_feature
*/
public function test_get_contenttypes_with_capability_feature(array $contenttypesenabled, array $contenttypescanfeature): void {
$this->resetAfterTest();
$cb = new contentbank();
$plugins = [];
// Content types not enabled where the user has permission to use a feature.
if (empty($contenttypesenabled) && !empty($contenttypescanfeature)) {
$enabled = false;
// Mock core_plugin_manager class and the method get_plugins_of_type.
$pluginmanager = $this->getMockBuilder(\core_plugin_manager::class)
->disableOriginalConstructor()
->setMethods(['get_plugins_of_type'])
->getMock();
// Replace protected singletoninstance reference (core_plugin_manager property) with mock object.
$ref = new \ReflectionProperty(\core_plugin_manager::class, 'singletoninstance');
$ref->setAccessible(true);
$ref->setValue(null, $pluginmanager);
// Return values of get_plugins_of_type method.
foreach ($contenttypescanfeature as $contenttypepluginname) {
$contenttypeplugin = new \stdClass();
$contenttypeplugin->name = $contenttypepluginname;
$contenttypeplugin->type = 'contenttype';
// Add the feature to the fake content type.
$classname = "\\contenttype_$contenttypepluginname\\contenttype";
$classname::$featurestotest = ['test2'];
$plugins[] = $contenttypeplugin;
}
// Set expectations and return values.
$pluginmanager->expects($this->once())
->method('get_plugins_of_type')
->with('contenttype')
->willReturn($plugins);
} else {
$enabled = true;
// Get access to private property enabledcontenttypes.
$rc = new \ReflectionClass(\core_contentbank\contentbank::class);
$rcp = $rc->getProperty('enabledcontenttypes');
$rcp->setAccessible(true);
foreach ($contenttypesenabled as $contenttypename) {
$plugins["\\contenttype_$contenttypename\\contenttype"] = $contenttypename;
// Add to the testable contenttype the feature to test.
if (in_array($contenttypename, $contenttypescanfeature)) {
$classname = "\\contenttype_$contenttypename\\contenttype";
$classname::$featurestotest = ['test2'];
}
}
// Set as enabled content types only those in the test.
$rcp->setValue($cb, $plugins);
}
$actual = $cb->get_contenttypes_with_capability_feature('test2', null, $enabled);
$this->assertEquals($contenttypescanfeature, array_values($actual));
}
}

View File

@ -37,6 +37,9 @@ class contenttype extends \core_contentbank\contenttype {
/** Feature for testing */
const CAN_TEST = 'test';
/** @var array Additional features for testing */
public static $featurestotest;
/**
* Returns the HTML code to render the icon for content bank contents.
*
@ -55,7 +58,13 @@ class contenttype extends \core_contentbank\contenttype {
* @return array
*/
protected function get_implemented_features(): array {
return [self::CAN_TEST];
$features = [self::CAN_TEST];
if (!empty(self::$featurestotest)) {
$features = array_merge($features, self::$featurestotest);
}
return $features;
}
/**
@ -66,4 +75,29 @@ class contenttype extends \core_contentbank\contenttype {
public function get_manageable_extensions(): array {
return ['.txt', '.png', '.h5p'];
}
/**
* Returns the list of different types of the given content type.
*
* @return array
*/
public function get_contenttype_types(): array {
$type = new \stdClass();
$type->typename = 'testable';
return [$type];
}
/**
* Returns true, so the user has permission on the feature.
*
* @return bool True if content could be edited or created. False otherwise.
*/
final public function can_test2(): bool {
if (!$this->is_feature_supported('test2')) {
return false;
}
return true;
}
}

View File

@ -53,7 +53,7 @@ if ($PAGE->course) {
$PAGE->set_url(new \moodle_url('/contentbank/view.php', ['id' => $id]));
$PAGE->set_context($context);
$PAGE->navbar->add($record->name);
$PAGE->set_heading($title);
$PAGE->set_heading($record->name);
$title .= ": ".$record->name;
$PAGE->set_title($title);
$PAGE->set_pagetype('contenbank');
@ -109,7 +109,6 @@ $PAGE->add_header_action(html_writer::div(
));
echo $OUTPUT->header();
echo $OUTPUT->box_start('generalbox');
// If needed, display notifications.
if ($errormsg !== '') {
@ -118,8 +117,11 @@ if ($errormsg !== '') {
echo $OUTPUT->notification($statusmsg, 'notifysuccess');
}
if ($contenttype->can_access()) {
echo $contenttype->get_view_content($content);
$viewcontent = new core_contentbank\output\viewcontent($contenttype, $content);
echo $OUTPUT->render($viewcontent);
} else {
$message = get_string('contenttypenoaccess', 'core_contentbank', $record->contenttype);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->box_end();
echo $OUTPUT->footer();

View File

@ -24,12 +24,15 @@
$string['author'] = 'Author';
$string['contentbank'] = 'Content bank';
$string['close'] = 'Close';
$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['contentsmoved'] = 'Content bank contents moved to {$a}.';
$string['contenttypenoaccess'] = 'You can not view this {$a} instance';
$string['contenttypenoedit'] = 'You can not edit contents of the {$a} content type';
$string['eventcontentcreated'] = 'Content created';
$string['eventcontentdeleted'] = 'Content deleted';
$string['eventcontentupdated'] = 'Content updated';
@ -45,6 +48,7 @@ $string['file_help'] = 'Files may be stored in the content bank for use in cours
$string['itemsfound'] = '{$a} items found';
$string['lastmodified'] = 'Last modified';
$string['name'] = 'Content';
$string['nocontenttypes'] = 'No content types available';
$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.';

View File

@ -156,6 +156,7 @@ $string['contentbank:deleteowncontent'] = 'Delete content from own 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['contentbank:useeditor'] = 'Create or edit content using a content type editor';
$string['context'] = 'Context';
$string['course:activityvisibility'] = 'Hide/show activities';
$string['course:bulkmessaging'] = 'Send a message to many people';

View File

@ -2544,4 +2544,16 @@ $capabilities = array(
'editingteacher' => CAP_ALLOW,
)
],
// Allow users to create/edit content within the content bank.
'moodle/contentbank:useeditor' => [
'riskbitmask' => RISK_SPAM,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'manager' => CAP_ALLOW,
'coursecreator' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
)
],
);

View File

@ -120,4 +120,9 @@
}
}
}
}
.cb-toolbar .dropdown-scrollable {
max-height: 190px;
overflow-y: auto;
}

View File

@ -13031,6 +13031,10 @@ table.calendartable caption {
.content-bank-container.view-list .cb-btnsort.dir-desc .desc {
display: block; }
.cb-toolbar .dropdown-scrollable {
max-height: 190px;
overflow-y: auto; }
/* course.less */
/* COURSE CONTENT */
.section_add_menus {

View File

@ -13246,6 +13246,10 @@ table.calendartable caption {
.content-bank-container.view-list .cb-btnsort.dir-desc .desc {
display: block; }
.cb-toolbar .dropdown-scrollable {
max-height: 190px;
overflow-y: auto; }
/* course.less */
/* COURSE CONTENT */
.section_add_menus {

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020052700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2020052700.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9dev+ (Build: 20200527)'; // Human-friendly version name