From 659e9b87070fadf2e429ddf89ac406c6d54ab0ae Mon Sep 17 00:00:00 2001 From: Daniel Neis Araujo Date: Wed, 27 Apr 2022 18:26:30 -0300 Subject: [PATCH] MDL-74590 contentbank: add custom fields to content bank --- admin/settings/plugins.php | 6 + contentbank/classes/content.php | 10 + contentbank/classes/contenttype.php | 7 + .../classes/customfield/content_handler.php | 186 ++++++++++++++++++ contentbank/classes/external/copy_content.php | 6 + contentbank/classes/form/edit_content.php | 19 +- contentbank/classes/output/customfields.php | 71 +++++++ contentbank/classes/output/viewcontent.php | 4 + .../contenttype/h5p/classes/form/editor.php | 11 +- contentbank/customfield.php | 37 ++++ contentbank/templates/customfields.mustache | 29 +++ contentbank/templates/viewcontent.mustache | 30 ++- contentbank/tests/behat/customfields.feature | 50 +++++ lang/en/contentbank.php | 1 + lang/en/moodle.php | 1 + lang/en/role.php | 2 + lib/db/access.php | 18 ++ version.php | 2 +- 18 files changed, 485 insertions(+), 5 deletions(-) create mode 100644 contentbank/classes/customfield/content_handler.php create mode 100644 contentbank/classes/output/customfields.php create mode 100644 contentbank/customfield.php create mode 100644 contentbank/templates/customfields.mustache create mode 100644 contentbank/tests/behat/customfields.feature diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php index 1a4761308fd9d..66659f9d48ac7 100644 --- a/admin/settings/plugins.php +++ b/admin/settings/plugins.php @@ -830,6 +830,12 @@ $temp = new admin_settingpage('managecontentbanktypes', new lang_string('managecontentbanktypes')); $temp->add(new admin_setting_managecontentbankcontenttypes()); $ADMIN->add('contentbanksettings', $temp); + $ADMIN->add('contentbanksettings', + new admin_externalpage('contentbank', new lang_string('contentbankcustomfields'), + $CFG->wwwroot . '/contentbank/customfield.php', + 'moodle/contentbank:configurecustomfields' + ) + ); $plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype'); foreach ($plugins as $plugin) { /** @var \core\plugininfo\contentbank $plugin */ diff --git a/contentbank/classes/content.php b/contentbank/classes/content.php index 583735ba815de..93f3312d30d59 100644 --- a/contentbank/classes/content.php +++ b/contentbank/classes/content.php @@ -397,4 +397,14 @@ public function is_view_allowed(): bool { $this->get_visibility() == self::VISIBILITY_PUBLIC || has_capability('moodle/contentbank:viewunlistedcontent', $context); } + + /** + * Checks if there are any custom field related to this content. + * + * @return bool True if there is at least one populated field. + */ + public function has_custom_fields(): bool { + $handler = \core_contentbank\customfield\content_handler::create(); + return !empty($handler->get_instance_data($this->get_id())); + } } diff --git a/contentbank/classes/contenttype.php b/contentbank/classes/contenttype.php index dc7f22a6f5c83..a6575ba2d61f9 100644 --- a/contentbank/classes/contenttype.php +++ b/contentbank/classes/contenttype.php @@ -238,10 +238,17 @@ public function get_view_url(content $content): string { * @return string HTML code to include in view.php. */ public function get_view_content(content $content): string { + global $PAGE; + // Trigger an event for viewing this content. $event = contentbank_content_viewed::create_from_record($content->get_content()); $event->trigger(); + if ($content->has_custom_fields()) { + $renderer = $PAGE->get_renderer('core'); + $renderable = new \core_contentbank\output\customfields($content); + return $renderer->render($renderable); + } return ''; } diff --git a/contentbank/classes/customfield/content_handler.php b/contentbank/classes/customfield/content_handler.php new file mode 100644 index 0000000000000..e0444d0f8117a --- /dev/null +++ b/contentbank/classes/customfield/content_handler.php @@ -0,0 +1,186 @@ +. + +namespace core_contentbank\customfield; + +use core_customfield\api; +use core_customfield\field_controller; + +/** + * Content handler for content bank custom fields + * + * @package core_contentbank + * @copyright 2024 Daniel Neis Araujo + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class content_handler extends \core_customfield\handler { + + /** + * @var content_handler + */ + static protected $singleton; + + /** + * @var \context + */ + protected $parentcontext; + + /** @var int Field is displayed in the content bank edit page, visible to everybody */ + const VISIBLETOALL = 2; + /** @var int Field is not displayed in the content bank edit page */ + const NOTVISIBLE = 0; + + /** + * Returns a singleton + * + * @param int $itemid + * @return \core_contentbank\customfield\content_handler + */ + public static function create(int $itemid = 0): \core_contentbank\customfield\content_handler { + if (static::$singleton === null) { + self::$singleton = new static(0); + } + return self::$singleton; + } + + /** + * Run reset code after unit tests to reset the singleton usage. + */ + public static function reset_caches(): void { + if (!PHPUNIT_TEST) { + throw new \coding_exception('This feature is only intended for use in unit tests'); + } + + static::$singleton = null; + } + + /** + * The current user can configure custom fields on this component. + * + * @return bool true if the current can configure custom fields, false otherwise + */ + public function can_configure(): bool { + return has_capability('moodle/contentbank:configurecustomfields', $this->get_configuration_context()); + } + + /** + * The current user can edit custom fields on the content bank. + * + * @param field_controller $field + * @param int $instanceid id of the course to test edit permission + * @return bool true if the current can edit custom fields, false otherwise + */ + public function can_edit(field_controller $field, int $instanceid = 0): bool { + $context = $this->get_instance_context($instanceid); + return (!$field->get_configdata_property('locked') || + has_capability('moodle/contentbank:changelockedcustomfields', $context)); + } + + /** + * The current user can view custom fields on the given course. + * + * @param field_controller $field + * @param int $instanceid id of the course to test edit permission + * @return bool true if the current can edit custom fields, false otherwise + */ + public function can_view(field_controller $field, int $instanceid): bool { + $visibility = $field->get_configdata_property('visibility'); + if ($visibility == self::NOTVISIBLE) { + return false; + } else { + return true; + } + } + + /** + * Context that should be used for new categories created by this handler + * + * @return \context the context for configuration + */ + public function get_configuration_context(): \context { + return \context_system::instance(); + } + + /** + * URL for configuration of the fields on this handler. + * + * @return \moodle_url The URL to configure custom fields for this component + */ + public function get_configuration_url(): \moodle_url { + return new \moodle_url('/contentbank/customfield.php'); + } + + /** + * Returns the context for the data associated with the given instanceid. + * + * @param int $instanceid id of the record to get the context for + * @return \context the context for the given record + */ + public function get_instance_context(int $instanceid = 0): \context { + global $DB; + return \context::instance_by_id($DB->get_field('contentbank_content', 'contextid', ['id' => $instanceid])); + } + + /** + * Allows to add custom controls to the field configuration form that will be saved in configdata + * + * @param \MoodleQuickForm $mform + */ + public function config_form_definition(\MoodleQuickForm $mform) { + $mform->addElement('header', 'content_handler_header', get_string('customfieldsettings', 'core_course')); + $mform->setExpanded('content_handler_header', true); + + // If field is locked. + $mform->addElement('selectyesno', 'configdata[locked]', get_string('customfield_islocked', 'core_course')); + $mform->addHelpButton('configdata[locked]', 'customfield_islocked', 'core_course'); + + // Field data visibility. + $visibilityoptions = [self::VISIBLETOALL => get_string('customfield_visibletoall', 'core_course'), + self::NOTVISIBLE => get_string('customfield_notvisible', 'core_course')]; + $mform->addElement('select', 'configdata[visibility]', get_string('customfield_visibility', 'core_course'), + $visibilityoptions); + $mform->addHelpButton('configdata[visibility]', 'customfield_visibility', 'core_course'); + } + + /** + * Creates or updates custom field data. + * + * @param \restore_task $task + * @param array $data + */ + public function restore_instance_data_from_backup(\restore_task $task, array $data) { + $courseid = $task->get_courseid(); + $context = $this->get_instance_context($courseid); + $editablefields = $this->get_editable_fields($courseid); + $records = api::get_instance_fields_data($editablefields, $courseid); + $target = $task->get_target(); + $override = ($target != \backup::TARGET_CURRENT_ADDING && $target != \backup::TARGET_EXISTING_ADDING); + + foreach ($records as $d) { + $field = $d->get_field(); + if ($field->get('shortname') === $data['shortname'] && $field->get('type') === $data['type']) { + if (!$d->get('id') || $override) { + $d->set($d->datafield(), $data['value']); + $d->set('value', $data['value']); + $d->set('valueformat', $data['valueformat']); + $d->set('contextid', $context->id); + $d->save(); + } + return; + } + } + } +} diff --git a/contentbank/classes/external/copy_content.php b/contentbank/classes/external/copy_content.php index bec18674aa0e7..720e2be099da3 100644 --- a/contentbank/classes/external/copy_content.php +++ b/contentbank/classes/external/copy_content.php @@ -91,6 +91,12 @@ public static function execute(int $contentid, string $name): array { $crecord->name = $params['name']; if ($content = $contenttype->create_content($crecord)) { + + $handler = \core_contentbank\customfield\content_handler::create(); + $handler->instance_form_before_set_data($record); + $record->id = $content->get_id(); + $handler->instance_form_save($record); + $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'contentbank', 'public', $params['contentid'], 'itemid, filepath, filename', false); diff --git a/contentbank/classes/form/edit_content.php b/contentbank/classes/form/edit_content.php index e75b7316fa2be..83fa94e41d274 100644 --- a/contentbank/classes/form/edit_content.php +++ b/contentbank/classes/form/edit_content.php @@ -56,10 +56,10 @@ abstract class edit_content extends moodleform { * @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']; + $this->id = $customdata['id'] ?? 0; + parent::__construct($action, $customdata, $method); $mform =& $this->_form; $mform->addElement('hidden', 'contextid', $this->contextid); @@ -72,6 +72,21 @@ public function __construct(string $action = null, array $customdata = null, str $this->_form->setType('id', PARAM_INT); } + /** + * Add elements to form + */ + public function definition() { + global $DB; + // Add custom fields to the form. + $content = $DB->get_record('contentbank_content', ['id' => $this->id]); + $handler = \core_contentbank\customfield\content_handler::create(); + $handler->instance_form_definition($this->_form, $this->id); + if ($content) { + $handler->instance_form_before_set_data($content); + } + $this->set_data($content); + } + /** * Overrides formslib's add_action_buttons() method. * diff --git a/contentbank/classes/output/customfields.php b/contentbank/classes/output/customfields.php new file mode 100644 index 0000000000000..a7ee8c95f2490 --- /dev/null +++ b/contentbank/classes/output/customfields.php @@ -0,0 +1,71 @@ +. + +/** + * Content bank custom fields. + * + * @package core_contentbank + * @copyright 2022 Daniel Neis Araujo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_contentbank\output; + +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use core_contentbank\content; + +/** + * Content bank Custom Fields renderable class. + * + * @package core_contentbank + * @copyright 2022 Daniel Neis Araujo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfields implements renderable, templatable { + + /** + * Constructor. + * + * @param \core_contentbank\content $content The content object. + */ + public function __construct(content $content) { + $this->content = $content; + } + + /** + * Export the data. + * + * @param renderer_base $output + * @return stdClass + */ + public function export_for_template(renderer_base $output) { + global $DB; + + $context = new \stdClass(); + + $context->url = $this->content->get_file_url(); + $context->name = $this->content->get_name(); + + $handler = \core_contentbank\customfield\content_handler::create(); + $customfields = $handler->get_instance_data($this->content->get_id()); + $context->data = $handler->display_custom_fields_data($customfields); + + return $context; + } +} diff --git a/contentbank/classes/output/viewcontent.php b/contentbank/classes/output/viewcontent.php index 36c7b3b1142cf..a7df7ebbbe7a1 100644 --- a/contentbank/classes/output/viewcontent.php +++ b/contentbank/classes/output/viewcontent.php @@ -174,6 +174,10 @@ public function export_for_template(renderer_base $output): stdClass { $contenthtml = $this->contenttype->get_view_content($this->content); $data->contenthtml = $contenthtml; + $handler = \core_contentbank\customfield\content_handler::create(); + $customfields = $handler->get_instance_data($this->content->get_id()); + $data->customfieldshtml = $handler->display_custom_fields_data($customfields); + // Check if the user can edit this content type. if ($this->contenttype->can_edit($this->content)) { $data->usercanedit = true; diff --git a/contentbank/contenttype/h5p/classes/form/editor.php b/contentbank/contenttype/h5p/classes/form/editor.php index 0a6c9ea211b68..af16652e8a83d 100644 --- a/contentbank/contenttype/h5p/classes/form/editor.php +++ b/contentbank/contenttype/h5p/classes/form/editor.php @@ -50,7 +50,7 @@ class editor extends edit_content { /** * Defines the form fields. */ - protected function definition() { + public function definition() { global $DB, $OUTPUT; $mform = $this->_form; @@ -116,6 +116,7 @@ protected function definition() { $mform->addElement('cancel', 'cancel', get_string('back')); } else { $this->h5peditor->add_editor_to_form($mform); + parent::definition(); $this->add_action_buttons(); } } @@ -157,6 +158,10 @@ public function save_content(stdClass $data): int { // Create entry in content bank. $contenttype = new contenttype($context); $newcontent = $contenttype->create_content($cbrecord); + $cfdata = fullclone($data); + $cfdata->id = $newcontent->get_id(); + $handler = \core_contentbank\customfield\content_handler::create(); + $handler->instance_form_save($cfdata, true); if ($file && $newcontent) { $updatedfilerecord = new stdClass(); $updatedfilerecord->id = $file->get_id(); @@ -172,6 +177,10 @@ public function save_content(stdClass $data): int { } else { // Update content. $this->content->update_content(); + $cfdata = fullclone($data); + $cfdata->id = $this->content->get_id(); + $handler = \core_contentbank\customfield\content_handler::create(); + $handler->instance_form_save($cfdata, true); } return $contentid ?? $newcontent->get_id(); diff --git a/contentbank/customfield.php b/contentbank/customfield.php new file mode 100644 index 0000000000000..8b5acdbd2082e --- /dev/null +++ b/contentbank/customfield.php @@ -0,0 +1,37 @@ +. + +/** + * Manage Content Bank custom fields + * + * @package core_contentbank + * @copyright 2024 Daniel Neis Araujo + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../config.php'); +require_once($CFG->libdir.'/adminlib.php'); + +admin_externalpage_setup('contentbank'); + +$output = $PAGE->get_renderer('core_customfield'); +$handler = core_contentbank\customfield\content_handler::create(); +$outputpage = new \core_customfield\output\management($handler); + +echo $output->header(), + $output->heading(new lang_string('contentbank')), + $output->render($outputpage), + $output->footer(); diff --git a/contentbank/templates/customfields.mustache b/contentbank/templates/customfields.mustache new file mode 100644 index 0000000000000..232728647cd61 --- /dev/null +++ b/contentbank/templates/customfields.mustache @@ -0,0 +1,29 @@ +{{! + 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 . +}} +{{! + @template core_contentbank/customfields + + This template renders the view of the document on content bank. + + Example context (json): + { + } +}} + +
+ {{{data}}} +
diff --git a/contentbank/templates/viewcontent.mustache b/contentbank/templates/viewcontent.mustache index 991e4e81a116f..8f6ce61b16d85 100644 --- a/contentbank/templates/viewcontent.mustache +++ b/contentbank/templates/viewcontent.mustache @@ -36,7 +36,8 @@ "contenthtml" : "", "usercanedit" : true, "editcontenturl" : "http://something/contentbank/edit.php?contextid=1&plugin=h5p&id=1", - "closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php" + "closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php", + "customfieldshtml" : "custom fields html" } }}
@@ -44,4 +45,31 @@
{{{ contenthtml }}}
+ {{#str}} custombankfields, core_contentbank {{/str}} +
diff --git a/contentbank/tests/behat/customfields.feature b/contentbank/tests/behat/customfields.feature new file mode 100644 index 0000000000000..d47cbca55b46d --- /dev/null +++ b/contentbank/tests/behat/customfields.feature @@ -0,0 +1,50 @@ +@core @core_contentbank @core_h5p @contentbank_h5p @_file_upload @javascript +Feature: Content bank custom fields + In order to add/edit custom fields for content + As a user + I need to be able to access the custom fields + + Background: + Given the following "blocks" exist: + | blockname | contextlevel | reference | pagetypepattern | defaultregion | + | private_files | System | 1 | my-index | side-post | + And the following "custom field categories" exist: + | name | component | area | itemid | + | Category for test | core_contentbank | content | 0 | + And I log in as "admin" + And I am on site homepage + And I turn editing mode on + And the following config values are set as admin: + | unaddableblocks | | theme_boost| + 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" + And I navigate to "Plugins > Content bank > Custom fields for Content bank" in site administration + And I click on "Add a new custom field" "link" + And I click on "Short text" "link" + And I set the following fields to these values: + | Name | Test field | + | Short name | testfield | + | Visible to | Everyone | + And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" + + Scenario: Users can edit customfields + Given I follow "Dashboard" + 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" + And I click on "Edit" "link" + And I set the following fields to these values: + | Test field | My test value | + When I click on "Save" "button" + Then I should see "Test field: My test value" diff --git a/lang/en/contentbank.php b/lang/en/contentbank.php index a5395a7804ac1..209ff09010026 100644 --- a/lang/en/contentbank.php +++ b/lang/en/contentbank.php @@ -42,6 +42,7 @@ $string['contextnotallowed'] = 'You are not allowed to access the content bank in this context.'; $string['copycontent'] = 'Copy content'; $string['copyof'] = 'Copy of {$a}'; +$string['custombankfields'] = 'Custom bank fields'; $string['emptynamenotallowed'] = 'Empty name is not allowed'; $string['eventcontentcreated'] = 'Content created'; $string['eventcontentdeleted'] = 'Content deleted'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 25c58bd59f322..23574fce0eef9 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -303,6 +303,7 @@ $string['contentexport_modulesummary'] = 'This page is part of the content downloaded from {$a->modulename} on {$a->date}. Note that some content and any files larger than {$a->maxfilesize} are not downloaded.'; $string['contentexport_viewfilename'] = 'View the file {$a}'; $string['contentbank'] = 'Content bank'; +$string['contentbankcustomfields'] = 'Custom fields for Content bank'; $string['context'] = 'Context'; $string['contextlevel'] = 'Context level'; $string['contextname'] = 'Context name'; diff --git a/lang/en/role.php b/lang/en/role.php index 915f240d8c371..6511b8ed55ea2 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -152,6 +152,8 @@ $string['confirmunassignyes'] = 'Remove'; $string['confirmunassignno'] = 'Cancel'; $string['contentbank:access'] = 'Access the content bank'; +$string['contentbank:changelockedcustomfields'] = 'Change locked custom fields for content bank'; +$string['contentbank:configurecustomfields'] = 'Configure custom fields for content bank'; $string['contentbank:copyanycontent'] = 'Copy any content in the content bank'; $string['contentbank:copycontent'] = 'Copy content in the content bank'; $string['contentbank:deleteanycontent'] = 'Delete any content from the content bank'; diff --git a/lib/db/access.php b/lib/db/access.php index eda798dd5b1ed..ff18332ced715 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -2629,6 +2629,24 @@ ] ], + 'moodle/contentbank:configurecustomfields' => [ + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], + + 'moodle/contentbank:changelockedcustomfields' => [ + 'riskbitmask' => RISK_SPAM, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], + // Allow users to download course content. 'moodle/course:downloadcoursecontent' => [ 'captype' => 'read', diff --git a/version.php b/version.php index de49c37e85a83..9d944cb77de88 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024071200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024071200.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.5dev (Build: 20240712)'; // Human-friendly version name